@MrXiao
2018-04-25T14:02:37.000000Z
字数 3803
阅读 751
设计模式
Java本身给我们提供了静态代理和动态代理两种代理方式,spring提供了基于类的代理方式:cglib.
在代理模式(Proxy Pattern)中,一个类代表另一个类的功能。这种类型的设计模式属于结构型模式。
在代理模式中,我们创建具有现有对象的对象,以便向外界提供功能接口。
意图:为其他对象提供一种代理,从而控制对这个对象的访问。
主要解决:在直接访问对象时带来的问题。
关键代码:实现与被代理类的组合。
优点:
缺点:
注意事项
静态代理:代理类在程序运行前就已存在,即人为的编写代理类代码。代理类与委托类实现同一接口或派生自相同的父类。
以下是静态代理的简单实现:
public interface House {
void sayHi();
}
public class HouseImpl implements House {
@Override
public void sayHi() {
System.out.println("welcome to my house!");
}
}
public class ProxyHouse implements House {
private House house;
public ProxyHouse(House house) {
this.house = house;
}
@Override
public void sayHi() {
System.out.println("I'm proxy");
house.sayHi();
}
}
public static void main(String[] args) {
House h = new HouseImpl();
h.sayHi();
h = new ProxyHouse(h);
h.sayHi();
}
可以实现客户与委托类之间的解耦,在不修改委托类的前提下能够做一些额外的处理。
静态代理的局限在于运行前必须编写好代理类,且针对每个需要代理方法都必须单独实现。
动态代理:在程序运行时创建的代理方式,即代理类是动态生成的,而不是提前编写的。
相比于静态代理,动态代理可以很方便的对代理类进行统一处理,而不用修改每个代理类的函数,常用于权限控制、日志记录等场景。
约束:委托类必须实现接口。
以下是动态代理的简单实现:在对某个类的一个方法的调用前和调用后都要做一下日志操作
//一个普通的接口
public interface House {
void sayHi();
}
//委托类,需要增加日志的类
public class HouseImpl implements House {
@Override
public void sayHi() {
System.out.println("welcome to my house!");
}
}
//动态代理的中介类,关键
public class DynamicProxy implements InvocationHandler {
private Object obj;//目标对象的引用,这里设计成Object类型,更具通用性
public DynamicProxy(Object obj) {
this.obj = obj;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("方法执行前");
Object res = method.invoke(obj, args);//调用目标对象的方法
System.out.println("方法执行后");
return res;
}
}
public static void main(String[] args) {
House h = new HouseImpl();
//创建中介类,代理了委托类
DynamicProxy dynamicProxy = new DynamicProxy(h);
//获取代理类实例,代理了中介类
House instance = (House)Proxy.newProxyInstance(House.class.getClassLoader(), new Class[]{House.class}, dynamicProxy);
//代理对象调用方法
instance.sayHi();
}
Java的动态代理,实际上是两次静态代理的组合。动态代理的重点是中介类的实现,需要实现java.lang.reflect.InvocationHandler接口。
动态代理与静态代理的区别:
在Spring AOP中,通常会用它来生成AopProxy对象。不仅如此,在Hibernate中PO(Persistant Object 持久化对象)字节码的生成工作也要靠它来完成。
以下是简单实现:
//一个普通类,委托类
public class HelloServiceImpl {
void sayHello() {
System.out.println("I'm helloServiceImpl");
}
}
//实现MethodInterceptor接口,回调函数,是增强方法的具体实现
public class CglibCallBack implements MethodInterceptor {
@Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
System.out.println("before");
Object invokeSuper = proxy.invokeSuper(obj, args);
System.out.println("after");
return invokeSuper;
}
}
public class Test {
public static void main(String[] args) {
Enhancer enhancer = new Enhancer();
//设置委托类
enhancer.setSuperclass(HelloServiceImpl.class);
//设置回调,即增强实现的类
enhancer.setCallback(new CglibCallBack());
//创建代理类
HelloServiceImpl helloService = (HelloServiceImpl)enhancer.create();
//执行方法
helloService.sayHello();
}
}
小结:
代理类会为委托类的委托方法生成两个方法,一个是重写的sayHello()方法,另一个是CGLIB$sayHello$()方法,直接调用父类的方法。执行代理方法时,先判断是否存在实现MethodInterceptor接口的回调类对象,如果存在,则调用MethodInterceptor中的intercept方法,否则调用父类方法(不代理)。
cglib中的方法调用不是通过反射实现的,而是直接的方法调用。有一个数组存放着方法的引用。
代理方式 | 实现 | 优点 | 缺点 | 特点 |
---|---|---|---|---|
JDK静态代理 | 代理类与委托类实现同一接口,并且在代理类中需要硬编码接口 | 实现简单,容易理解 | 代理类需要硬编码接口,在实际应用中可能会导致重复编码,浪费存储空间并且效率很低 | 好像没啥特点 |
JDK动态代理 | 代理类与委托类实现同一接口,主要是通过代理类实现InvocationHandler并重写invoke方法来进行动态代理的,在invoke方法中将对方法进行增强处理 | 不需要硬编码接口,代码复用率高 | 只能够代理实现了接口的委托类 | 底层使用反射机制进行方法的调用 |
CGLIB动态代理 | 代理类将委托类作为自己的父类并为其中的非final委托方法创建两个方法,一个是与委托方法签名相同的方法,它在方法中会通过super调用委托方法;另一个是代理类独有的方法。在代理方法中,它会判断是否存在实现了MethodInterceptor接口的对象,若存在则将调用intercept方法对委托方法进行代理 | 可以在运行时对类或者是接口进行增强操作,且委托类无需实现接口 | 不能对final类以及final方法进行代理 | 底层将方法全部存入一个数组中,通过数组索引直接进行方法调用 |