前言
代理模式是一个高频使用的设计模式,其原因在于我们可以在不改变被代理类的情况下,提供一个和被代理类一样的代理类,并负责预处理被代理类的方法,过滤或执行被代理类的方法以及后续操作。这里所说的一样是指代理类对象可以替代被代理类对象,实现这种效果有两种,一种是代理类和被代理类实现同一个接口,另一种是代理类直接继承自被代理类,这两种方式在接下来的jdk动态代理和cglib中均有体现。在深入了解动态代理之前,我们先了解一下Java的反射机制和静态代理,其中反射机制是动态代理实现的基础。
Java的反射机制
通过反射实例化对象
Java反射实例化对象主要依赖于java.lang.Class对象,其中一个重要的方法是
//通过全限名称获取Class对象
public static Class<?> forName(String className)
通过全限名称获取Class对象,完成类加载的过程,如果需要调用无参的构造方法用Class对象的newInstance()即可完成实例化过程,一个具体的例子如下
result=(ReflectService) Class.forName(serviceFullName).newInstance();
如果需要调用有参数的构造方法,则还需要调用public Constructor<T> getConstructor(Class<?>... parameterTypes)
方法指明构造方法的参数类型列表,然后执行public T newInstance(Object ... initargs)
指明构造方法的参数值的表。具体的例子如下
result=(ReflectService) Class.forName(serviceName).getConstructor(String.class).newInstance(param);
执行反射方法
执行反射方法需要先获取Class对象,获取Class对象除了使用Class.forName方法之外,还可以通过对象的getClass()方法,接下来执行
- public Method getMethod(String name, Class<?>… parameterTypes),通过Class的getMethod方法获取method对象,name为方法名,parameterTypes为参数类型表。
- public Object invoke(Object obj, Object… args),执行method对象的invoke方法,obj为执行方法的对象,args为方法参数值
如果方法没有参数,可以不写parameterTypes和args,一个调用的具体例子如下
//有参数
Method method=service2.getClass().getMethod("sayToTwo", String.class,String.class);
method.invoke(service2, "person1","person2");
//无参数
Method method2=service2.getClass().getMethod("sayHello");
method2.invoke(service2);
静态代理
假设我们有一个UserManager接口,其中定义了三个方法,用来增加用户,删除用户,修改用户信息。
public interface UserManager {
public void addUser(String username,String password);
public void delUser(int userId);
public void modifyUser(int userId,String username,String password);
}
并且我们有一个实现类UserManager实现了这个接口
public class UserManagerImpl implements UserManager {
@Override
public void addUser(String username, String password) {
System.out.println("---UserManagerImpl:addUser---");
}
@Override
public void delUser(int userId) {
System.out.println("---UserManagerImpl:delUser---");
}
@Override
public void modifyUser(int userId, String username, String password) {
System.out.println("---UserManagerImpl:modifyUser---");
}
public void addBatch(List<String>users) {
System.out.println("---UserManagerImpl:addBatch---");
}
}
假设我们想要在每次操作之前都想加一个日志操作,那么最简单的方式是直接在UserManagerImpl中增加一个logging()方法,然后加到方法实际调用之前,但是这样的坏处是日志记录和业务代码混在了一起,这不是我们想要的,另一种情况是这个类在第三方的jar包中,我们不能直接修改这个类代码。静态代理可以解决这个问题,我们不必要修改原有的代码也可以把想要的日志操作加进去,它的类图是这样设计的。
其中,被代理对象是UserManagerImpl,代理对象UserManagerProxy,它们都实现了UserManager接口,而UserManagerImplProxy持有UserManagerImpl对象,在addUser方法之前插入logging方法。这样UserManagerImpl和UserManagerProxy对于使用了UserManager接口的对象的地方是等同的,我们实现了在方法之前插入方法。 虽然我们实现了在不修改原有代码的情况下,在原有的业务逻辑之前加入日志记录的操作,将日志操作与原有的业务逻辑代码解耦,但是在代理类中,日志操作仍然和业务逻辑耦合在一起,我们将这种耦合关系转移到了代理类中,代理类中的耦合关系是不可避免的。对于每个业务逻辑如果都有不同的日志操作过程,也就是代理的粒度细粒度到这种操作,静态代理是适合的。但是如果插入的逻辑,比如日志操作,是同样的,并且需要插入的方法数是很多的,比如有1000个方法之前需要插入业务逻辑,那么静态代理就显得不那么合适了,因为我们需要在代理类中插入1000次logging的调用。
JDK动态代理
相比静态代理,动态代理的动态在于方法,我们可以拦截所有接口定义的方法,并且在实际方法调用的过程中获取到实际调用的方法,然后进行一些逻辑处理。JDK动态代理实现的过程如下:
- 首先,继承InvocationHandler,重写public Object invoke(Object proxy, Method method, Object[] args)方法,其中proxy为自动生成的代理对象,method为被调用的方法,args为方法参数
- 使用Proxy的静态方法public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h)生成对应的代理实例
- 将代理实例上转型到对应的接口,执行接口中的方法
例如我们上面的例子,我们首先实现一个InvocationHandler的实现类,然后用createProxy生成了代理对象。
public class LogHandler implements InvocationHandler {
public Object target;
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
logging();
Object result=method.invoke(target, args);
return result;
}
private void logging() {
System.out.println("start logging now!");
}
public Object createProxy(Object target) {
this.target=target;
return Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), this);
}
}
调用的过程
//动态代理调用
LogHandler logHandler=new LogHandler();
UserManager dynamicProxy=(UserManager) logHandler.createProxy(userManager);
dynamicProxy.addUser("hello", "hello");
dynamicProxy.delUser(1);
dynamicProxy.modifyUser(1, "hello", "hello");
从调用过程来看,我们就很明白自动生成的代理类实现了UserManager接口,实际上代理类还继承了Proxy类。另外一个问题,如果我们想要执行被代理类原有的方法,必须给InvocationHandler传入一个被代理类的对象target,在invoke方法中执行method.invoke放在中执行执行的类。为什么我们不能用invoke方法中的proxy,它不是也是实现了同样的接口吗?回答这个问题很简单,因为proxy类的对应方法增强了,如果用proxy的话,它会递归的调用该方法,直至虚拟机栈溢出。
从原理上来看,动态代理的关键在于可以动态的确定是哪个方法被调用,并且可以统一路由到InvocationHandler的invoke方法上,使得所有方法之前插入同样的逻辑操作得以归结到一个方法只内,不可以不说设计之精妙,至于jdk是如何路由到invoke方法上的,有待继续探究。
另外有趣的一点是,如果抛开代理的本意,我可以仅在提供接口的情况下而不提供具体实现类(被代理类)的情况下使用这种分发技术,执行对应的逻辑,实际上,这里的接口就仅仅充当了一组规范而已,而这一组规范路由到同一个处理方法,在注解方式提供参数信息的帮助下,这种方式也大有用武之处。
在上面的UserManagerImpl类中,我不仅实现了接口方法,而且提供了一个子类特有的方法addBatch(List
cglib动态代理
不同于JDK动态代理代理类要和被代理类实现同样的接口,cglib动态代理使用asm字节码增强生成代理对象直接继承自被代理对象,从而实现动态代理的过程。其实现过程如下
- 实现MethodInterceptor接口,实现其中的public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy)方法
- 配置增强类Enhancer的父类,MethodInterceptor处理对象
- Enhanceer对象强转型被代理类,执行对应的方法
其基本过程和JDK动态代理类似,唯一的不同是我们不用传入被代理类,而只是被代理类的Class对象,重要的实现代码如下
/**
* @param proxy 代理对象,当前对象
* @param method 方法
* @param args 方法参数
* @param methodProxy代理方法,用于执行原来的代理方法
* @return 代理逻辑返回值
*/
public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
logging();
System.out.println(proxy.getClass().getSuperclass());
//这里只能拿到代理对象proxy,所以只能通过找代理对象的父方法执行原来的方法
//如果能够拿到原始的被代理对象(比如通过构造函数传入),则可以通过method来进行执行
Object result=methodProxy.invokeSuper(proxy, args);
return result;
}
intercept每个参数代表的含义不同,我们如果需要调用被代理类的方法,需要通过MethodProxy的invokeSuper方法调用。 设置增强类的过程
public Object getProxy(Class<?> cls) {
//增强类
Enhancer enhancer=new Enhancer();
//设置增强的对象
enhancer.setSuperclass(cls);
//设置代理逻辑
enhancer.setCallback(this);
return enhancer.create();
}
调用过程
//cglib动态代理
CglibProxy cglibProxy=new CglibProxy();
UserManagerImpl cglibUserManager=(UserManagerImpl)cglibProxy.getProxy(UserManagerImpl.class);
cglibUserManager.addBatch(new ArrayList<>());
cglibUserManager.delUser(1);
参考原文:
- http://suo.im/5at3wJ