动态代理概述
想要说清动态代理是什么,首先我们聊聊代理模式。
代理模式
代理模式是常用的java设计模式,他的特征是代理类与委托类有同样的接口,代理类主要负责为委托类预处理消息、过滤消息、把消息转发给委托类,以及事后处理消息等。代理类与委托类之间通常会存在关联关系,一个代理类的对象与一个委托类的对象关联,代理类的对象本身并不真正实现服务,而是通过调用委托类的对象的相关方法,来提供特定的服务。
按照代理的创建时期,代理类可以分为两种。
静态代理:由程序员创建或特定工具自动生成源代码,再对其编译。在程序运行前,代理类的.class文件就已经存在了。
动态代理:在程序运行时,运用反射机制动态创建而成。
创建过程
创建动态代理的代码大致如下,详细的可以看后面的应用案例部分。
(UserService) Proxy.newProxyInstance(userService.getClass().getClassLoader(), userService.getClass().getInterfaces(), handler);
一个典型的动态代理创建对象过程可分为以下四个步骤:
- 通过实现InvocationHandler接口创建自己的调用处理器 IvocationHandler handler = new InvocationHandlerImpl(…);
- 通过为Proxy类指定ClassLoader对象和一组interface创建动态代理类
Class clazz = Proxy.getProxyClass(classLoader,new Class[]{…}); - 通过反射机制获取动态代理类的构造函数,其参数类型是调用处理器接口类型
Constructor constructor = clazz.getConstructor(new Class[]{InvocationHandler.class}); - 通过构造函数创建代理类实例,此时需将调用处理器对象作为参数被传入
Interface Proxy = (Interface)constructor.newInstance(new Object[] (handler));
为了简化对象创建过程,Proxy类中的newInstance方法封装了2~4,只需两步即可完成代理对象的创建。
JDK动态代理的缺点
关于JDK中动态代理的缺点像少年啦飞驰的博客说的非常好,这里引用一下:
诚然,Proxy已经设计得非常优美,但是还是有一点点小小的遗憾之处,那就是它始终无法摆脱仅支持interface代理的桎梏,因为它的设计注定了这个遗憾。回想一下那些动态生成的代理类的继承关系图,它们已经注定有一个共同的父类叫Proxy。Java的继承机制注定了这些动态代理类们无法实现对class的动态代理,原因是多继承在Java中本质上就行不通。有很多条理由,人们可以否定对 class代理的必要性,但是同样有一些理由,相信支持class动态代理会更美好。接口和类的划分,本就不是很明显,只是到了Java中才变得如此的细化。如果只从方法的声明及是否被定义来考量,有一种两者的混合体,它的名字叫抽象类。实现对抽象类的动态代理,相信也有其内在的价值。此外,还有一些历史遗留的类,它们将因为没有实现任何接口而从此与动态代理永世无缘。如此种种,不得不说是一个小小的遗憾。但是,不完美并不等于不伟大,伟大是一种本质,Java动态代理就是佐例。
应用案例
在当前的系统中,用户登录方法运行的非常好。代码如下:
package com.mrbcy.bigdata.basic.proxy;
public class UserAction {
public static void main(String[] args) {
// 用户登录
UserService userService = new UserServiceImpl();
boolean isLogin = userService.login("张三","1234567");
System.out.println(isLogin ? "登录成功":"登录失败");
}
}
我们突然有一个需求,就是在执行登录操作之前把这个操作记录到日志中。
这个需求很容易满足,我们可以修改UserServiceImpl的login方法,让它去记录日志,类似这样:
package com.mrbcy.bigdata.basic.proxy;
import java.text.SimpleDateFormat;
import java.util.Date;
public class UserServiceImpl implements UserService {
@Override
public boolean login(String userName, String password) {
System.out.println("----------------- 日志消息 --------------------");
System.out.println("【登录操作】用户名:" + userName + " 密码:" + password + " --" + new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()));
System.out.println("----------------------------------------------");
if(userName.equals("张三") && password.equals("123456")){
return true;
}
return false;
}
}
但是这样修改会让日志这个动作和login方法紧紧的耦合在一起,是非常不利于修改的。假设UserService有10个方法,如果我们想让UserService的每一个方法都记录日志,就需要修改10个方法。如果想让所有的Service的所有方法都记录日志,那要修改的内容就成千上万了。这么大的工作量让维护这个日志的动作变得十分困难和容易出错。
使用动态代理技术可以在不修改原本实现的前提下实现日志信息的记录。这也是AOP思想的一种应用。
Handler代码如下:
package com.mrbcy.bigdata.basic.proxy;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.text.SimpleDateFormat;
import java.util.Date;
public class UserServiceImplProxyHandler implements InvocationHandler {
private Object userService;
public UserServiceImplProxyHandler(Object userService){
this.userService = userService;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
if(method.getName().equals("login")){
System.out.println("----------------- 日志消息 --------------------");
System.out.println("【登录操作】用户名:" + args[0] + " 密码:" + args[1] + " --" + new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()));
System.out.println("----------------------------------------------");
}
return method.invoke(userService, args);
}
}
我们也需要稍稍修改一下调用Action的代码,通过一个工厂方法获得Service,这样在以后修改Service时就不用再修改调用方代码。
UserAction.java
package com.mrbcy.bigdata.basic.proxy;
public class UserAction {
public static void main(String[] args) {
// 用户登录
UserService userService = ServiceFactory.getUserService();
boolean isLogin = userService.login("张三","1234567");
System.out.println(isLogin ? "登录成功":"登录失败");
}
}
ServiceFactory.java
package com.mrbcy.bigdata.basic.proxy;
import java.lang.reflect.Proxy;
public class ServiceFactory {
static public UserService getUserService(){
// return new UserServiceImpl();
UserService userService = new UserServiceImpl();
UserServiceImplProxyHandler handler = new UserServiceImplProxyHandler(userService);
return (UserService) Proxy.newProxyInstance(userService.getClass().getClassLoader(), userService.getClass().getInterfaces(), handler);
}
}