代理模式
代理模式(Proxy) 的定义:由于某些原因需要给某对象提供一个代理以控制对该对象的访问。这时,访问对象不适合或者不能直接引用目标对象,代理对象作为访问对象和目标对象之间的中介。
简单地说就是我们实际访问目标是通过代理对象来间接访问的,代理就是对目标方法进行增强。
静态代理
定义
静态代理是我们自己创建的类,在编译时就已经将接口、本代理类和代理类确定下来。
软件设计模式中所指的代理一般就是说的静态代理。
代码讲解
老规矩模拟个情景: 微商代购。
- 首先抽象一个动作接口
Action
,我们的动作是买buy()
:
interface Action {
void buy();
}
- 在没有代购前,我们自己去买东西,需要自己去挑选,然后买,再自己带回来。用程序实现:
public class Main implements Action {
@Override
public void buy() {
System.out.println("to buy...");
}
public static void main(String[] args) {
System.out.println("selecting...");
new Main().buy();
System.out.println("bring back...");
}
}
运行结果:
这是最简单且常见的编程方式,我们下面通过微商来代替我们去买:
- 创业建一个微商,他也实现
Action
接口。微商有在我们付账前的准备工作before()
,和在我们下单后的动作after()
,其内容是代替我们亲自去做的动作,我们这里直接使用Action
对象方法:
public class Proxy implements Action{
Action a;
public Proxy(Action a) { this.a = a;}
public void before() {
System.out.println("proxy to select...");
}
public void after() {
System.out.println("proxy to buy...");
System.out.println("proxy to send...");
}
@Override
public void buy() {
before();
a.buy();
after();
}
}
- 我们只需要付账
pay()
就好了,注意在调用代理Proxy
对象需要把我们被代理的对象传进去:
public class Main implements Action {
@Override
public void buy() {
pay();
}
public void pay() {
System.out.println("to pay");
}
public static void main(String[] args) {
Proxy p = new Proxy(new Main());
p.buy();
System.out.println("get it");
}
}
运行结果:
通过上述代码,我们就已经完成一个简单的静态代理模式了。当然我们可以把情景想的更现实,比如微商上面还有地区代理,只需要再写一个代理对象让他嵌套代理就可以。
特点
我们在创建代理对象时,通过构造器塞入一个目标对象,然后在代理对象的方法内部调用目标对象同名方法,并在调用前后做增强逻辑。也就是说,代理对象 = 增强代码 + 目标对象。有了代理对象后,就不用原对象了。
缺点:
开发者需要手动为目标类编写对应的代理类,而且要对类中的每个方法都编写增强逻辑的代码,如果当前系统中已经存在成百上千个类,工作量太大了,且重复代码过多。该怎么解决呢?这就用到了动态代理。
动态代理
定义
动态代理就是在静态代理的基础上创建代理对象更加的灵活,动态代理类的字节码在程序运行时,由Java反射机制动态产生。它会根据需要,通过反射机制在程序运行期,动态的为目标对象创建代理对象,无需程序员手动编写它的源代码。
代码讲解
动态代理中要用到JDK的反射组件java.lang.reflect
,还是使用上面的例子进行代码讲解。
- 首先给微商代理类对象改名为
ProxyShop
与Java反射中的Proxy
区分开: - 改写主程序,使用
reflect
组件中的Proxy.newProxyInstance()
方法,其中第一个参数是被代理类,第二个参数是使用到的接口类,第三个参数有就是实现调用处理程序的内部类:
public class Main implements Action {
@Override
public void buy() {
pay();
}
public void pay() {
System.out.println("to pay");
}
public static void main(String[] args) {
Main m = new Main();
Action a = (Action) Proxy.newProxyInstance(Main.class.getClassLoader(),
new Class[]{Action.class},
new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println(method.getName() + " start: ");
Object o = method.invoke(m, args);
System.out.println(method.getName() + " end!");
return o;
}
});
a.buy();
System.out.println("get it");
}
}
运行结果:
实际上jajdk动态代理在生成的动态代理类$Proxy0.class
中,构造方法调用了父类Proxy.class的构造方法,给成员变量invocationHandler
赋值,$Proxy0.class
的static
模块中创建了被代理类的方法,调用相应方法时方法体中调用了父类中的成员变量InvocationHandler
的invoke()
方法。
动态代理机制及特点
-
通过实现
InvocationHandler
接口创建自己的调用处理器; -
通过为
Proxy
类指定ClassLoader
对象和一组interface
来创建动态代理类; -
通过反射机制获得动态代理类的构造函数,其唯一参数类型是调用处理器接口类型;
-
通过构造函数创建动态代理类实例,构造时调用处理器对象作为参数被传入。
其他实现
注意:JDK的动态代理要依靠接口实现,如果有些类并没有接口实现,则不能使用JDK代理。、
JDK的动态代理存在一些局限性,java还可以实现别的动态代理方法:
- CGLIB
Enhancer
对象调用setSuperclass()
方法设置父类,调用setCallback()
方法设置回调,回调中的对象实现MethodInterceptor
接口,最后生成ceate()
动态代理对象。
- Instrument
通过类加载过程中修改二进制字节码的方式进行动态代理实现。
- 结合springAOP