代理模式 23种设计模式的一种,日常开发当中用的比较多,有必要深入研究一下,以便随时都能手写一个动态代理出来,
JDK代理分为三种,
第一种,静态代理,属于硬编码,使用局限基于接口定义,
第二 动态代理 基于接口定义,
第三种 CGLIB基于类的动态代理
第一种就不讨论了,主要针对第二种方式和第三种方式。
1 准备一个接口 userService
2 准备一个接口的实现 类 userServiceImpl
public interface UserService {
public String getName(String name);
public int getAge(int age);
}
public class UserServiceImpl implements UserService {
/* JDK动态代理实现接口
*
*/
public UserServiceImpl() {
}
@Override
public String getName(String name) {
System.out.println("UserServiceImpl 实现方法getName: "+name);
return name;
}
/*
*
*/
@Override
public int getAge(int age) {
System.out.println("UserServiceImpl 实现方法 :getAge: "+age);
return age;
}
}
定义InvocationHandler: 代理类实现
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
/**
* @author lmc
* @Email huyang@126.com
* @date 2018年11月28日
*/
public class MyInvocationHandler implements InvocationHandler{
/* JDK动态代理核心 的生成类
*/
//传入代理对象
Object target;
public MyInvocationHandler(Object tag) {
this.target=tag;
}
/* InvocationHandler 接口的回调函数方法
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//回调返回的对象
Object result =null;
//判断在哪个方法执行之前做一些工作,比如日志,事务等
if (method.getName().equals("getName")) {
System.out.println("before 在调用原始接口方法之前做些准备工作 ");
System.out.println("before+代理类的getName方法 打印日志: method.invoke(target,args)之前执行");
result =method.invoke(target, args);//target是被代理类的对象实例,后面是方法内的参数
System.out.println("after+代理类的getName方法 打印日志 在method.invoke(targe,agrs) 之后执行");
System.out.println("after 代理执行原始接口方法之后,做一些清理工作,解放事务或者日志,清理的工作");
}else{
result=method.invoke(target, args);
}
return result;
}
public static void main(String[] args){
//创建被代理类实例对象
UserServiceImpl userimpl = new UserServiceImpl();
//代理类执行逻辑的实例对象
MyInvocationHandler invocationHandler = new MyInvocationHandler(userimpl);
//转换成调用 的调口对象
UserService userProxy = (UserService) Proxy.newProxyInstance(userimpl.getClass().getClassLoader(), userimpl.getClass().getInterfaces(), invocationHandler);
System.out.println("接口代理 UserService useProxy: "+userProxy.getName("limingcai"));
System.out.println("接口代理 UserService useProxy:"+userProxy.getAge(18));
}
}
测试结果如下
before 在调用原始接口方法之前做些准备工作
before+代理类的getName方法 打印日志: method.invoke(target,args)之前执行
UserServiceImpl 实现方法getName: limingcai
after+代理类的getName方法 打印日志 在method.invoke(targe,agrs) 之后执行
after 代理执行原始接口方法之后,做一些清理工作,解放事务或者日志,清理的工作
接口代理 UserService useProxy: limingcai
UserServiceImpl 实现方法 :getAge: 18
接口代理 UserService useProxy:18
直接new InvocationHandler 接口的方式创建代理也比较方便
public static void main(String[] args){
//这种直接new InvocationHandler的方式,可以随时创建代理,通用性不如实现接口,要通用型就创建类并实现InvocationHandler接口
//创建被代理类实例对象
UserServiceImpl userimpl = new UserServiceImpl();
//代理类返回对象,必须实现此接口. 里面的代理实例对象必须是传进去的参数实例 userimpl
InvocationHandler invocationHandler = new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Object object = null;
if (method.getName().equals("getName")) {
System.out.println("调用之前执行 before getName ");
object = method.invoke(userimpl, args);
System.out.println("调用之后执行的方法after getName");
}else{
object = method.invoke(userimpl, args);
System.out.println(" 直接调用代理方法 ");
}
return object;
}
};
//转换成调用 的调口对象
UserService userProxy = (UserService) Proxy.newProxyInstance(userimpl.getClass().getClassLoader(),
userimpl.getClass().getInterfaces(), invocationHandler);
System.out.println("接口代理 UserService useProxy: "+userProxy.getName("limingcai"));
System.out.println("接口代理 UserService useProxy:"+userProxy.getAge(18));
}
两者的结果并没有什么区别,就是一个定义一个类实现了接口InvocationHandler 比较通用型的类规范
直接 new InvocationHandler 的方式是在临时处理代理的时候就用此方法,省去一些必要的代码,实现方式不一样,本质上是没有什么区别,只是在代码和通用性上不一样。
下面我们从源码角度分析一下这个过程的实现原理,先看InvocationHandler:
InvocationHandler是一个接口,只有一个方法:
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable;
在定义增强逻辑类时,需要实现该接口,并在invoke方法里实现增强逻辑和对真实对象方法的调用。对于invoke方法的三个参数,proxy表示代理对象,method表示真实对象的方法,args表示真实对象方法的参数,这里也可以看出对真实对象方法的调用是通过反射来实现的。
增强逻辑定义好了以后,我们需要一个代理对象来执行,先看如何产生这个代理对象。代理对象的产生是通过调用Proxy类的静态方法:newProxyInstance 源码
public static Object newProxyInstance(ClassLoader loader,
Class<?>[] interfaces,
InvocationHandler h)
throws IllegalArgumentException
//该方法需要三个参数:ClassLoader确保返回的代理对象和真实对象由同一个类 加载器加载,interfaces用于定义代理类应该实现的方法, invocationHandler用于定义代理类的增强逻辑
//这一行代码很关键,这里是获取代理对象Class对象,Proxy类内部维护了代理
//对象的缓存,如果缓存里有则直接返回,如果没有,则生成它
Class<?> cl = getProxyClass0(loader, intfs);
//最后由构造器返回新的代理对象实例
return newInstance(cons, ih);
简单总结 Proxy类通过ProxyClassFactory生成了继承Proxy类并实现了真实对象接口的 $ProxyX代理对象的二进制字节码流,并加载该字节码返回代理对象Class对象,由该Class对象经反射得到构造器构造了代理对象的实例。
使用JDK动态代理有一个很大的限制,就是它要求目标类必须实现了对应方法的接口,它只能为接口创建代理实例。我们在上文测试类中的Proxy的newProxyInstance方法中可以看到,该方法第二个参数便是目标类的接口。如果该类没有实现接口,这就要靠cglib动态代理了。
CGLib采用非常底层的字节码技术,可以为一个类创建一个子类,并在子类中采用方法拦截的技术拦截所有父类方法的调用,并顺势植入横切逻辑。
二、接下来我们进行cglib动态代理的演示。
首先我们需要导包,我用的包是cglib-nodep-2.1_3.jar。
我们首先创建一个代理创建器CglibProxy:
先定义类 UserCglibImpl
再定义代理类 UserCglibProxy
public class UserCglibImpl {
public String getName(String name) {
System.out.println("UserCglibImpl 实现方法getName: "+name);
return name;
}
/*
*/
public int getAge(int age) {
System.out.println("UserCglibImpl 实现方法 :getAge: "+age);
return age;
}
}
public class UserCglibProxy implements MethodInterceptor {
public Object getProxy(Object classz){
Enhancer enhancer = new Enhancer();
//设置需要创建的子类
enhancer.setSuperclass(classz.getClass());
//通过字节码技术动态创建子类实例
enhancer.setCallback(this);
return enhancer.create();
}
/* 实现代理接口的核心方法
*/
@Override
public Object intercept(Object proxy, Method method, Object[] arg, MethodProxy methodProxy) throws Throwable {
Object result = null;
if (method.getName().equals("getName")) {
System.out.println("调用代理方法getName 之前做些工作 befer 打印");
//目标方法调用
result = methodProxy.invokeSuper(proxy, arg);
System.out.println("调用代理方法getName 之后做些工作 after 清理");
}else{
//目标方法调用
result = methodProxy.invokeSuper(proxy, arg);
System.out.println("直接调用被代理方法");
}
return result;
}
/**
* @param args
*/
public static void main(String[] args) {
UserCglibProxy userCglibProxy = new UserCglibProxy();
//传入被代理对象的实例new UserCglibImpl()
UserCglibImpl userimpl = (UserCglibImpl) userCglibProxy.getProxy(new UserCglibImpl());
System.out.println(userimpl.getName("lmc1888889999"));
System.out.println(userimpl.getAge(188));
}
}
另外一种方式是直接 new MethodInterceptor接口方式调用
public class CGLIBproxy {
public String getName(String name){
System.out.println("类初始 name: "+name);
return name;
}
public int getAge(int age){
System.out.println("类初始 age:"+age);
return age;
}
public Object getProxy(Object classz){
// 定义增强器
Enhancer enhancer = new Enhancer();
// 定义要代理的对象
enhancer.setSuperclass(classz.getClass());
// 定义回调函数
enhancer.setCallback(new MethodInterceptor() {
//这里要理解intercept方法的几个参数代表的意思
//obj指的是代理类对象
//Method指的是 目标类中被拦截的方法
//args指的是 调用拦截方法所需的参数
//MethodProxy指的是用来调用目标类被拦截方法的方法,这个方法比反射更快
@Override
public Object intercept(Object obj, Method method, Object[] arg, MethodProxy methrodProxy) throws Throwable {
Object result = null;
System.out.println("invoke 调用之前打印日志");
//这里只能用invokeSuper
result = methrodProxy.invokeSuper(obj, arg);
System.out.println("invoke 代理调用之后打印日志");
return result;
}
});
return enhancer.create();
}
/**
* @param args
*/
public static void main(String[] args) {
//getProxy 这个方法完成可以改写其它方式
CGLIBproxy cglibproxy = new CGLIBproxy();
// 生成代理对象
CGLIBproxy cglib = (CGLIBproxy) cglibproxy.getProxy(cglibproxy); //(cglibproxy) enhancer.create();
// 在代理对象上调用方法
System.out.println("CGLIBproxy 实现类调用: "+cglib.getName("lmc168168168"));
//实现类的方式去调用代理
//UserCglibImpl userproxy = (UserCglibImpl) cglibproxy.getProxy(new UserCglibImpl());
//System.out.println("UserCglibImpl 实现类调用: "+userproxy.getName("lmc168168168"));
}
}
两种接口实现方式,没什么本质区别,都需要传入被代理的对象实例
CGLib动态代理能代理类和接口,但是不能代理final类,也是有一定局限性。
JDK动态代理和CGLib动态代理都是运行时增强,通过将横切代码植入代理类的方式增强。与此不同的是AspectJ,它能够在通过特殊的编译器在编译时期将横切代码植入增强,这样的增强处理在运行时候更有优势,因为JDK动态代理和CGLib动态代理每次运行都需要增强。
源码分析
代理类源码概览
从上面的示例代码中,我们知道通过cglib生成代理类只需要一个目标类和一个回调函数(增强逻辑),下面我们从在代理对象上调用getName()方法出发,一步一步分析cglib动态代理的实现原理。
跟JDK动态代理InvocationHandler一样,先执行前置增强逻辑,然后将目标类的真实逻辑。注意此处目标类的真实逻辑执行cglib的实现方式与JDK实现方式不同:JDK使用的是反射技术,而cglib则使用了FastClass构建方法索引+继承的方式访问目标类的方法。
methrodProxy.invokeSuper(obj, arg); 为什么代码中不能调用invoke,而是只能调用invokeSuper。invoke代码:
因为会陷入无限递归,直至栈溢出
public Object invoke(Object obj, Object[] args) throws Throwable {
try {
init();
FastClassInfo fci = fastClassInfo;
//因为在回调函数中obj传入的代理对象,这里实际上是在代理对象上调用
//getName方法,将陷入无限递归,直至栈溢出
return fci.f1.invoke(fci.i1, obj, args);
} catch (InvocationTargetException e) {
throw e.getTargetException();
} catch (IllegalArgumentException e) {
if (fastClassInfo.i1 < 0)
throw new IllegalArgumentException("Protected method: " + sig1);
throw e;
}
}