Plusaber's Blog

  • Home

  • Tags

  • Categories

  • Archives

Spring(1)_基础

Posted on 2015-06-05 | In java | Comments:

Introduction

Spring 框架是一个开源的 Java 平台,它为容易而快速的开发出耐用的 Java 应用程序提供了全面的基础设施。Spring最基本的使命是:简化Java开发。Spring 框架的核心特性可以用于开发任何 Java 应用程序,但是在 Java EE 平台上构建 web 应用程序是需要扩展的。 Spring 框架的目标是使 J2EE 开发变得更容易使用,通过启用基于 POJO 编程模型来促进良好的编程实践。

为了降低Java开发的复杂性,Spring采取了一下4种关键策略:

  • 基于POJO(bean)的轻量级和最小侵入性编程;
  • 通过依赖注入和面向接口实现松耦合;
  • 基于切面和管理进行声明式编程;
  • 通过切面和模板减少样板式代码。

Spring最重要的两个特性是依赖注入和基于切面编程。

依赖注入

在面向对象编程中,我们将不可避免的耦合对象,也就在一个对象的功能中需要依赖与其他一些对象。耦合具有两面性,一方面耦合的代码难以测试,难以复用。另一方面耦合是必须的—完全没有的耦合的代码什么也做不了。

1
2
3
4
5
6
7
8
9
public class DamselRescuingKnight implements Knight {
private RescueDamselQuest quest;
public DamselRescuingKnight() {
this.quest = new RescueDamselQuest(); // 紧耦合
}
public void embarkOnQuest() {
quest.embark();
}
}

如上面的代码,如果需要不同的quest的行为,则无法轻易实现,必须修改源代码,且无法复用。

通过依赖注入(DI),对象的依赖关系将有负责协调系统中对各个对象的第三方组件在创建对象时设定。对象无需自行创建或管理它们的依赖关系—也就是对象无需自己创建需要依赖的对象,而是创建时有第三方安排合适的需要依赖的对象给这个对象。

到底什么是依赖注入?让我们将这两个词分开来看一看。这里将依赖关系部分转化为两个类之间的关联。例如,类A依赖于类B。现在,让我们看一看第二部分,注入。所有这一切都意味着类B 将通过 IoC 被注入到类 A 中。

依赖注入可以以向构造函数传递参数的方式发生,或者通过使用 setter 方法 post-construction。

1
2
3
4
5
6
7
8
9
10
11
12
public class BraveKnight implements Knight {
private Quest quest;
public BraveKnight(Quest quest) {
this.quest = quest; // 构造器注入
}
public void embarkOnQuest() {
quest.embark();
}
public void setQuest(Quest quest) {
this.quest = quest; // setter注入
}
}

这样BraveKnigh没有与任何特定的Quest实现发生耦合。只需要直接使用依赖对象的方法,而该依赖对象会在运行时有第三方指定。这就是依赖注入最大的好处—松耦合。

现在BraveKnight类可以接受传递给它的任何一种Quest的实现,我们又该如何将特定的Query实现传递给它?

在Spring中,每个对象都是一个bean,由Spring容器管理。创建应用组件(对象)之间的协作关系的行为通常称为装配。Spring有多种装配Bean的方式,采用XML配置通常是最常见的装配方式,也就是,对象之间的协作关系都在XML中指定。下面是一个简单的Spring配置文件:knights.xml,该配置文件让BraveKnight接受了一个SlayDragonQuest探险任务。

1
2
3
4
5
6
7
8
9
10
11
12
13
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="knight" class="com.springinaction.knights.BraveKnight">
<constructor-arg ref="quest" />
</bean>

<!-- Inject quest bean -->
<bean id="quest" class="com.springinaction.knights.SlayDragonQuest"> <constructor-arg value="#{T(System).out}" />
</bean>
</beans>

下一步需要做的是装载XML配置文件,并把应用启动起来。
Spring通过应用上下文(Application Context)装载Bean的定义并把他们组装起来。Spring应用上下文全权负责对象的创建和组装。Spring自带了几种应用上下文的实现,它们之间的主要区别是如何加载它们的配置。

因为knight.xml中的Bean是在XML文件中声明的,随意选择ClassPathXmlApplicaitonContext作为应用上下文是比较合适的,该类加载位于应用系统classpath下的一个或多个xml文件。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import org.springframework.context.support.
ClassPathXmlApplicationContext;
public class KnightMain {
public static void main(String[] args) throws Exception {
ClassPathXmlApplicationContext context =
Load Spring context
Get knight bean
new ClassPathXmlApplicationContext(
"META-INF/spring/knight.xml");
Knight knight = context.getBean(Knight.class);
knight.embarkOnQuest();
context.close();
}
}

面向切面编程(AOP)

依赖注入让相互协作的软件组件保持松散耦合,而AOP编程允许把遍布应用的功能分离出来形成可重用的组件。

面向切面编程往往被定义为促使应用程序分离关注点的一项技术。系统通常由许多不同组件组成,每一个组件各负责一块特定功能。一些组件除了实现自身核心的功能之外,还经常承担额外的职责。诸如日志、事务管理和安全之类的系统服务经常融入到有自身核心业务逻辑的组件中去,这些系统业务通常称为横切关注点,因为它们总是跨越系统的多个组件。

如果将这些关注点,分散到多个组件中去,代码将出现双重复杂性:

  • 如果横切关注点实现代码将重复出现在多个组件(模块)中。如果要改变这些关注点的逻辑,则必须修改每个组件的相关代码。即是我们将其抽象成模块,其他模块只是调用其方法,但是方法的调用还是会重复出现在各个模块中。
  • 组件的代码可能会因为处理这些横切关注点的使用(也就是与自身核心业务无关的代码)变得混乱。一个像地址簿增加地址条目的方法应该只关注如何添加地址,而不是同时需要关注是不是安全或者是否需要支持事务。

下面图片展示了这种复杂性。左边的业务对象与系统级服务集合的过于紧密。每个对象不但需要知道它需要日志,安全控制,事务处理,而且还要亲自执行这些服务。

Spring_aop_example1

AOP使这些服务模块化,并以声明的方式讲它们应用到需要影响的组件中去。结果是功能组件具有更高内聚性以及更加关注自身业务,完全不需要了解系统服务的复杂性,从而保持POJO简单。

如下面图片所示,AOP可以让我们使用各种功能层去包裹核心业务。这些功能层也声明的方式应用到系统中,核心业务甚至根本不知道这些服务的存在。使得我们可以将横切关注点和业务逻辑模块分离。

Spring_aop_example2

假设knight在执行任务之前需要辅助者进行一些操作,最直接的方式是由knight自己直接调用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import java.io.PrintStream;
public class Minstrel {
private PrintStream stream;
public Minstrel(PrintStream stream) {
this.stream = stream;
}
public void singBeforeQuest() {
stream.println("Fa la la, the knight is so brave!");
}
public void singAfterQuest() {
stream.println("Tee hee hee, the brave knight " +
"did embark on a quest!");
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package com.springinaction.knights;
public class BraveKnight implements Knight {
private Quest quest;
private Minstrel minstrel;
public BraveKnight(Quest quest, Minstrel minstrel) {
this.quest = quest;
this.minstrel = minstrel;
}
public void embarkOnQuest() throws QuestException {
minstrel.singBeforeQuest();
quest.embark();
minstrel.singAfterQuest();
}
}
//Should a knight manage his own minstrel?

这样做可以达到预期效果,但是knight引入不少与自身业务无关的代码,如果需要更多的战斗前准备,代码将会变得很混乱和难以修改。我们希望kinght只需关注自己的业务,Minstrel能自己判断并在knight战斗前后执行相应操作。

我们可以把Minstrel抽象为一个切面:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
<?xml version="1.0" encoding="UTF-8"?>>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-3.2.xsd
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="knight" class="com.springinaction.knights.BraveKnight">
<constructor-arg ref="quest" />
</bean>
<bean id="quest" class="com.springinaction.knights.SlayDragonQuest"> <constructor-arg value="#{T(System).out}" />
</bean>
<bean id="minstrel" class="com.springinaction.knights.Minstrel">
<constructor-arg value="#{T(System).out}" />
</bean>
<aop:config>
<aop:aspect ref="minstrel">
<aop:pointcut id="embark"
expression="execution(* *.embarkOnQuest(..))"/>
<aop:before pointcut-ref="embark"
method="singBeforeQuest"/>
<aop:after pointcut-ref="embark"
method="singAfterQuest"/>
</aop:aspect>
</aop:config>
</beans>

这里使用Spring的AOP配置的命名空间把Minstrel Bean声明为一个切面。首先把其声明一个bean,然后在<aop:aspect>元素中引用该Bean。更加具体的语法说明会在后面介绍。

使用模板消除样板式代码

容纳Bean

Spring容器

在基于Spring的应用中,应用对象生存于Spring容器中。Spring容器负责创建对象,装配对象,配置,管理对象的整个生命周期。

容器是Spring框架的核心。Spring容器使用依赖注入管理构成应用的组件,创建相互协作的组件之间的关联。毫无疑问,这些对象更加简单干净,容易理解,更加容易重用以及更易于单元测试。

Spring有两种类型的容器:Bean工厂(BeanFactory)是最简单的容器,提供了基本的DI支持。应用上下文基于BeanFactory之上构建,并提供面向应用的服务,例如从属性文件解析文本信息的能力,以及发布应用事件给感兴趣的时间监听者的能力。

在大部分情况下Bean工厂由于太简单无法满足需求,所以一般使用上下文。

应用上下文

Spring自带了几种类型的应用上下文:

  • AnnotationConfigApplicationContext—Loads a Spring application context from one or more Java-based configuration classes
  • AnnotationConfigWebApplicationContext—Loads a Spring web application context from one or more Java-based configuration classes
  • ClassPathXmlApplicationContext—Loads a context definition from one or more XML files located in the classpath, treating context-definition files as class- path resources
  • FileSystemXmlApplicationContext—Loads a context definition from one or more XML files in the filesystem
  • XmlWebApplicationContext—Loads context definitions from one or more XML files contained in a web application

Bean的生命周期

  1. Spring instantiates the bean.
  2. Spring injects values and bean references into the bean’s properties.
  3. If the bean implements BeanNameAware, Spring passes the bean’s ID to the setBeanName() method.
  4. If the bean implements BeanFactoryAware, Spring calls the setBeanFactory() method, passing in the bean factory itself.
  5. If the bean implements ApplicationContextAware, Spring calls the setApplicationContext() method, passing in a reference to the enclosing application context.
  6. If the bean implements the BeanPostProcessor interface, Spring calls its postProcessBeforeInitialization() method.
  7. If the bean implements the InitializingBean interface, Spring calls its afterPropertiesSet() method. Similarly, if the bean was declared with an initmethod, then the specified initialization method is called.
  8. If the bean implements BeanPostProcessor, Spring calls its postProcessAfterInitialization() method.
  9. At this point, the bean is ready to be used by the application and remains in the application context until the application context is destroyed.
  10. If the bean implements the DisposableBean interface, Spring calls its destroy() method. Likewise, if the bean was declared with a destroy method, the specified method is called.

Proxy pattern

Posted on 2015-05-26 | In Design Pattern | Comments:

Proxy pattern

代理模式为另一个对象提供一个代理对象以控制对这个对象的访问,被代理的对象可以是远程对象、创建开销大的对象或者需要安全控制的对象。

远程代理

远程代理即是我们希望通过调用一个本地对象,然后其能将每个请求转发到远程对象上进行。换句话说,客户对象意味它调用的是远程服务上的方法,实际上是代理对象假装有客户要调用的方法,真正执行方法的在远程的对象。

Design_pattern_proxy_1

通过Java RMI我们可以简单实现远程调用,而无需处理细节的网络通信。

Design_pattern_proxy_2

使用Java RMI的具体步骤为:

  1. 创建远程接口
  2. 实现远程接口的类Impl(main方法需要注册到RMI中)
  3. 使用Java rmic生成Impl的stud和skeleton,也就是客户和服务的辅助类。
  4. 在远程机器上启动RMI registry(倾听Impl的注册)
  5. 运行主方法(可以是启动类)将Impl注册到RMI服务上。
  6. 客户端调用Naming方法取得stub对象。
  7. 客户调用哦该stub方法,就像stub就是真正的服务对象一样。

Design_pattern_proxy_3

Design_pattern_proxy_4

服务端实现:

1
2
3
4
5
public interface GumballMachineRemote extends Remote {
public int getCount() throws RemoteException;
public String getLocation() throws RemoteException;
public State getState() throws RemoteException;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
public interface State extends Serializable {
public void insertQuarter();
public void ejectQuarter();
public void turnCrank();
public void dispense();
}

public class NoQuarterState implements State {
transient GumballMachine gumballMachine;

public NoQuarterState(GumballMachine gumballMachine) {
this.gumballMachine = gumballMachine;
}

public void insertQuarter() {
System.out.println("You inserted a quarter");
gumballMachine.setState(gumballMachine.getHasQuarterState());
}

public void ejectQuarter() {
System.out.println("You haven't inserted a quarter");
}

public void turnCrank() {
System.out.println("You turned, but there's no quarter");
}

public void dispense() {
System.out.println("You need to pay first");
}

public String toString() {
return "waiting for quarter";
}
}

...
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
public class GumballMachine
extends UnicastRemoteObject implements GumballMachineRemote
{
State soldOutState;
State noQuarterState;
State hasQuarterState;
State soldState;
State winnerState;

State state = soldOutState;
int count = 0;
String location;

public GumballMachine(String location, int numberGumballs) throws RemoteException {
soldOutState = new SoldOutState(this);
noQuarterState = new NoQuarterState(this);
hasQuarterState = new HasQuarterState(this);
soldState = new SoldState(this);
winnerState = new WinnerState(this);

this.count = numberGumballs;
if (numberGumballs > 0) {
state = noQuarterState;
}
this.location = location;
}


public void insertQuarter() {
state.insertQuarter();
}

public void ejectQuarter() {
state.ejectQuarter();
}

public void turnCrank() {
state.turnCrank();
state.dispense();
}

void setState(State state) {
this.state = state;
}

void releaseBall() {
System.out.println("A gumball comes rolling out the slot...");
if (count != 0) {
count = count - 1;
}
}

public void refill(int count) {
this.count = count;
state = noQuarterState;
}

public int getCount() {
return count;
}

public State getState() {
return state;
}

public String getLocation() {
return location;
}

public State getSoldOutState() {
return soldOutState;
}

public State getNoQuarterState() {
return noQuarterState;
}

public State getHasQuarterState() {
return hasQuarterState;
}

public State getSoldState() {
return soldState;
}

public State getWinnerState() {
return winnerState;
}

public String toString() {
StringBuffer result = new StringBuffer();
result.append("\nMighty Gumball, Inc.");
result.append("\nJava-enabled Standing Gumball Model #2004");
result.append("\nInventory: " + count + " gumball");
if (count != 1) {
result.append("s");
}
result.append("\n");
result.append("Machine is " + state + "\n");
return result.toString();
}
}

在RMI registry中注册

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public class GumballMachineTestDrive {

public static void main(String[] args) {
GumballMachineRemote gumballMachine = null;
int count;

if (args.length < 2) {
System.out.println("GumballMachine <name> <inventory>");
System.exit(1);
}

try {
count = Integer.parseInt(args[1]);

gumballMachine =
new GumballMachine(args[0], count);
Naming.rebind("//" + args[0] + "/gumballmachine", gumballMachine);
} catch (Exception e) {
e.printStackTrace();
}
}
}

客户端

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class GumballMonitor {
GumballMachineRemote machine;

public GumballMonitor(GumballMachineRemote machine) {
this.machine = machine;
}

public void report() {
try {
System.out.println("Gumball Machine: " + machine.getLocation());
System.out.println("Current inventory: " + machine.getCount() + " gumballs");
System.out.println("Current state: " + machine.getState());
} catch (RemoteException e) {
e.printStackTrace();
}
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
public class GumballMonitorTestDrive {

public static void main(String[] args) {
String[] location = {"rmi://santafe.mightygumball.com/gumballmachine",
"rmi://boulder.mightygumball.com/gumballmachine",
"rmi://seattle.mightygumball.com/gumballmachine"};

GumballMonitor[] monitor = new GumballMonitor[location.length];

for (int i=0;i < location.length; i++) {
try {
GumballMachineRemote machine =
(GumballMachineRemote) Naming.lookup(location[i]);
monitor[i] = new GumballMonitor(machine);
System.out.println(monitor[i]);
} catch (Exception e) {
e.printStackTrace();
}
}

for(int i=0; i < monitor.length; i++) {
monitor[i].report();
}
}
}

虚拟代理

远程代理可以作为另一个JVM上对象的本地代表。调用代理的方法,会被代理利用网络转发到远程执行,并且结果会通过网络返回给代理,再有代理将结果转给客户。

Design_pattern_proxy_5

而虚拟代理通常作为创建开销大的对象的代表。虚拟代理经常直到我们真正需要一个对象的时候才创建它。当对象在创建前和创建中时,由虚拟代理来扮演对象的替身。对象创建后,代理就会将请求委托给对象。

比如我们需要创建一个CD封面对象,这个封面图片需要从网上下载。在下外完成前,我们最好也显式一些内容,这时我们就可以使用虚拟代理。

Design_pattern_proxy_6

  • ImageProxy首先创建一个ImageIcon,然后开始从网络上加载图像。
  • 在加载过程中,ImageProxy显式”加载中…”
  • 当图像加载完毕,ImageProxy把所有方法掉用委托给真正的ImageIcon。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
class ImageProxy implements Icon {
ImageIcon imageIcon;
URL imageURL;
Thread retrievalThread;
boolean retrieving = false;

public ImageProxy(URL url) { imageURL = url; }

public int getIconWidth() {
if (imageIcon != null) {
return imageIcon.getIconWidth();
} else {
return 800;
}
}

public int getIconHeight() {
if (imageIcon != null) {
return imageIcon.getIconHeight();
} else {
return 600;
}
}

public void paintIcon(final Component c, Graphics g, int x, int y) {
if (imageIcon != null) {
imageIcon.paintIcon(c, g, x, y);
} else {
g.drawString("Loading CD cover, please wait...", x+300, y+190);
if (!retrieving) {
retrieving = true;

retrievalThread = new Thread(new Runnable() {
public void run() {
try {
imageIcon = new ImageIcon(imageURL, "CD Cover");
c.repaint();
} catch (Exception e) {
e.printStackTrace();
}
}
});
retrievalThread.start();
}
}
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class ImageComponent extends JComponent {
private Icon icon;

public ImageComponent(Icon icon) {
this.icon = icon;
}

public void setIcon(Icon icon) {
this.icon = icon;
}

public void paintComponent(Graphics g) {
super.paintComponent(g);
int w = icon.getIconWidth();
int h = icon.getIconHeight();
int x = (800 - w)/2;
int y = (600 - h)/2;
icon.paintIcon(this, g, x, y);
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
public class ImageProxyTestDrive {
ImageComponent imageComponent;
JFrame frame = new JFrame("CD Cover Viewer");
JMenuBar menuBar;
JMenu menu;
Hashtable cds = new Hashtable();

public static void main (String[] args) throws Exception {
ImageProxyTestDrive testDrive = new ImageProxyTestDrive();
}

public ImageProxyTestDrive() throws Exception{
cds.put("Ambient: Music for Airports","http://images.amazon.com/images/P/B000003S2K.01.LZZZZZZZ.jpg");
cds.put("Buddha Bar","http://images.amazon.com/images/P/B00009XBYK.01.LZZZZZZZ.jpg");
cds.put("Ima","http://images.amazon.com/images/P/B000005IRM.01.LZZZZZZZ.jpg");
cds.put("Karma","http://images.amazon.com/images/P/B000005DCB.01.LZZZZZZZ.gif");
cds.put("MCMXC A.D.","http://images.amazon.com/images/P/B000002URV.01.LZZZZZZZ.jpg");
cds.put("Northern Exposure","http://images.amazon.com/images/P/B000003SFN.01.LZZZZZZZ.jpg");
cds.put("Selected Ambient Works, Vol. 2","http://images.amazon.com/images/P/B000002MNZ.01.LZZZZZZZ.jpg");

URL initialURL = new URL((String)cds.get("Selected Ambient Works, Vol. 2"));
menuBar = new JMenuBar();
menu = new JMenu("Favorite CDs");
menuBar.add(menu);
frame.setJMenuBar(menuBar);

for(Enumeration e = cds.keys(); e.hasMoreElements();) {
String name = (String)e.nextElement();
JMenuItem menuItem = new JMenuItem(name);
menu.add(menuItem);
menuItem.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent event) {
imageComponent.setIcon(new ImageProxy(getCDUrl(event.getActionCommand())));
frame.repaint();
}
});
}

// set up frame and menus

Icon icon = new ImageProxy(initialURL);
imageComponent = new ImageComponent(icon);
frame.getContentPane().add(imageComponent);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setSize(800,600);
frame.setVisible(true);

}

URL getCDUrl(String name) {
try {
return new URL((String)cds.get(name));
} catch (MalformedURLException e) {
e.printStackTrace();
return null;
}
}
}

使用Java API代理实现保护代理

Java在java.lang.reflect包中有自己的代理支持,利用哦该这个包我们可以在运行时动态的创建一个代理类,实现一个或多个接口,并将方法的调用转发到我们所指定的类。因为实际的代理是在运行时创建的,所以这个技术称为动态代理。

Design_pattern_proxy_7

我们不需要自己创建Proxy类,也不能往里面添加代码。我们希望处理代码应该放在InvocationHandler中。InvocationHandler的工作是响应代理的任何调用,可以认为是代理收到方法调用后实际执行请求的对象。

假设我们正在实现一个信息管理服务,我们希望当前用户可以更改自己的基本信息,但不是不能更改自己的客观信息,同时其他用户也不能更改当前用户的基本信息。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
public interface PersonBean {

String getName();
String getGender();
String getInterests();
int getHotOrNotRating();

void setName(String name);
void setGender(String gender);
void setInterests(String interests);
void setHotOrNotRating(int rating);

}

public class PersonBeanImpl implements PersonBean {
String name;
String gender;
String interests;
int rating;
int ratingCount = 0;

public String getName() {
return name;
}

public String getGender() {
return gender;
}

public String getInterests() {
return interests;
}

public int getHotOrNotRating() {
if (ratingCount == 0) return 0;
return (rating/ratingCount);
}


public void setName(String name) {
this.name = name;
}

public void setGender(String gender) {
this.gender = gender;
}

public void setInterests(String interests) {
this.interests = interests;
}

public void setHotOrNotRating(int rating) {
this.rating += rating;
ratingCount++;
}
}

这时我们可以为PersonBean创建动态代理,以过滤对这个类的请求:

  1. 创建InvocationHandler(如果有多种模式,需要创建多个,这里我们分为两类,操作用户是用户本人或是其他人,所以需要两个InvocationHandler)。
  2. 写代码创建动态代理。
  3. 利用适当的代理保证PersonBean对象,并将这个代理提供给客户,而不是对象本身。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public class OwnerInvocationHandler implements InvocationHandler { 
PersonBean person;

public OwnerInvocationHandler(PersonBean person) {
this.person = person;
}

public Object invoke(Object proxy, Method method, Object[] args)
throws IllegalAccessException {

try {
if (method.getName().startsWith("get")) {
return method.invoke(person, args);
} else if (method.getName().equals("setHotOrNotRating")) {
throw new IllegalAccessException();
} else if (method.getName().startsWith("set")) {
return method.invoke(person, args);
}
} catch (InvocationTargetException e) {
e.printStackTrace();
}
return null;
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public class NonOwnerInvocationHandler implements InvocationHandler { 
PersonBean person;

public NonOwnerInvocationHandler(PersonBean person) {
this.person = person;
}

public Object invoke(Object proxy, Method method, Object[] args)
throws IllegalAccessException {

try {
if (method.getName().startsWith("get")) {
return method.invoke(person, args);
} else if (method.getName().equals("setHotOrNotRating")) {
return method.invoke(person, args);
} else if (method.getName().startsWith("set")) {
throw new IllegalAccessException();
}
} catch (InvocationTargetException e) {
e.printStackTrace();
}
return null;
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
public class MatchMakingTestDrive {
Hashtable datingDB = new Hashtable();

public static void main(String[] args) {
MatchMakingTestDrive test = new MatchMakingTestDrive();
test.drive();
}

public MatchMakingTestDrive() {
initializeDatabase();
}

public void drive() {
PersonBean joe = getPersonFromDatabase("Joe Javabean");
PersonBean ownerProxy = getOwnerProxy(joe);
System.out.println("Name is " + ownerProxy.getName());
ownerProxy.setInterests("bowling, Go");
System.out.println("Interests set from owner proxy");
try {
ownerProxy.setHotOrNotRating(10);
} catch (Exception e) {
System.out.println("Can't set rating from owner proxy");
}
System.out.println("Rating is " + ownerProxy.getHotOrNotRating());

PersonBean nonOwnerProxy = getNonOwnerProxy(joe);
System.out.println("Name is " + nonOwnerProxy.getName());
try {
nonOwnerProxy.setInterests("bowling, Go");
} catch (Exception e) {
System.out.println("Can't set interests from non owner proxy");
}
nonOwnerProxy.setHotOrNotRating(3);
System.out.println("Rating set from non owner proxy");
System.out.println("Rating is " + nonOwnerProxy.getHotOrNotRating());
}

PersonBean getOwnerProxy(PersonBean person) {

return (PersonBean) Proxy.newProxyInstance(
person.getClass().getClassLoader(),
person.getClass().getInterfaces(),
new OwnerInvocationHandler(person));
}

// 获得这个对象的代理对象
PersonBean getNonOwnerProxy(PersonBean person) {

return (PersonBean) Proxy.newProxyInstance(
person.getClass().getClassLoader(),
person.getClass().getInterfaces(),
new NonOwnerInvocationHandler(person));
}

PersonBean getPersonFromDatabase(String name) {
return (PersonBean)datingDB.get(name);
}

void initializeDatabase() {
PersonBean joe = new PersonBeanImpl();
joe.setName("Joe Javabean");
joe.setInterests("cars, computers, music");
joe.setHotOrNotRating(7);
datingDB.put(joe.getName(), joe);

PersonBean kelly = new PersonBeanImpl();
kelly.setName("Kelly Klosure");
kelly.setInterests("ebay, movies, music");
kelly.setHotOrNotRating(6);
datingDB.put(kelly.getName(), kelly);
}
}

Big Web Services with Java

Posted on 2015-05-17 | In WebService | Comments:

Web services就是客户端通过HTTP协议向server发送请求,并获得所需要的信息或服务。Web services可以看做一种处于不同机器不同平台上的软件进行交互的一种标准方式。

目前主要有两种类型的Web services:
一种是所谓的big web services,因为其相对于第二种方式比较重量级。big web services使用XML封装请求信息以及回答,按照Simple Object Asscess Protocol,所以也称为SOAP消息,提供方法信息和作用域信息,另外使用的一项技术是WSDL(web service description language),web服务描述语言,是一套用于描述SOAP WEB服务的XML词汇。客户端可以通过参考WSDL文档,可以确切的知道可以调用哪些RPC式方法,需要哪些参数,以及返回值数据类型。SOAP的服务都发布有WSDL文档,否则很难使用。对于很多程序语言,WSDL也是用来自动生成客户端stub必须的文档。

另一种是RESTful webservice,具体两种实现的区别请参考Web Service的Programmable Web及其分类。这里主要介绍基于java实现big web service.

Big web service的基本技术

SOAP

SOAP是Web Service的基本通信协议,是一种规范,用来定义SOAP消息的XML格式(XMLFormat)。包含在一对 SOAP 元素(SOAPElements)中的、结构正确的 XML 段就是 SOAP 消息。
SOAP 规范还介绍了如何将程序数据表示为 XML,以及如何使用 SOAP 进行远程过程调用 (RPC)。
最后SOAP规范还定义了HTTP消息是怎样传输SOAP消息的。这并不代表SOAP只能用HTTP来作为传输协议,MSMQ、SMTP、TCP/IP都可以做SOAP的传输协议。

WSDL

Web Services Description Language的缩写,是一个用来描述Web服务和说明如何与Web服务通信的XML语言。WSDL是Web Service的描述语言,用于描述Web Service的服务,接口绑定等,为用户提供详细的接口说明书。
举个例子,你要使用供应商的WebService构建应用程序。你可以向供应商索取使用Web Service的范例,然后按照范例来构建应用程序。这样可能出现意料不到的错误,比如说,你在程序中使用的客户代码的数据类型是integer,而供应商使用的数据类型是string。WSDL详细定义客户端消息的格式,需要什么样的参数,这样可以避免不必要的错误。
要查看 WSDL 的值,可以假设您要调用由您的一位业务伙伴提供的 SOAP 方法。您可以要求对方提供一些 SOAP 消息示例,然后编写您的应用程序以生成并使用与示例类似的消息。WSDL 通过明确的表示法指定请求消息必须包含的内容以及响应消息的样式。
WSDL 文件用于说明消息格式的表示法以 XML 架构标准为基础,这意味着它与编程语言无关,而且以标准为基础,因此适用于说明可从不同平台、以不同编程语言访问的 XML Web Service 接口。除说明消息内容外,WSDL 还定义了服务的位置,以及使用什么通信协议与服务进行通信。WSDL 文件定义了编写使用 XML Web Service 的程序所需的全部内容。

Google搜索服务的WSDL文档片断:

1
2
3
4
<operation name="doGoogleSearch">
<input message="typens:doGoogleSearch">
<output message="typens:doGoogleSearchRespnse">
</operation>

一个SOAP RPC调用的HTTP请求

1
2
3
4
5
6
7
8
9
10
11
12
POST search/beta2 HTTP/1.1
Host:...
...

<?xml version="1.0"...>
<soap:Envelop xmln:soap="UTF-8">
<soap:body>
<gs:doGoogleSearch>
<q>plusaber</q>
</gs:doGoogleSearch>
</soap:Body>
</soap:Envelope>

UDDI

UniversalDescription Discovery and Integration即统一描述、发现和集成协议。UDDI实现了一组可公开访问的接口,通过这些接口,网络服务可以向服务信息库注册其服务信息、服务需求者可以找到分散在世界各地的网络服务。
UDDI 目录条目是介绍所提供的业务和服务的 XML 文件。可以把它比喻成电话本,电话本里记录的是电话信息,而UDDI记录的是Web Service信息。你可以不把Web Service注册到UDDI。但如果要让全球的人知道你的Web Service,最好还是注册到UDDI。
UDDI 目录还包含若干种方法,可用于搜索构建您的应用程序所需的服务。例如,您可以搜索特定地理位置的服务提供商或者搜索特定的业务类型。之后,UDDI 目录将提供信息、联系方式、链接和技术数据,以便您确定能满足需要的服务。
UDDI 允许您查找提供所需的Web 服务的公司。如果您已经知道要与谁进行业务合作,但尚不了解它还能提供哪些服务,这时该如何处理呢?WS-Inspection规范允许您浏览特定服务器上提供的 XML Web Service 的集合,从中查找所需的服务。

Big Web Service的Java实现

Web Service本质是一种标准,具体每种语言都提供了不同的实现。在Java领域,Big Web Service的标准是JAX-WS,实现框架有:CXF, axis2, Java 6开始自带的Web Service引擎(自带的JAX-WS implementation)。

JAX-WS的不同实现框架

这里先说明一下为什么存在这些不同的实现框架,特别是Java 6开始自带的Web Service引擎与其他更复杂的框架,参考Difference between Jax-ws, axis2, cxf:
The JAX-WS implementation built into the JDK really is just the basic soap stuff. If you need any of the more complex WS-* things like WS-Security, WS-RM, WS-Policy, etc…, you need to use one of the alternatives like CXF or Metro or Axis2. It can also depend on what you are trying to integrate with. For example, CXF has top notch Spring support as well as very good OSGi support.

CXF also has other things besides just JAX-WS. It has a compliant JAX-RS implementation as well and supports exposing services as both REST and SOAP very well. Has a W3C compliant SOAP/JMS implementation if that type of things is required. Basically, lots of stuff not available from the in-jdk JAX-WS impl.

Also see:
Difference between Apache CXF and Axis

也就是Java自带的Web Service引擎提供了SOAP通信的服务,但是不支持更高级的需求,比如Web Service安全性,与Spring结合等。

创建和使用Web Service基本步骤

  1. 编写服务的接口
  2. 编写服务接口的实现类
  3. 发布
  4. 测试或使用

使用JAX-WS(Java自带)实现第一个Web Service

项目结构如下:

WebService_1

  • 编写服务接口HelloWorld
1
2
3
4
5
6
7
8
9
10
11
package jws;

import javax.jws.WebParam;
import javax.jws.WebService;

@WebService
public interface HelloWorld {

public String sayHello(@WebParam(name="who") String who);

}

注意@WebService是必须要有的,用于指定这是一个Web Service服务类。

另外要暴露给客户端的方法需要注明为@WebMethod。@WebMethod是用来标注在WebService的方法即操作上的,通过它我们可以指定某一个操作的操作名、使用SOAP绑定时的Action名称,以及该方法是否允许发布为WebService。@WebMethod有三个属性:operationName、action和exclude。如果接口中方法不希望作为WebService访问的时候就可以通过@WebMethod来指定将其排除在外(@WebMethod(exclude=true))。

@Oneway是标注在Service接口的操作方法上的。使用@Oneway标注的方法表示它不需要等待服务端的返回,也不需要预留资源来处理服务端的返回。

因为接口在被编译为class文件的时候不能保存参数名,有时候这会影响可读性。如果需要参数名显示的可读性强一些的话,我们可以使用@WebParam来指定。

  • 编写服务接口HelloWorld实现类
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
package jws;

import javax.jws.WebMethod;
import javax.jws.WebService;

// endpointInterface用于指定实现的接口
// 同时也设定了服务的targetName和name,后面client定位这个服务时需要用到

@WebService(endpointInterface = "jws.HelloWorld")
public class HelloWorldImpl implements HelloWorld {

@Override
@WebMethod(exclude=true)
public String sayHello(String who) {
return"Hello, " + who;
}

}

javax.xml.ws.WebServiceException: Undefined port type Java Struts SOAP WSDL

  • 发布服务
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
package jws;

import javax.xml.ws.Endpoint;


public class ServerTest {

public static void deployService() {
System.out.println("Server start ……");
HelloWorld service = new HelloWorldImpl();
String address = "http://localhost:9999/ws/hello";
Endpoint.publish(address, service);
}

public static void main(String[] args) throws InterruptedException {
deployService();
System.out.println("server ready ……");
Thread.sleep(1000 * 60);
System.out.println("server exiting");
System.exit(0);
}
}
  • (可跳过)验证服务

打开浏览器,输入http://localhost:9999/ws/hello?wsdl,可以验证服务的WSDL文件(mac的safari不显示,可以用Chrome验证)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
<!--
Published by JAX-WS RI (http://jax-ws.java.net). RI's version is JAX-WS RI 2.2.9-b130926.1035 svn-revision#5f6196f2b90e9460065a4c2f4e30e065b245e51e.
-->
<!--
Generated by JAX-WS RI (http://jax-ws.java.net). RI's version is JAX-WS RI 2.2.9-b130926.1035 svn-revision#5f6196f2b90e9460065a4c2f4e30e065b245e51e.
-->
<definitions xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd" xmlns:wsp="http://www.w3.org/ns/ws-policy" xmlns:wsp1_2="http://schemas.xmlsoap.org/ws/2004/09/policy" xmlns:wsam="http://www.w3.org/2007/05/addressing/metadata" xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/" xmlns:tns="http://jws/" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns="http://schemas.xmlsoap.org/wsdl/" targetNamespace="http://jws/" name="HelloWorldImplService">
<types>
<xsd:schema>
<xsd:import namespace="http://jws/" schemaLocation="http://localhost:9999/ws/hello?xsd=1"/>
</xsd:schema>
</types>
<message name="sayHello">
<part name="parameters" element="tns:sayHello"/>
</message>
<message name="sayHelloResponse">
<part name="parameters" element="tns:sayHelloResponse"/>
</message>
<portType name="HelloWorld">
<operation name="sayHello">
<input wsam:Action="http://jws/HelloWorld/sayHelloRequest" message="tns:sayHello"/>
<output wsam:Action="http://jws/HelloWorld/sayHelloResponse" message="tns:sayHelloResponse"/>
</operation>
</portType>
<binding name="HelloWorldImplPortBinding" type="tns:HelloWorld">
<soap:binding transport="http://schemas.xmlsoap.org/soap/http" style="document"/>
<operation name="sayHello">
<soap:operation soapAction=""/>
<input>
<soap:body use="literal"/>
</input>
<output>
<soap:body use="literal"/>
</output>
</operation>
</binding>
<service name="HelloWorldImplService">
<port name="HelloWorldImplPort" binding="tns:HelloWorldImplPortBinding">
<soap:address location="http://localhost:9999/ws/hello"/>
</port>
</service>
</definitions>
  • 编写客户端,调用服务

需要利用工具类生成并导入服务的接口类HelloWorld。

method 1: 使用Eclipse工具生成
method 2: 通过cxf的命令生成客户端代码。
进入apache-cxf-2.3.1\bin所在目录,输入命令:

1
wsdl2java -p com.jaxb.client -d . http://localhost:9999/ws/hello?wsdl

命令格式为:wsdl2java –p 包名 –d 生成代码存放目录 wsdl的url

wsdl2java用法:

param usage
-p 指定其wsdl的命名空间,也就是要生成代码的包名;
-d 指定要产生代码所在目录;
-client 生成客户端测试web service的代码;
-server 生成服务器启动web service的代码;
-impl 生成web service的实现代码;
-ant 生成build.xml文件;
-all 生成所有开始端点代码:types,serviceproxy, service interface, server mainline, client mainline, implementation object,and an Ant build.xml file。

另外还需要知道服务wsdl文件中的targetNamespace和name用于定位服务。

targetNamespace="http://jws/" name="HelloWorldImplService

将生成代码拷贝到client代码目录下或导入。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
package client;

import java.net.MalformedURLException;
import java.net.URL;

import javax.xml.namespace.QName;
import javax.xml.ws.Service;

import jws.HelloWorld;

public class Client {
public static void main(String[] args) {
URL url = null;
try {
url = new URL("http://localhost:9999/ws/hello?wsdl");
} catch (MalformedURLException e) {
java.util.logging.Logger.getLogger(HelloWorld.class.getName())
.log(java.util.logging.Level.INFO,
"Can not initialize the default wsdl from {0}", "http://localhost:9000/helloWorld?wsdl");
}

QName qname = new QName("http://jws/", "HelloWorldImplService");

Service service = Service.create(url, qname);

HelloWorld hw = service.getPort(HelloWorld.class);
System.out.println(hw.sayHello("World"));
}
}
1
Hello, World

上面是基本的基于Java 6开始自带的Web Service引擎的helloworld实现,没有使用任何其他包,例如CFX。

HelloWrold基于CXF的实现

基本流程和上面一致。

使用CXF的JaxWsServerFactoryBean类进行发布。

1
2
3
4
5
6
7
HelloServiceImpl impl = new HelloServiceImpl();
JaxWsServerFactoryBean factoryBean = newJaxWsServerFactoryBean();
factoryBean.setAddress("http://localhost:8080/WSCXF/helloService");
factoryBean.setServiceClass(IHelloService.class);//接口类
factoryBean.setServiceBean(impl);
factoryBean.create();
System.out.println("WS发布成功!");

这两种方法都是将服务发布到Jetty下。另外还可以利用cxf和spring整合在tomcat下进行发布。

客户端使用cxf的调用服务:

1
2
3
4
5
JaxWsProxyFactoryBean soapFactoryBean = newJaxWsProxyFactoryBean();
soapFactoryBean.setAddress("http://localhost:8080/WSCXF/helloService");
soapFactoryBean.setServiceClass(IHelloService.class);
Object o = soapFactoryBean.create();
IHelloService helloService = (IHelloService)o;

可以看出Big web service的生成发布以及调用过程都是基本一致的,只是在各个步骤可以使用不同的框架和实现,不同框架提供了不同程度的安全性,整合等支持。

CXF和Spring整合

参考JAX-WS Hello World Example – Document Style

REST_Programmable Web及其分类

Posted on 2015-05-16 | In WebService | Comments:

Programmable Web及其分类

在编写计算机程序时,我们经常需要使用第三方库中的算法。但是有时我们需要获得由数据驱动的而且数据无法容易获得的服务,这时我们需要依赖于programmable web。这里programmable web是跟human web相对的。human web指的是传统的面向人类使用的web,如人们浏览的网站返回HTML文档显式供人类查看,而programmable web指的是面向计算机程序使用的web,比如web服务等,一般返回更加规则的结果,如XML文档,JSON文档等作为程序的输入。本质上programmable web在工作方式上跟human web是一致的,都是发送请求后,获得所需要的输出或操作。

programmable web是基于HTTP和XML技术的,虽然HTML,JSON等也在使用,但是使用较多的还是XML。

programmable web分类主要是指可以根据调用web服务的方式和形式不同的技术进行分类,如基于RPC(SOAP+WSDL), REST, REST-RPC混合等方式。到这里还是非常的抽象,要对programmable web进行分类,首先来回顾一下programmable web的一些技术。

HTTP

HTTP是一种基于文档的协议。客户端把文档放在信封里,然后发送给服务器。接收到请求后,服务器解析请求并作为回应,把相应文档放在信封里,然后发送给客户端。HTTP对信封格式有严格标准,但是并不关心信封里面的内容,一般是有客户端或服务端解析。

一个HTTP GET请求:

1
2
3
4
GET /index.html HTTP/1.1
Host: www.plusaber.net
User-Ageent: Mozilla/5.0....
...

HTTP请求主要有下面几个组成部分:

HTTP方法:表示客户端希望服务器如何处理该信封。
路径:这里是URI里主机名(hostname)后面的部分。
请求报头:一组key-value pairs,作为元数据。
实体主体,也就是文档,这里GET请求是没有主体的,POST请求会将请求参数放在这里,而GET请求会请求参数以键值对形式直接放在URI中。

HTTP相应也是一个放在信封里的文档,如下:

1
2
3
4
5
6
7
8
9
10
HTTP/1.1 200 OK
Date: Fri....
Server: Aparche...
......

<!DOCTYPE html...>
<head>
...
<title>Hello, world</title>
......

HTTP响应可分为三个部分:

HTTP响应代码
响应报头
实体主体

方法信息

HTTP是Programmable Web上所有服务所共有的。而Web服务之间的差别的形成,是由于在两个问题上的做法有所不同。只要了解一个Web服务器是如何处理这两个问题的,就可以清楚地直到该服务是否跟Web的理念保持一致。

第一个问题是:客户端如何把自己的意图传达给服务器,也就是客户希望进行什么操作,如获取数据、删除数据还是改写数据。这种关于对数据采取什么操作的信息,称为方法信息(method information)。

RESTful Web Services的方式是把它放在HTTP方法中(HTTP method),包括主要的GET, HEAD, PUT, DELETE和POST。通过这些方法足以分辨操作类型,标准化的名称是一个很好的优点。

RPC方式是通过提供另外的方法描述提供method information,如在URI中的参数(RPC-REST混合模式),在SOAP信封中调用WSDS中定义的方法(SOAP-RPC)。而不关注请求时的HTTP method,RPC-REST混合模式中可能总是使用GET,尽管实际的操作可能是DELETE,SOAP-RPC可能总是使用POST(需要包含SOAP信息进行服务调用),尽管实际的操作可能是PUT。

Google搜索服务的WSDL文档片断:

1
2
3
4
<operation name="doGoogleSearch">
<input message="typens:doGoogleSearch">
<output message="typens:doGoogleSearchRespnse">
</operation>

一个SOAP RPC调用的HTTP请求

1
2
3
4
5
6
7
8
9
10
11
12
POST search/beta2 HTTP/1.1
Host:...
...

<?xml version="1.0"...>
<soap:Envelop xmln:soap="UTF-8">
<soap:body>
<gs:doGoogleSearch>
<q>plusaber</q>
</gs:doGoogleSearch>
</soap:Body>
</soap:Envelope>

第二个问题是:客户端如何告诉服务器对哪些数据进行操作。也就是作用域信息(scoping information)。

作用域信息

很多Web服务器都把作用域信息放在URI路径里。
另一种选择是把作用域信息放在实体主体里,SOAP Web服务通常采用这种方式。

采用了什么样的服务设计,决定了哪些信息属于方法信息,哪些信息属于作用域信息,即使功能相同。

1
2
请求(GET)  http://flickr.com/photos/tags/penguin
请求(GET) http://api.flickr.com/services/rest/?method=flickr.photos.search&tags=penguin

两者实现的功能相同,请求也都是GET,但是后者在uri参数中制定了调用的方法名flickr.photos.search,令HTTP的GET方法失去了原本用意,也就是实际上的操作并不不受GET或POST影响。前一个例子中Google SOAP API中也是如此,在SOAP信封中制定了调用的方法甚至参数。

相互竞争的服务架构

不同Web服务会有不同做法,目前主要有三种常见的Web服务架构:REST式架构、RPC式架构和REST-RPC混合架构。

RPC式架构通过一个复杂的、编程语言式的接口,来暴露其内部算法。而ROA则通过一个简单的、文档处理接口,来暴露其内部数据。

REST式、面向资源的机构(Resource-Oriented Architectures)

REST式架构意味着,方法信息都在HTTP方法里;面向资源的架构(ROA)意味着,作用域信息都在URI里,二者结合起来可以非常强大。一个面向资源的REST式Web服务,通过HTTP请求第一行就能基本了解客户端需要做什么了。如果HTTP方法跟方法信息对不上,那么服务就不算事RESTful的;如果作用域信息不放在URI里,那么服务就不是面向资源的。

RPC式架构(RPC-Style Architectures)

RPC式Web服务通常从客户端收到一个包括方法信息和作用域信息的信封,然后发回回复信封。一种常用的信封格式是SOAP,进一步封装杂HTTP信封中。SOAP信封内容随着调用方法和参数不同有所变化,但是HTTP信封格式总是不变的,同样的uri,同样的HTTP method(一般总是POST)。SOAP-RPC服务为采用HTTP的很多特性,只暴露一个URI,并且只支持一种HTTP方法(POST方法)。而REST服务为不同的作用域信息暴露不同的URI。

除了SOAP RPC,更宽泛的是XML-RPC,SOAP其实就是基于XML,不过多了一系列规范和相应的技术栈,包括WSDL,UDDI.

REST-RPC混合架构

1
http://api.flickr.com/services/rest/?method=flickr.photos.search&tags=penguin

这里的服务调用将作用域信息放在URI中,有些类似于REST,但是不同的是其将方法信息也放在URI中了,所以本质上是一个RPC式服务,只是将URI当做信封格式来用。

许多只读的Web服务,尽管起初是按照RPC风格设计的,但都可以被认为是完全REST式和面向资源的。但是如果服务允许DELETE等操作的话,就会出现客户端所使用的HTTP方法与正在想执行的操作不一致的情况,就失去了REST的特征,所以称为REST-RPC混合服务。

Programmable Web涉及的技术

HTTP

所有Web服务都用HTTP,只是使用方式有所不同。

URI

同样的,所有Web服务都用HTTP,但使用方式有所不同。一个REST服务为客户端可能操作的每一个数据暴露一个URI;一个REST-RPC混合服务为客户端可行进行的每个操作暴露一个URI;一个RPC服务为每个远程处理暴露一个相同的URI,即服务的端点(endpoint)。

XML-RPC

用XML表达函数调用及其返回值,专门用于RPC式Web服务,现在已经逐渐被更完善的SOAP技术栈所替代。

SOAP

基于XML的信封格式,同样是为RPC提供方法信息和作用域信息。

WSDL

WSDL(web service description language),web服务描述语言,是一套用于描述SOAP WEB服务的XML词汇。客户端可以通过参考WSDL文档,可以确切的知道可以调用哪些RPC式方法,需要哪些参数,以及返回值数据类型。SOAP的服务都发布有WSDL文档,否则很难使用。

WADL

WADL(web applicaiotn description language), web应用描述语言,是一套用于描述REST式服务的XML词汇。提供的功能与WSDL类似,但是由于REST式服务接口比较简单,其必要性没有像WSDL至于SOAP-RPC那么大。

State pattern

Posted on 2015-05-01 | In Design Pattern | Comments:

State pattern

问题定义

假设我们需要制造一台糖果机,其操作如下:

Design_pattern_state_1

我们可以分清楚各个状态以及状态的转换,当做一个简单的状态机来实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
public class GumballMachine {

final static int SOLD_OUT = 0;
final static int NO_QUARTER = 1;
final static int HAS_QUARTER = 2;
final static int SOLD = 3;

int state = SOLD_OUT;
int count = 0;

public GumballMachine(int count) {
this.count = count;
if (count > 0) {
state = NO_QUARTER;
}
}

public void insertQuarter() {
if (state == HAS_QUARTER) {
System.out.println("You can't insert another quarter");
} else if (state == NO_QUARTER) {
state = HAS_QUARTER;
System.out.println("You inserted a quarter");
} else if (state == SOLD_OUT) {
System.out.println("You can't insert a quarter, the machine is sold out");
} else if (state == SOLD) {
System.out.println("Please wait, we're already giving you a gumball");
}
}

public void ejectQuarter() {
if (state == HAS_QUARTER) {
System.out.println("Quarter returned");
state = NO_QUARTER;
} else if (state == NO_QUARTER) {
System.out.println("You haven't inserted a quarter");
} else if (state == SOLD) {
System.out.println("Sorry, you already turned the crank");
} else if (state == SOLD_OUT) {
System.out.println("You can't eject, you haven't inserted a quarter yet");
}
}




public void turnCrank() {
if (state == SOLD) {
System.out.println("Turning twice doesn't get you another gumball!");
} else if (state == NO_QUARTER) {
System.out.println("You turned but there's no quarter");
} else if (state == SOLD_OUT) {
System.out.println("You turned, but there are no gumballs");
} else if (state == HAS_QUARTER) {
System.out.println("You turned...");
state = SOLD;
dispense();
}
}

public void dispense() {
if (state == SOLD) {
System.out.println("A gumball comes rolling out the slot");
count = count - 1;
if (count == 0) {
System.out.println("Oops, out of gumballs!");
state = SOLD_OUT;
} else {
state = NO_QUARTER;
}
} else if (state == NO_QUARTER) {
System.out.println("You need to pay first");
} else if (state == SOLD_OUT) {
System.out.println("No gumball dispensed");
} else if (state == HAS_QUARTER) {
System.out.println("No gumball dispensed");
}
}

public void refill(int numGumBalls) {
this.count = numGumBalls;
state = NO_QUARTER;
}

public String toString() {
StringBuffer result = new StringBuffer();
result.append("\nMighty Gumball, Inc.");
result.append("\nJava-enabled Standing Gumball Model #2004\n");
result.append("Inventory: " + count + " gumball");
if (count != 1) {
result.append("s");
}
result.append("\nMachine is ");
if (state == SOLD_OUT) {
result.append("sold out");
} else if (state == NO_QUARTER) {
result.append("waiting for quarter");
} else if (state == HAS_QUARTER) {
result.append("waiting for turn of crank");
} else if (state == SOLD) {
result.append("delivering a gumball");
}
result.append("\n");
return result.toString();
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
public class GumballMachineTestDrive {

public static void main(String[] args) {
GumballMachine gumballMachine = new GumballMachine(5);

System.out.println(gumballMachine);

gumballMachine.insertQuarter();
gumballMachine.turnCrank();

System.out.println(gumballMachine);

gumballMachine.insertQuarter();
gumballMachine.ejectQuarter();
gumballMachine.turnCrank();

System.out.println(gumballMachine);

gumballMachine.insertQuarter();
gumballMachine.turnCrank();
gumballMachine.insertQuarter();
gumballMachine.turnCrank();
gumballMachine.ejectQuarter();

System.out.println(gumballMachine);

gumballMachine.insertQuarter();
gumballMachine.insertQuarter();
gumballMachine.turnCrank();
gumballMachine.insertQuarter();
gumballMachine.turnCrank();
gumballMachine.insertQuarter();
gumballMachine.turnCrank();

System.out.println(gumballMachine);
}
}

当需要增加功能时,这份代码将十分难以修改和扩展,比如现在我们需要当曲柄被转动时,有10%的记录掉下两颗糖果。

更好的设计是:

  1. 定义一个state接口。在这个接口内,糖果机的每个动作都有一个对应的方法。
  2. 然后为机器的每个状态实现状态类。这些类将入则在对应状态下进行机器的行为。
  3. 将动作委托到状态类。

Design_pattern_state_2

1
2
3
4
5
6
7
public interface State {

public void insertQuarter();
public void ejectQuarter();
public void turnCrank();
public void dispense();
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
public class NoQuarterState implements State {
GumballMachine gumballMachine;

public NoQuarterState(GumballMachine gumballMachine) {
this.gumballMachine = gumballMachine;
}

public void insertQuarter() {
System.out.println("You inserted a quarter");
gumballMachine.setState(gumballMachine.getHasQuarterState());
}

public void ejectQuarter() {
System.out.println("You haven't inserted a quarter");
}

public void turnCrank() {
System.out.println("You turned, but there's no quarter");
}

public void dispense() {
System.out.println("You need to pay first");
}

public String toString() {
return "waiting for quarter";
}
}

public class HasQuarterState implements State {
GumballMachine gumballMachine;

public HasQuarterState(GumballMachine gumballMachine) {
this.gumballMachine = gumballMachine;
}

public void insertQuarter() {
System.out.println("You can't insert another quarter");
}

public void ejectQuarter() {
System.out.println("Quarter returned");
gumballMachine.setState(gumballMachine.getNoQuarterState());
}

public void turnCrank() {
System.out.println("You turned...");
gumballMachine.setState(gumballMachine.getSoldState());
}

public void dispense() {
System.out.println("No gumball dispensed");
}

public String toString() {
return "waiting for turn of crank";
}
}

public class SoldState implements State {

GumballMachine gumballMachine;

public SoldState(GumballMachine gumballMachine) {
this.gumballMachine = gumballMachine;
}

public void insertQuarter() {
System.out.println("Please wait, we're already giving you a gumball");
}

public void ejectQuarter() {
System.out.println("Sorry, you already turned the crank");
}

public void turnCrank() {
System.out.println("Turning twice doesn't get you another gumball!");
}

public void dispense() {
gumballMachine.releaseBall();
if (gumballMachine.getCount() > 0) {
gumballMachine.setState(gumballMachine.getNoQuarterState());
} else {
System.out.println("Oops, out of gumballs!");
gumballMachine.setState(gumballMachine.getSoldOutState());
}
}

public String toString() {
return "dispensing a gumball";
}
}

public class SoldOutState implements State {
GumballMachine gumballMachine;

public SoldOutState(GumballMachine gumballMachine) {
this.gumballMachine = gumballMachine;
}

public void insertQuarter() {
System.out.println("You can't insert a quarter, the machine is sold out");
}

public void ejectQuarter() {
System.out.println("You can't eject, you haven't inserted a quarter yet");
}

public void turnCrank() {
System.out.println("You turned, but there are no gumballs");
}

public void dispense() {
System.out.println("No gumball dispensed");
}

public String toString() {
return "sold out";
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
public class GumballMachine {

State soldOutState;
State noQuarterState;
State hasQuarterState;
State soldState;

State state = soldOutState;
int count = 0;

public GumballMachine(int numberGumballs) {
soldOutState = new SoldOutState(this);
noQuarterState = new NoQuarterState(this);
hasQuarterState = new HasQuarterState(this);
soldState = new SoldState(this);

this.count = numberGumballs;
if (numberGumballs > 0) {
state = noQuarterState;
}
}

public void insertQuarter() {
state.insertQuarter();
}

public void ejectQuarter() {
state.ejectQuarter();
}

public void turnCrank() {
state.turnCrank();
state.dispense();
}

void setState(State state) {
this.state = state;
}

void releaseBall() {
System.out.println("A gumball comes rolling out the slot...");
if (count != 0) {
count = count - 1;
}
}

int getCount() {
return count;
}

void refill(int count) {
this.count = count;
state = noQuarterState;
}

public State getState() {
return state;
}

public State getSoldOutState() {
return soldOutState;
}

public State getNoQuarterState() {
return noQuarterState;
}

public State getHasQuarterState() {
return hasQuarterState;
}

public State getSoldState() {
return soldState;
}

public String toString() {
StringBuffer result = new StringBuffer();
result.append("\nMighty Gumball, Inc.");
result.append("\nJava-enabled Standing Gumball Model #2004");
result.append("\nInventory: " + count + " gumball");
if (count != 1) {
result.append("s");
}
result.append("\n");
result.append("Machine is " + state + "\n");
return result.toString();
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class GumballMachineTestDrive {

public static void main(String[] args) {
GumballMachine gumballMachine = new GumballMachine(5);

System.out.println(gumballMachine);

gumballMachine.insertQuarter();
gumballMachine.turnCrank();

System.out.println(gumballMachine);

gumballMachine.insertQuarter();
gumballMachine.turnCrank();
gumballMachine.insertQuarter();
gumballMachine.turnCrank();

System.out.println(gumballMachine);
}
}

定义状态模式

状态模式允许对象在内部状态改变时改变它的行为,对象看起来好像修改了它的类。

Design_pattern_state_3

状态模式跟策略模式形式上很相似,但是,意图不同!

状态模式将一群行为封装在状态对象中,运行过程中当前状态在状态对象集合中游走改变,但是客户端对于状态对象完全不了解。状态模式一般是用来避免在context中放置许多条件判断的替代方案,以便于扩展。

而策略模式中,通常是客户主动指定所需要的策略对象,以满足所需要的行为。策略模式是除继承之外一种弹性替代方案,可以避免被父类的行为困住,我们可以通过组合不同的对象来改变行为。

解决扩展问题

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
public class GumballMachine {

State soldOutState;
State noQuarterState;
State hasQuarterState;
State soldState;
State winnerState;

State state = soldOutState;
int count = 0;

public GumballMachine(int numberGumballs) {
soldOutState = new SoldOutState(this);
noQuarterState = new NoQuarterState(this);
hasQuarterState = new HasQuarterState(this);
soldState = new SoldState(this);
winnerState = new WinnerState(this);

this.count = numberGumballs;
if (numberGumballs > 0) {
state = noQuarterState;
}
}

public void insertQuarter() {
state.insertQuarter();
}

public void ejectQuarter() {
state.ejectQuarter();
}

public void turnCrank() {
state.turnCrank();
state.dispense();
}

void setState(State state) {
this.state = state;
}

void releaseBall() {
System.out.println("A gumball comes rolling out the slot...");
if (count != 0) {
count = count - 1;
}
}

int getCount() {
return count;
}

void refill(int count) {
this.count = count;
state = noQuarterState;
}

public State getState() {
return state;
}

public State getSoldOutState() {
return soldOutState;
}

public State getNoQuarterState() {
return noQuarterState;
}

public State getHasQuarterState() {
return hasQuarterState;
}

public State getSoldState() {
return soldState;
}

public State getWinnerState() {
return winnerState;
}

public String toString() {
StringBuffer result = new StringBuffer();
result.append("\nMighty Gumball, Inc.");
result.append("\nJava-enabled Standing Gumball Model #2004");
result.append("\nInventory: " + count + " gumball");
if (count != 1) {
result.append("s");
}
result.append("\n");
result.append("Machine is " + state + "\n");
return result.toString();
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
public class WinnerState implements State {
GumballMachine gumballMachine;

public WinnerState(GumballMachine gumballMachine) {
this.gumballMachine = gumballMachine;
}

public void insertQuarter() {
System.out.println("Please wait, we're already giving you a Gumball");
}

public void ejectQuarter() {
System.out.println("Please wait, we're already giving you a Gumball");
}

public void turnCrank() {
System.out.println("Turning again doesn't get you another gumball!");
}

public void dispense() {
System.out.println("YOU'RE A WINNER! You get two gumballs for your quarter");
gumballMachine.releaseBall();
if (gumballMachine.getCount() == 0) {
gumballMachine.setState(gumballMachine.getSoldOutState());
} else {
gumballMachine.releaseBall();
if (gumballMachine.getCount() > 0) {
gumballMachine.setState(gumballMachine.getNoQuarterState());
} else {
System.out.println("Oops, out of gumballs!");
gumballMachine.setState(gumballMachine.getSoldOutState());
}
}
}

public String toString() {
return "despensing two gumballs for your quarter, because YOU'RE A WINNER!";
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
package headfirst.state.gumballstatewinner;

public class GumballMachineTestDrive {

public static void main(String[] args) {
GumballMachine gumballMachine =
new GumballMachine(10);

System.out.println(gumballMachine);

gumballMachine.insertQuarter();
gumballMachine.turnCrank();
gumballMachine.insertQuarter();
gumballMachine.turnCrank();

System.out.println(gumballMachine);

gumballMachine.insertQuarter();
gumballMachine.turnCrank();
gumballMachine.insertQuarter();
gumballMachine.turnCrank();

System.out.println(gumballMachine);

gumballMachine.insertQuarter();
gumballMachine.turnCrank();
gumballMachine.insertQuarter();
gumballMachine.turnCrank();

System.out.println(gumballMachine);

gumballMachine.insertQuarter();
gumballMachine.turnCrank();
gumballMachine.insertQuarter();
gumballMachine.turnCrank();

System.out.println(gumballMachine);

gumballMachine.insertQuarter();
gumballMachine.turnCrank();
gumballMachine.insertQuarter();
gumballMachine.turnCrank();

System.out.println(gumballMachine);
}
}
1…678…17
Plusaber

Plusaber

Plusaber's Blog
82 posts
12 categories
22 tags
Links
  • LinkedIn
  • Indeed
  • Baito
  • Kaggle
© 2014 – 2019 Plusaber
Powered by Hexo v3.8.0
|
Theme – NexT.Mist v7.1.1