浅谈下代理模式
一直没太弄明白的代理模式,这次花时间弄明白下,如果有错误,希望有人看到可以纠正交流下,我理解的 代理模式就是给A对象提供一个代理B对象,B对象可以控制调用A对象的方法。
代理模式分为两种,一种是静态代理,一种是动态代理。下面我们先看下静态代理。
静态代理
这里面有三个角色,百度百科就可以查到。
抽象角色: 通过接口或抽象类声明真实角色实现的业务方法。
代理角色: 实现抽象角色,是真实角色的代理,通过真实角色的业务逻辑方法来实现抽象方法,并可以附加自己的操作。
真实角色: 实现抽象角色,定义真实角色所要实现的业务逻辑,供代理角色调用。
下面看下静态代理的代码实现
第一步,先定义一个抽象角色,看到抽象了,当然是接口:
package proxy;
public interface User {
void buyHosue();
}
第二步,再定义一个真实角色,来实现这个抽象角色:
package proxy;
public class UserImpl implements User {
@Override
public void buyHosue() {
System.out.println("Buy a big house.");
}
}
第三步,定义一个代理角色,代理角色的构造或者实例方法我们需要将抽象角色的实现真实角色当作参数传入,参数类型需要保证是抽象角色
package proxy;
public class UserProxy implements User {
public User user;
public UserProxy(User user) {
this.user = user;
}
@Override
public void buyHosue() {
System.out.println("Prepare money to buy a house");
user.buyHosue();
System.out.println("Prepare money to decorate the house");
}
}
第四步,测试静态代理。
package proxy;
public class ProxyTest {
public static void main(String[] args) {
User user = new UserImpl();
UserProxy proxyA = new UserProxy(user);
proxyA.buyHosue();
User userB = new UserBImpl();
UserProxy proxyB = new UserProxy(userB);
proxyB.buyHosue();
}
}
看下输出:
Prepare money to buy a house
Buy a big house.
Prepare money to decorate the house
Prepare money to buy a house
B Buy a big house.
Prepare money to decorate the house
可以看出通过抽象对象的不同实现,我们可以代理不同的真实角色。代理类可以为委托类预处理消息、把消息转发给委托类和事后处理消息等
总结,静态代理是由程序员创建或特定工具自动生成源代码,在对其编译。在程序员运行之前,代理类.class文件就已经被创建了。动态代理是在程序运行时通过反射机制动态创建的。
优点: 符合开闭原则,对目标对象进行了扩展。
缺点: 通常只代理一个类,事先知道要代理的是什么,接口一旦发生改变,代理类也得相应修改。
动态代理:
与静态代理类对照的是动态代理类,动态代理类的字节码在程序运行时由Java反射机制动态生成,无需程序员手工编写它的源代码。动态代理类不仅简化了编程工作,而且提高了软件系统的可扩展性,因为Java反射机制可以生成任意类型的动态代理类。java.lang.reflect 包中的Proxy类和InvocationHandler接口提供了生成动态代理类的能力,这种代理模式是 JDK动态代理模式 ,实现了JDK里的InvocationHandler接口的invoke方法,但注意的是代理的是接口,也就是你的真实对象必须要实现抽象对象,通过Proxy里的newProxyInstance得到代理对象,我们在网上看到的先用Proxy.getProxyClass生成代理类,再获得代理类的构造函数,最后新建一个实例是Proxy.newProxyInstance的内部实现,直接用newProxyInstance这种方式最简单;另外一种是 Cglib动态代理模式 ,这种模式是借助利用asm开源包,对代理对象类的class文件加载进来,通过修改其字节码生成子类来处理。
JDK动态代理模式
对象类型还是三个,抽象对象,真实对象,代理对象
贴下我的练习代码,还是用上面的抽象对象User
第一步,真实对象
package proxy;
import proxy.annotation.AfterExecute;
import proxy.annotation.BeforeExecute;
public class UserImpl implements User {
@BeforeExecute
public void before() {
System.out.println("Prepare money to buy a house");
}
@Override
public void buyHosue() {
System.out.println("Buy a big house.");
}
@AfterExecute
public void after() {
System.out.println("Prepare money to decorate the house");
}
}
新增了两个方法before和after用来实现一点小逻辑,自定义了两个注解,一个BeforeExecute,另一个是AfterExecute。
第二步,代理对象
package proxy;
import proxy.annotation.AfterExecute;
import proxy.annotation.BeforeExecute;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
public class DynamicProxyHandler<T> implements InvocationHandler {
private T realObject;
private Method beforeMethod;
private Method afterMethod;
public Object newProxy(T object) {
this.realObject = object;
//获取真实对象中的所有方法
Method[] declaredMethods = object.getClass()
.getDeclaredMethods();
for (Method method : declaredMethods) {
if (!method.isAccessible())
//setAccessible设置成true取消了Java的权限控制检查,
//是用来访问private的方法
method.setAccessible(true);
//如果注解是BeforeExecute
if (method.isAnnotationPresent(BeforeExecute.class)) {
beforeMethod = method;
} else if (method.isAnnotationPresent(AfterExecute.class)) {
afterMethod = method;
}
}
//用newProxyInstance获取代理对象的实例
//第一个参数是真实对象的类加载器
//第二个参数是一个数组,我们都知道一个类可以实现多个接口,所以这里是数组,在主程序使用的时候,我们可以转换成要用的接口类型就行
//第三个参数也是需要将DynamicProxyHandler传入,这里也是对invoke的回调
//可以直接在这里new一个InvocationHandler,就不用DynamicProxyHandler继承实现InvocationHandler了,具体看下cglib代理。
return Proxy.newProxyInstance(realObject .getClass().getClassLoader(), object.getClass().getInterfaces(), this);
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
try {
if (null != beforeMethod)
beforeMethod.invoke(realObject);
} catch (Exception e) {
e.printStackTrace();
}
Object result = method.invoke(realObject, args);
try {
if (null != afterMethod)
afterMethod.invoke(realObject);
} catch (Exception e) {
e.printStackTrace();
}
return result;
}
}
这里加了一个应用,对AOP的使用,如果不需要,完全可以只需要method调用invoke方法就行了。
第三步,测试动态代理
package proxy;
public class DynamicProxyTest {
public static void main(String[] args) {
DynamicProxyHandler dynamicProxyHandler = new DynamicProxyHandler();
User user = (User) dynamicProxyHandler.newProxy(new UserImpl());
user.buyHosue();
}
}
输出:
Prepare money to buy a house
Buy a big house.
Prepare money to decorate the house
总结,可以看出JDK动态代理的代理对象完全不需要知道它代理的是什么鬼,在运行时,人家给啥他就代理啥,这个极大的增强了代码的扩展性,减少了代码的改动,这就是所谓开闭原则。
Cglib动态代理模式
如果我们代理的对象不是一个接口,而是一个单纯的对象,这时候JDK代理模式就歇菜了,所以这时候就出现了Cglib这个大救星,它的主要逻辑是借助Enhancer这个工具类为所代理的类生成一个子类,覆盖其中的方法,因为是继承,所以里面的类不能用final修饰。所以在这种代理模式中是没有必要有抽象对象的,当然有也没有关系,我们使用的是真实对象realObject,直接看代码。
第一步,真实对象
package proxy;
//注意:这里没有实现抽象对象(接口)
public class CglibUser {
public void buyHosue() {
System.out.println("Buy a big house.");
}
}
第二步,通过Enhancer为代理的对象创建一个子类
package proxy;
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;
public class CglibProxy implements MethodInterceptor {
private Object realObject;
public Object getInstance(Object target) {
//真实对象
this.realObject = target;
//new一个工具类,后面的操作靠它了,可以把他理解成工具类
Enhancer enhancer = new Enhancer();
//当然是给真实对象创建一个子类,所以设置它为父类
enhancer.setSuperclass(this.realObject.getClass());
//callback回调的方法就是这个方法本身,这里回调的方法就是invoke方法
//我们要调用的代理的方法就在这个invoke方法里了
enhancer.setCallback(this);
//创建一个子类,返回给主程序使用,在主程序做你想做的事
return enhancer.create();
}
@Override
public Object intercept(Object object, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
System.out.println("Prepare money to buy a house");
Object result = methodProxy.invoke(realObject, args);
System.out.println("Prepare money to decorate the house");
return result;
}
}
这个不是很明显,再看下下面这个,两个逻辑一样,回调显示的更加明显
package proxy;
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;
/**
* Created by user on 2018/12/20.
*/
public class CglibProxy2<T> {
private Object realObject;
public Object getInstance(Object target) {
//真实对象
this.realObject = target;
//new一个工具类,后面的操作靠它了,可以把他理解成工具类
Enhancer enhancer = new Enhancer();
//当然是给真实对象创建一个子类,所以设置它为父类
enhancer.setSuperclass(this.realObject.getClass());
//callback的时候我们实现了一个MethodInterceptor
//我们要调用的代理的方法就在这个invoke方法里了
enhancer.setCallback(new MethodInterceptor() {
@Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
System.out.println("Prepare money to buy a house");
Object result = methodProxy.invoke(realObject, args);
System.out.println("Prepare money to decorate the house");
return result;
}
});
return enhancer.create();
}
}
第三步,测试Cglib代理
package proxy;
public class CglibProxyTest {
public static void main(String[] args) {
CglibProxy cglibProxy = new CglibProxy();
CglibUser cglibUser = (CglibUser) cglibProxy.getInstance(new CglibUser());
cglibUser.buyHosue();
CglibProxy2 cglibProxy2 = new CglibProxy2();
CglibUser cglibUser2 = (CglibUser) cglibProxy2.getInstance(new CglibUser());
cglibUser2.buyHosue();
}
}
看下输出结果:
Prepare money to buy a house
Buy a big house.
Prepare money to decorate the house
Prepare money to buy a house
Buy a big house.
Prepare money to decorate the house
总结,Cglib代理模式的优点就是看可以给不是接口实现类做代理,这极大的方便了spring对象的管理。
JDK代理和Cglib代理,简单来看就是先生成新的class文件,然后加载到jvm中,然后使用反射,先用class取得他的构造方法,然后使用构造方法反射得到他的一个实例。唯一的区别在于生成新的class文件方式和结果不一样。在生成代理对象的时候两者都使用了缓存,JDK代理使用了WeakReference引用,这个可以跟下源码,JDK中是在获取代理类的时候使用的,而cglib使用的直接是 WeakHashMap,基本也类似。
一些疑问
1.spring使用的是哪种代理呢?
如果一个类有顶层接口,则默认使用JDK的动态代理来代理,如果直接是一个类,不是实现类,则使用cglib动态代理。 其次,如果没有需要代理的方法,如所有方法都没有@Transactional @Service @Controller @Repository等注解,则不会被代理。Tips,springboot好像默认使用的cglib代理,改动参数配置改不过来。
2.Mybatis的Mapper使用的是哪种代理?
JDK动态代理,我们处理数据库的数据的时候都是借助Mybatis的SqlSession这个强大的接口去实现对数据库语句的CRUD,代理的时候我们只需要将所用到的SqlSession实现类也就是真实对象传输给JDK,让JDK去生成代理对象。
看下源码MapperProxyFactory的newInstance方法就一目了然了:
3.Spring的Aop使用的是哪种代理?
- 如果所要代理的对象实现了接口,默认情况下会采用JDK的动态代理实现AOP;
- 如果所要代理的对象实现了接口,可以强制使用Cglib代理,因为他不关注是否实现接口,在spring配置文件中加入<aop:aspectj-autoproxy proxy-target-class=“true”/>
- 如果目标对象没有实现接口,则会使用Cglib代理
- 所以spring的代理模式是在两种模式之间切换,但是springboot2.0之后默认使用了Cglib代理,修改强制不使用也不起作用,这个我还在验证。