介绍静态代理
代理模式在不改变原始类(或者叫被代理类)代码的情况下,通过引入代理类来给原始类附加功能。
下面我们通过代码来解释以下这句话。
具体的实现代码如下所示:
/*** 原始类(或者称为被代理类)* UserController 类只负责业务功能*/public class UserController implements IUserController {public String login() {// 业务逻辑return "login() 的返回值";}public String register() {// 业务逻辑return "register() 的返回值";}}
/*** 代理类* UserControllerProxy 负责在业务代码执行前后附加其他逻辑代码,并通过委托的方式调用原始类来执行业务代码*/public class UserControllerProxy implements IUserController {IUserController userController;public UserControllerProxy(IUserController userController) {this.userController = userController;}@Overridepublic String login() {System.out.println("在 login() 业务代码执行前,附加的其他逻辑代码");String login = userController.login();System.out.println("在 login() 业务代码执行后,附加的其他逻辑代码");return login;}@Overridepublic String register() {System.out.println("在 register() 业务代码执行前,附加的其他逻辑代码");String register = userController.register();System.out.println("在 register() 业务代码执行后,附加的其他逻辑代码");return register;}}
public interface IUserController {public String login();public String register();}
public static void main(String[] args) {UserControllerProxy userControllerProxy = new UserControllerProxy(new UserController());userControllerProxy.login();userControllerProxy.register();/*执行结果打印如下:在 login() 业务代码执行前,附加的其他逻辑代码在 login() 业务代码执行后,附加的其他逻辑代码在 register() 业务代码执行前,附加的其他逻辑代码在 register() 业务代码执行后,附加的其他逻辑代码*/}
代理类 UserControllerProxy 和原始类 UserController 实现相同的接口 IUserController。
- UserController 类只负责业务功能。
- 代理类 UserControllerProxy 负责在业务代码执行前后附加其他逻辑代码,并通过委托的方式调用原始类来执行业务代码。
上面的代码在不改变原始类(UserControler 类)代码的情况下,通过引入代理类(UserControllerProxy 代理类),来给原始类附加功能。
下面解释一下委托的意思,有两个对象参与处理同一个请求,接受请求的对象将请求委托给另一个对象来处理。
上面的代码中,两个对象(UserControler 的实例和 UserControllerProxy 的实例)参与处理同一个请求,接受请求的对象(UserControllerProxy 的实例)将请求委托给另一个对象(UserControler 的实例)来处理。
上面代码这种实现代理模式的前提是,原始类已经实现了某接口,或者原始类没有定义接口,但是我们有权修改原始类让它实现某接口。
但是,如果原始类并没有定义接口,并且原始类代码并不是我们开发维护的(比如它来自一个第三方的类库),我们也没办法直接修改原始类,给它重新定义一个接口。在这种情况下,我们该如何实现代理模式呢?
在这种情况下,我们可以让代理类继承原始类,具体的实现代码如下所示:
/*** 原始类(或者称为被代理类)* UserController 类只负责业务功能*/public class UserController {public String login() {// 业务逻辑return "login() 的返回值";}public String register() {// 业务逻辑return "register() 的返回值";}}
/*** 代理类* UserControllerProxy 负责在业务代码执行前后附加其他逻辑代码,并通过委托的方式调用原始类来执行业务代码*/public class UserControllerProxy extends UserController {@Overridepublic String login() {System.out.println("在 login() 业务代码执行前,附加的其他逻辑代码");String login = super.login();System.out.println("在 login() 业务代码执行后,附加的其他逻辑代码");return login;}@Overridepublic String register() {System.out.println("在 register() 业务代码执行前,附加的其他逻辑代码");String register = super.register();System.out.println("在 register() 业务代码执行后,附加的其他逻辑代码");return register;}}
public static void main(String[] args) {UserControllerProxy userControllerProxy = new UserControllerProxy();userControllerProxy.login();userControllerProxy.register();/*执行结果打印如下:在 login() 业务代码执行前,附加的其他逻辑代码在 login() 业务代码执行后,附加的其他逻辑代码在 register() 业务代码执行前,附加的其他逻辑代码在 register() 业务代码执行后,附加的其他逻辑代码*/}
介绍动态代理
刚刚的代码实现实际上存在一定的问题:
- 一方面,我们需要在代理类中,将原始类的所有的方法,都重新实现一遍,并且为每个方法都附加相似的代码逻辑(附加功能)。
- 另一方面,如果要添加附加功能的类有不止一个,我们需要针对每个类都创建一个代理类。
简单来说就是,静态代理模式可能存在太多重复代码的问题。那这个问题怎么解决呢?
我们可以使用动态代理来解决这个问题。
动态代理就是,我们不事先为原始类创建代理类,而是在程序运行的过程中,动态的为原始类创建代理类。
在 Java 中,动态代理有很多种实现方式。其中两种最常见的实现方式是:
- JDK 自身提供的动态代理;
- cglib 动态代理。
JDK 自身提供的动态代理,主要利用了 Java 的反射机制。
cglib 动态代理,利用传说中更高性能的字节码操作机制。
下面我们逐一介绍 JDK 自身提供的动态代理 和 cglib 动态代理的实现。
JDK 动态代理
我们用 JDK 自身提供的动态代理来实现上面的功能,具体的代码实现如下所示:
/*** 原始类(或者称为被代理类)* UserController 类只负责业务功能*/public class UserController implements IUserController {public String login() {// 业务逻辑return "login() 的返回值";}public String register() {// 业务逻辑return "register() 的返回值";}}
public class ProxyFactory {public Object newProxyInstance(Object proxied) {ClassLoader classLoader = proxied.getClass().getClassLoader();Class<?>[] interfaces = proxied.getClass().getInterfaces();myInvocationHandler myInvocationHandler = new myInvocationHandler(proxied);Object proxyInstance = Proxy.newProxyInstance(classLoader, interfaces, myInvocationHandler);return proxyInstance;}private class myInvocationHandler implements InvocationHandler {private Object proxied;public myInvocationHandler(Object proxied) {this.proxied = proxied;}@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {System.out.println("在业务代码执行前,附加的其他逻辑代码" + method.getName());Object result = method.invoke(proxied, args);System.out.println("在业务代码执行后,附加的其他逻辑代码" + method.getName());return result;}}}
public interface IUserController {public String login();public String register();}
public static void main(String[] args) {ProxyFactory proxyFactory = new ProxyFactory();IUserController userControllerProxy = (IUserController) proxyFactory.newProxyInstance(new UserController());String login = userControllerProxy.login();String register = userControllerProxy.register();System.out.println(login);System.out.println(register);/*执行结果打印如下:在业务代码执行前,附加的其他逻辑代码login在业务代码执行前,附加的其他逻辑代码login在业务代码执行前,附加的其他逻辑代码register在业务代码执行前,附加的其他逻辑代码registerlogin() 的返回值register() 的返回值*/}
上面的 JDK 自身提供的动态代理的代码,其中最主要的就是 Proxy.newProxyInstance(classLoader, interfaces, myInvocationHandler);这个方法,下面介绍一下这个方法定义的参数:
- ClassLoader loader:定义代理类的类加载器
- Class<?>[] interfaces:代理类要实现的接口列表
- InvocationHandler h:将方法调用分派给的调用处理程序
其中 InvocationHandler 类的 invoke() 方法的第一个参数 proxy 暗藏玄机,期待一起探索。
JDK 自身提供的动态代理的实现原理就是,代理类实例实现了指定的接口,当我们调用代理类实例的任意一个方法(接口中定义的方法)时,都会被分派到我们定义的调用处理程序中,也就是 InvocationHandler 实例的 invoke() 方法,在 invoke() 方法中通过反射将请求委托给原始类对象。
cglib 动态代理
上面介绍的 JDK 自身提供的动态代理仍然有局限性,因为要想创建原始类的代理类,那么原始类必须实现接口。如果原始类没有实现接口,那么 JDK 自身提供的动态代理时无法使用的。
那这个问题怎么解决呢?我们可以使用 cglib 动态代理解决这个问题。
cglib 动态代理采取的是创建原始类(被代理类)的子类的方式。
具体的代码实现如下所示:
因为是子类化,我们可以达到近似使用被调用者本身的效果。在 Spring 编程中,框架通常会处理这种情况,当然我们也可以显式指定。
Spring AOP 支持两种模式的动态代理
如利用传说中更高性能的字节码操作机制,类似 ASM、cglib(基于 ASM)、Javassist 等。
JDK 动态代理在设计和实现上与 cglib 等方式有什么不同,进而如何取舍?
动态代理解决的问题,及应用场景
动态代理解决了什么问题,它的应用场景是什么?
日志、用户鉴权、全局性异常处理、性能监控,甚至事务处理
