1、代理模式概述
代理模式是常用的设计模式之一,可以在原业务代码中通过非侵入式修改添加新的业务逻辑,达到不同业务逻辑间解耦的目的。代理模式分为静态代理和动态代理,其中动态代理是静态代理的优化版,其实现分为JDK实现和CGLIB第三方库实现,Spring的AOP就是基于动态代理实现的。
无论是静态代理还是动态代理,代码中可以分为以下几个角色:
- 抽象角色:一般会使用接口或者抽象类实现;
- 真实角色:被代理的角色;
- 代理角色:类似“中介”,代理真实角色后,一般会做一些附属操作;
- 客户:访问代理对象的人。
代理模式的通用关系图如下所示:
上图中,抽象接口里定义了被代理类要实现的业务逻辑对应的方法和代理类要“增强”的方法,注意被代理类和代理类都要实现这个抽象接口或者继承抽象类。与Client端交互的是代理类而不是被代理类,Client端调用的代理类实例的方法底层都是调用被代理类的业务逻辑,代理类里要有指向被代理类的对象的引用,通过这个引用底层才能调用被代理类的业务逻辑。代理类里实现抽象接口的方法,不仅要包含被代理类的底层业务逻辑(基本的),还要包含一些附加操作或功能的增强,这也是代理模式的设计目的。
2、静态代理
Q通过下面的Demo说明,设计一个业务逻辑接口,接口里定义了业务逻辑包含增删查改;实现类(被代理类)里实现接口里定义的方法;定义代理类,在被代理类基本方法的基础上,增加了在方法调用前和调用后要执行的操作,例子里以打印信息代替,实际业务肯定要比这复杂,角色对应关系如下:
- 抽象角色:
UserService - 真实角色:
UserServiceImpl - 代理角色:
UserServiceProxy - 客户:
UserServiceProxyTest
UserService:
public interface UserService {String add();String delete();String update();String query();}
UserServiceImpl:
public class UserServiceImpl implements UserService{@Overridepublic String add() {System.out.println("调用业务逻辑:增");String result = new String("增业务的执行结果");return result;}@Overridepublic String delete() {System.out.println("调用业务逻辑:删");String result = new String("删业务的执行结果");return result;}@Overridepublic String update() {System.out.println("调用业务逻辑:改");String result = new String("改业务的执行结果");return result;}@Overridepublic String query() {System.out.println("调用业务逻辑:查");String result = new String("查业务的执行结果");return result;}}
UserServiceProxy:
public class UserServiceProxy implements UserService{private UserService userService;public UserServiceProxy(UserService userService) {this.userService = userService;}@Overridepublic String add() {String methodName = "add";String result;beforeInvoke(methodName);result = userService.add();afterInvoke(methodName);return result;}@Overridepublic String delete() {String methodName = "delete";String result;beforeInvoke(methodName);result = userService.add();afterInvoke(methodName);return result;}@Overridepublic String update() {String methodName = "update";String result;beforeInvoke(methodName);result = userService.add();afterInvoke(methodName);return result;}@Overridepublic String query() {String methodName = "query";String result;beforeInvoke(methodName);result = userService.add();afterInvoke(methodName);return result;}private void beforeInvoke(String methodName){System.out.println(methodName + "方法调用前执行的操作");}private void afterInvoke(String methodName){System.out.println(methodName + "方法调用后执行的操作");}}
UserServiceProxyTest:
public class UserServiceProxyTest {@Testpublic void test(){// 创建被代理的对象UserService userService = new UserServiceImpl();// 创建代理对象UserServiceProxy userServiceProxy = new UserServiceProxy(userService);// 通过代理对象调用被代理对象的底层业务逻辑String result = userServiceProxy.delete();System.out.println(result);}}
3、动态代理
静态代理是写好代理类的代码,在编译期间就指定了具体的代理类;动态代理是指具体的代理类是在运行期间由JDK或者第三方库的字节码生成技术生成,这也是“动态”的体现之处。动态代理的实现由两种方式:JDK的实现和第三方库CGLib字节码生成技术实现。既然有了静态代理,为什么还要有动态代理?或者动态代理相比静态代理的优势是什么?答案是少写冗余代码,这点我们在3.1中例子里具体分析。
3.1 JDK实现动态代理
3.1.1 JDK实现动态代理的API
最关键的两个类就是InvocationHandler和Proxy了。
(1)**Proxy**
这里就需要Proxy类的一个方法,即在运行期间生成代理类的方法,newProxyInstance方法:
public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h)
这是一个静态方法,有3个入参:
ClassLoader loader:生成代理对象使用哪个类装载器【一般我们使用的是被代理类的装载器】Class<?>[] interfaces:生成哪个对象的代理对象,通过接口指定【指定要被代理类的接口】InvocationHandler h:生成的代理对象的方法里干什么事【实现InvocationHandler接口,在接口里添加附加操作】
(2)**InvocationHandler**
public interface InvocationHandler {public Object invoke(Object proxy, Method method, Object[] args)throws Throwable;}
InvocationHandler是一个函数式接口,具体实现时如果业务逻辑简单也可以用Lambda表达式,但一般都是定义一个实现类。
Proxy代理类对应一个调用处理类,在这个调用处理类里完成基于底层业务逻辑的附加操作,就相当于一个Handler,这个调用处理类是实现InvocationHandler接口的,具体的底层业务逻辑的调用和附加操作的定义都是在InvocationHandler接口的invoke方法里实现的,其中底层业务逻辑是通过反射API(method.invoke(this.userService, args))实现的。
invoke方法的参数说明:
proxy: 代理类代理的真实代理对象;method: 我们所要调用某个对象真实的方法的Method对象,对应抽象接口里声明的方法;-
3.1.2 JDK实现动态代理Demo
这个Demo要实现的任务及场景跟上面静态代理的一模一样,就是想通过静态代理和动态代理两种代理模式实现相同的需求,以对比二者的不同,这里再声明一下代理模式里的角色对应的类,加粗的角色是动态代理和静态代理不一样的地方:
抽象角色:
UserService;- 真实角色:
**UserServiceImpl**,作为**UserServiceInvocationHandler**的一个属性,而不是代理角色的一个属性; - 代理角色:
**UserServiceProxy**,在**UserServiceProxyTest**里通过**Proxy.newProxyInstance()**方法动态地生成; - 客户:
**UserServiceProxyTest**。
以下是代码,其中UserService和UserServiceImpl和静态代理里的一样,不一样的是UserServiceInvocationHandler和UserServiceProxyTest:
UserServiceInvocationHandler:
public class UserServiceInvocationHandler implements InvocationHandler {private UserService userService;public UserServiceInvocationHandler(UserService userService){this.userService = userService;}@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {// 通过反射API获得被调用方法的方法名String methodName = method.getName();// 底层方法调用前的附加操作beforeInvoke(methodName);// 通过反射API调用底层业务逻辑String result = (String) method.invoke(this.userService, args);// 底层方法调用后的附加操作afterInvoke(methodName);// 返回底层业务逻辑的调用结果return result;}private void beforeInvoke(String methodName){System.out.println(methodName + "方法调用前执行的操作");}private void afterInvoke(String methodName){System.out.println(methodName + "方法调用后执行的操作");}}
UserServiceProxyTest:
public class UserServiceProxyTest {@Testpublic void test2(){UserService userService = new UserServiceImpl();UserServiceInvocationHandler handler = new UserServiceInvocationHandler(userService);// Java API创建一个代理业务逻辑类的代理类实例UserService userServiceProxy = (UserService) Proxy.newProxyInstance(userService.getClass().getClassLoader(),userService.getClass().getInterfaces(), handler);// 通过代理类实例调用业务逻辑String result = userServiceProxy.query();System.out.println(result);}}
在UserServiceInvocationHandler中我们可以看出,不需要像静态代理里的UserServiceProxy里对每一个方法(add、delete、update和query)都去添加附加操作,只需要在**UserServiceInvocationHandler**的**invoke**方法里这一处添加附加操作就可以了,且在**invoke**里添加的附加操作对抽象接口里声明的所有方法都生效,达到了少些冗余代码,解耦逻辑的目的。
3.2 CGLib实现动态代理(没有深入研究)
使用JDK实现动态代理虽然相比静态代理代码解耦上有个改善,但JDK实现动态代理的话,他有一个限制,就是它只能为接口创建代理实例,而对于没有通过接口定义业务方法的类,如何创建动态代理实例呢?答案就是CGLib。
CGLIB(Code Generation Library)是一个基于ASM的字节码生成库,它将被代理类的class文件加载进来,允许我们在运行时对字节码进行修改和动态生成。
Srping默认采用JDK的方式实现动态代理,可以通过设置参数切换成CGLIB。
3.3 静态代理和动态代理对比
- 相比静态代理,动态代理一个最大的好处是:需要被增强的方法都声明在接口中,并在
**InvocationHandler**接口的**invoke**方法里统一维护,后期如果抽象接口里新增需要增强的方法时,或者对方法增强的操作进行改动,仅需要在**invoke**方法里这一处维护即可,不像静态代理需要改动代理类里所有实现抽象接口的方法; - 要对抽象接口中的方法进行个性化增强时,静态代理可以做到(因为是一个方法一个增强处理,这也带来了冗余代码),动态代理不能做到(动态代理invoke方法是对所有接口里声明的方法做统一的增强);
- 静态代理,一个代理类只服务于一个被代理类,如果被代理类比较多,需要为每一个被代理类增加一个对应的代理类,维护和解耦上都不理想;动态代理,在程序运行时动态地生成代理类,不需要为每一个被代理类创建一个代理类,允许一个
InvocationHandler对应多个被代理类(如果业务逻辑允许); 动态代理仅针对抽象接口,静态代理可以针对抽象接口,也可以针对抽象类。
Todo
正向代理、反向代理;
- CGLIB具体使用;
