动态代理
Java在JDK1.3后引入的动态代理机制,使我们可以在运行期动态的创建代理类。使用动态代理实现AOP需要有四个角色:被代理的类,被代理类的接口,织入器,和InvocationHandler,而织入器使用接口反射机制生成一个代理类,然后在这个代理类中织入代码。被代理的类是AOP里所说的目标,InvocationHandler是切面,它包含了Advice和Pointcut。
3.1.1 使用动态代理
那如何使用动态代理来实现AOP。下面的例子演示在方法执行前织入一段记录日志的代码,其中Business是代理类,LogInvocationHandler是记录日志的切面,IBusiness, IBusiness2是代理类的接口,Proxy.newProxyInstance是织入器。
清单一:动态代理的演示
Java代码
- public static void main(String[] args) {
- //需要代理的接口,被代理类实现的多个接口都必须在这里定义
- Class[] proxyInterface = new Class[] { IBusiness.class, IBusiness2.class };
- //构建AOP的Advice,这里需要传入业务类的实例
- LogInvocationHandler handler = new LogInvocationHandler(new Business());
- //生成代理类的字节码加载器
- ClassLoader classLoader = DynamicProxyDemo.class.getClassLoader();
- //织入器,织入代码并生成代理类
- IBusiness2 proxyBusiness = (IBusiness2) Proxy.newProxyInstance(classLoader, proxyInterface, handler);
- //使用代理类的实例来调用方法。
- proxyBusiness.doSomeThing2();
- ((IBusiness) proxyBusiness).doSomeThing();
- }
- /**
- * 打印日志的切面
- */
- public static class LogInvocationHandler implements InvocationHandler {
- private Object target; //目标对象
- LogInvocationHandler(Object target) {
- this.target = target;
- }
- @Override
- public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
- //执行原有逻辑
- Object rev = method.invoke(target, args);
- //执行织入的日志,你可以控制哪些方法执行切入逻辑
- if (method.getName().equals("doSomeThing2")) {
- System.out.println("记录日志");
- }
- return rev;
- }
- }
- 接口IBusiness和IBusiness2定义省略。
业务类,需要代理的类。
Java代码
- public class Business implements IBusiness, IBusiness2 {
- @Override
- public boolean doSomeThing() {
- System.out.println("执行业务逻辑");
- return true;
- }
- @Override
- public void doSomeThing2() {
- System.out.println("执行业务逻辑2");
- }
- }
输出
Java代码
- 执行业务逻辑2
- 记录日志
- 执行业务逻辑
可以看到“记录日志”的逻辑切入到Business类的doSomeThing方法前了。
3.1.2 动态代理原理
本节将结合动态代理的源代码讲解其实现原理。动态代理的核心其实就是代理对象的生成,即Proxy.newProxyInstance(classLoader, proxyInterface, handler)。让我们进入newProxyInstance方法观摩下,核心代码其实就三行。
清单二:生成代理类
Java代码
- //获取代理类
- Class cl = getProxyClass(loader, interfaces);
- //获取带有InvocationHandler参数的构造方法
- Constructor cons = cl.getConstructor(constructorParams);
- //把handler传入构造方法生成实例
- return (Object) cons.newInstance(new Object[] { h });
其中getProxyClass(loader, interfaces)方法用于获取代理类,它主要做了三件事情:在当前类加载器的缓存里搜索是否有代理类,没有则生成代理类并缓存在本地JVM里。清单三:查找代理类。
Java代码
- // 缓存的key使用接口名称生成的List
- Object key = Arrays.asList(interfaceNames);
- synchronized (cache) {
- do {
- Object value = cache.get(key);
- // 缓存里保存了代理类的引用
- if (value instanceof Reference) {
- proxyClass = (Class) ((Reference) value).get();
- }
- if (proxyClass != null) {
- // 代理类已经存在则返回
- return proxyClass;
- } else if (value == pendingGenerationMarker) {
- // 如果代理类正在产生,则等待
- try {
- cache.wait();
- } catch (InterruptedException e) {
- }
- continue;
- } else {
- //没有代理类,则标记代理准备生成
- cache.put(key, pendingGenerationMarker);
- break;
- }
- } while (true);
- }
代理类的生成主要是以下这两行代码。 清单四:生成并加载代理类
Java代码
- //生成代理类的字节码文件并保存到硬盘中(默认不保存到硬盘)
- proxyClassFile = ProxyGenerator.generateProxyClass(proxyName, interfaces);
- //使用类加载器将字节码加载到内存中
- proxyClass = defineClass0(loader, proxyName,proxyClassFile, 0, proxyClassFile.length);
ProxyGenerator.generateProxyClass()方法属于sun.misc包下,Oracle并没有提供源代码,但是我们可以使用JD-GUI这样的反编译软件打开jre\lib\rt.jar来一探究竟,以下是其核心代码的分析。
清单五:代理类的生成过程
Java代码
- //添加接口中定义的方法,此时方法体为空
- for (int i = 0; i < this.interfaces.length; i++) {
- localObject1 = this.interfaces[i].getMethods();
- for (int k = 0; k < localObject1.length; k++) {
- addProxyMethod(localObject1[k], this.interfaces[i]);
- }
- }
- //添加一个带有InvocationHandler的构造方法
- MethodInfo localMethodInfo = new MethodInfo("<init>", "(Ljava/lang/reflect/InvocationHandler;)V", 1);
- //循环生成方法体代码(省略)
- //方法体里生成调用InvocationHandler的invoke方法代码。(此处有所省略)
- this.cp.getInterfaceMethodRef("InvocationHandler", "invoke", "Object; Method; Object;")
- //将生成的字节码,写入硬盘,前面有个if判断,默认情况下不保存到硬盘。
- localFileOutputStream = new FileOutputStream(ProxyGenerator.access$000(this.val$name) + ".class");
- localFileOutputStream.write(this.val$classFile);
那么通过以上分析,我们可以推出动态代理为我们生成了一个这样的代理类。把方法doSomeThing的方法体修改为调用LogInvocationHandler的invoke方法。
清单六:生成的代理类源码
Java代码
- public class ProxyBusiness implements IBusiness, IBusiness2 {
- private LogInvocationHandler h;
- @Override
- public void doSomeThing2() {
- try {
- Method m = (h.target).getClass().getMethod("doSomeThing", null);
- h.invoke(this, m, null);
- } catch (Throwable e) {
- // 异常处理(略)
- }
- }
- @Override
- public boolean doSomeThing() {
- try {
- Method m = (h.target).getClass().getMethod("doSomeThing2", null);
- return (Boolean) h.invoke(this, m, null);
- } catch (Throwable e) {
- // 异常处理(略)
- }
- return false;
- }
- public ProxyBusiness(LogInvocationHandler h) {
- this.h = h;
- }
- //测试用
- public static void main(String[] args) {
- //构建AOP的Advice
- LogInvocationHandler handler = new LogInvocationHandler(new Business());
- new ProxyBusiness(handler).doSomeThing();
- new ProxyBusiness(handler).doSomeThing2();
- }
- }
3.1.3 小结
从前两节的分析我们可以看出,动态代理在运行期通过接口动态生成代理类,这为其带来了一定的灵活性,但这个灵活性却带来了两个问题,第一代理类必须实现一个接口,如果没实现接口会抛出一个异常。第二性能影响,因为动态代理使用反射的机制实现的,首先反射肯定比直接调用要慢,经过测试大概每个代理类比静态代理多出10几毫秒的消耗。其次使用反射大量生成类文件可能引起Full GC造成性能影响,因为字节码文件加载后会存放在JVM运行时区的方法区(或者叫持久代)中,当方法区满的时候,会引起Full GC,所以当你大量使用动态代理时,可以将持久代设置大一些,减少Full GC次数。
静态代理是通过在代码中显式定义一个业务实现类一个代理,在代理类中对同名的业务方法进行包装,用户通过代理类调用被包装过的业务方法;
JDK动态代理是通过接口中的方法名,在动态生成的代理类中调用业务实现类的同名方法;
CGlib动态代理是通过继承业务类,生成的动态代理类是业务类的子类,通过重写业务方法进行代理;
动态代理的第一种实现:
1 有接口的情况,静态代理
AOP 针对有接口的实现方式
画图分析原理
package cn.itcast.demo3;
import org.junit.Test;
/*
* 目标是让目标对象和增强都可以切换!
*/
public class Demo3 {
@Test
public void fun1() {
ProxyFactory factory = new ProxyFactory();//创建工厂
factory.setTargetObject(new ManWaiter());//设置目标对象
factory.setBeforeAdvice(new BeforeAdvice() {//设置前置增强
public void before() {
System.out.println("您好不好!");
}
});
factory.setAfterAdvice(new AfterAdvice() {//设置后置增强
public void after() {
System.out.println("再见不见!");
}
});
Waiter waiter = (Waiter)factory.createProxy();
waiter.shouQian();
}
public void zhuanZhang() {
/*
* 1.
* 2.
* 3.
*/
}
}
package cn.itcast.demo3;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
/**
* 它用来生成代理对象
* 它需要所有的参数
* * 目标对象
* * 增强
* @author cxf
*/
/**
* 1. 创建代理工厂
* 2. 给工厂设置三样东西:
* * 目标对象:setTargetObject(xxx);
* * 前置增强:setBeforeAdvice(该接口的实现)
* * 后置增强:setAfterAdvice(该接口的实现)
* 3. 调用createProxy()得到代理对象
* * 执行代理对象方法时:
* > 执行BeforeAdvice的before()
* > 目标对象的目标方法
* > 执行AfterAdvice的after()
* @author cxf
*
*/
public class ProxyFactory {
private Object targetObject;//目标对象
private BeforeAdvice beforeAdvice;//前置增强
private AfterAdvice afterAdvice;//后置增强
/**
* 用来生成代理对象
* @return
*/
public Object createProxy() {
/*
* 1. 给出三大参数
*/
ClassLoader loader = this.getClass().getClassLoader();
Class[] interfaces = targetObject.getClass().getInterfaces();
InvocationHandler h = new InvocationHandler() {
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
/*
* 在调用代理对象的方法时会执行这里的内容
*/
// 执行前置增强
if(beforeAdvice != null) {
beforeAdvice.before();
}
Object result = method.invoke(targetObject, args);//执行目标对象的目标方法
// 执行后置增强
if(afterAdvice != null) {
afterAdvice.after();
}
// 返回目标对象的返回值
return result;
}
};
/*
* 2. 得到代理对象
*/
Object proxyObject = Proxy.newProxyInstance(loader, interfaces, h);
return proxyObject;
}
public Object getTargetObject() {
return targetObject;
}
public void setTargetObject(Object targetObject) {
this.targetObject = targetObject;
}
public BeforeAdvice getBeforeAdvice() {
return beforeAdvice;
}
public void setBeforeAdvice(BeforeAdvice beforeAdvice) {
this.beforeAdvice = beforeAdvice;
}
public AfterAdvice getAfterAdvice() {
return afterAdvice;
}
public void setAfterAdvice(AfterAdvice afterAdvice) {
this.afterAdvice = afterAdvice;
}
}
package cn.itcast.demo3;
// 服务员
public interface Waiter {
// 服务
public void serve();
public void shouQian();
}
package cn.itcast.demo3;
public class ManWaiter implements Waiter {
public void serve() {
System.out.println("服务中...");
}
public void shouQian() {
System.out.println("混蛋,给我钱!");
}
}
package cn.itcast.demo3;
public interface AfterAdvice {
public void after();
}
package cn.itcast.demo3;
/**
* 前置增强
* @author cxf
*
*/
public interface BeforeAdvice {
public void before();
}
AOP 针对没有接口的实现方式
动态代理的第二种实现——CGlib
cglib是针对类来实现代理的,原理是对指定的业务类生成一个子类,并覆盖其中业务方法实现代理。因为采用的是继承,所以不能对final修饰的类进行代理。
1:首先定义业务类,无需实现接口(当然,实现接口也可以,不影响的)
public class BookFacadeImpl1 {
public void addBook() {
System.out.println("新增图书...");
}
}
2:实现 MethodInterceptor方法代理接口,创建代理类
public class BookFacadeCglib implements MethodInterceptor {
private Object target;//业务类对象,供代理方法中进行真正的业务方法调用
//相当于JDK动态代理中的绑定
public Object getInstance(Object target) {
this.target = target; //给业务对象赋值
Enhancer enhancer = new Enhancer(); //创建加强器,用来创建动态代理类
enhancer.setSuperclass(this.target.getClass()); //为加强器指定要代理的业务类(即:为下面生成的代理类指定父类)
//设置回调:对于代理类上所有方法的调用,都会调用CallBack,而Callback则需要实现intercept()方法进行拦
enhancer.setCallback(this);
// 创建动态代理类对象并返回
return enhancer.create();
}
// 实现回调方法
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
System.out.println("预处理——————");
proxy.invokeSuper(obj, args); //调用业务类(父类中)的方法
System.out.println("调用后操作——————");
return null;
}
3:创建业务类和代理类对象,然后通过 代理类对象.getInstance(业务类对象) 返回一个动态代理类对象(它是业务类的子类,可以用业务类引用指向它)。最后通过动态代理类对象进行方法调用。
public static void main(String[] args) {
BookFacadeImpl1 bookFacade=new BookFacadeImpl1();
BookFacadeCglib cglib=new BookFacadeCglib();
BookFacadeImpl1 bookCglib=(BookFacadeImpl1)cglib.getInstance(bookFacade);
bookCglib.addBook();
}
AOP操作术语