【AOP 的实现之动态代理(一)】:jdk和cglib

【AOP 性能专题】 专栏收录该内容
8 篇文章 0 订阅

动态代理 
  Java在JDK1.3后引入的动态代理机制,使我们可以在运行期动态的创建代理类。使用动态代理实现AOP需要有四个角色:被代理的类,被代理类的接口,织入器,和InvocationHandler,而织入器使用接口反射机制生成一个代理类,然后在这个代理类中织入代码。被代理的类是AOP里所说的目标,InvocationHandler是切面,它包含了Advice和Pointcut。 


3.1.1 使用动态代理 
  那如何使用动态代理来实现AOP。下面的例子演示在方法执行前织入一段记录日志的代码,其中Business是代理类,LogInvocationHandler是记录日志的切面,IBusiness, IBusiness2是代理类的接口,Proxy.newProxyInstance是织入器。 
清单一:动态代理的演示

Java代码  收藏代码

  1. public static void main(String[] args) {   
  2.     //需要代理的接口,被代理类实现的多个接口都必须在这里定义   
  3.     Class[] proxyInterface = new Class[] { IBusiness.class, IBusiness2.class };   
  4.     //构建AOP的Advice,这里需要传入业务类的实例   
  5.     LogInvocationHandler handler = new LogInvocationHandler(new Business());   
  6.     //生成代理类的字节码加载器   
  7.     ClassLoader classLoader = DynamicProxyDemo.class.getClassLoader();   
  8.     //织入器,织入代码并生成代理类   
  9.     IBusiness2 proxyBusiness = (IBusiness2) Proxy.newProxyInstance(classLoader, proxyInterface, handler);   
  10.     //使用代理类的实例来调用方法。   
  11.     proxyBusiness.doSomeThing2();   
  12.     ((IBusiness) proxyBusiness).doSomeThing();   
  13. }   
  14.   
  15. /**  
  16. * 打印日志的切面  
  17. */   
  18. public static class LogInvocationHandler implements InvocationHandler {   
  19.   
  20.     private Object target; //目标对象   
  21.   
  22.     LogInvocationHandler(Object target) {   
  23.         this.target = target;   
  24.     }   
  25.   
  26.     @Override   
  27.     public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {   
  28.         //执行原有逻辑   
  29.         Object rev = method.invoke(target, args);   
  30.         //执行织入的日志,你可以控制哪些方法执行切入逻辑   
  31.         if (method.getName().equals("doSomeThing2")) {   
  32.             System.out.println("记录日志");   
  33.         }   
  34.         return rev;   
  35.     }   
  36. }   
  37.   
  38. 接口IBusiness和IBusiness2定义省略。   

 

   业务类,需要代理的类。

Java代码  收藏代码

  1. public class Business implements IBusiness, IBusiness2 {   
  2.   
  3.     @Override   
  4.     public boolean doSomeThing() {   
  5.         System.out.println("执行业务逻辑");   
  6.         return true;   
  7.     }   
  8.   
  9.     @Override   
  10.     public void doSomeThing2() {   
  11.         System.out.println("执行业务逻辑2");   
  12.     }   
  13.   
  14. }   

 

   输出

Java代码  收藏代码

  1. 执行业务逻辑2   
  2. 记录日志   
  3. 执行业务逻辑   

 

  可以看到“记录日志”的逻辑切入到Business类的doSomeThing方法前了。


 

3.1.2 动态代理原理 
    本节将结合动态代理的源代码讲解其实现原理。动态代理的核心其实就是代理对象的生成,即Proxy.newProxyInstance(classLoader, proxyInterface, handler)。让我们进入newProxyInstance方法观摩下,核心代码其实就三行。 
清单二:生成代理类

Java代码  收藏代码

  1. //获取代理类   
  2. Class cl = getProxyClass(loader, interfaces);   
  3. //获取带有InvocationHandler参数的构造方法   
  4. Constructor cons = cl.getConstructor(constructorParams);   
  5. //把handler传入构造方法生成实例   
  6. return (Object) cons.newInstance(new Object[] { h });     

 

    其中getProxyClass(loader, interfaces)方法用于获取代理类,它主要做了三件事情:在当前类加载器的缓存里搜索是否有代理类,没有则生成代理类并缓存在本地JVM里。清单三:查找代理类。

Java代码  收藏代码

  1.  // 缓存的key使用接口名称生成的List   
  2. Object key = Arrays.asList(interfaceNames);   
  3. synchronized (cache) {   
  4.     do {   
  5. Object value = cache.get(key);   
  6.          // 缓存里保存了代理类的引用   
  7. if (value instanceof Reference) {   
  8.     proxyClass = (Class) ((Reference) value).get();   
  9. }   
  10. if (proxyClass != null) {   
  11. // 代理类已经存在则返回   
  12.     return proxyClass;   
  13. } else if (value == pendingGenerationMarker) {   
  14.     // 如果代理类正在产生,则等待   
  15.     try {   
  16. cache.wait();   
  17.     } catch (InterruptedException e) {   
  18.     }   
  19.     continue;   
  20. } else {   
  21.     //没有代理类,则标记代理准备生成   
  22.     cache.put(key, pendingGenerationMarker);   
  23.     break;   
  24. }   
  25.     } while (true);   
  26. }   

  

代理类的生成主要是以下这两行代码。 清单四:生成并加载代理类

 

Java代码  收藏代码

  1. //生成代理类的字节码文件并保存到硬盘中(默认不保存到硬盘)   
  2. proxyClassFile = ProxyGenerator.generateProxyClass(proxyName, interfaces);   
  3. //使用类加载器将字节码加载到内存中   
  4. proxyClass = defineClass0(loader, proxyName,proxyClassFile, 0, proxyClassFile.length);   

 

  ProxyGenerator.generateProxyClass()方法属于sun.misc包下,Oracle并没有提供源代码,但是我们可以使用JD-GUI这样的反编译软件打开jre\lib\rt.jar来一探究竟,以下是其核心代码的分析。 
清单五:代理类的生成过程

Java代码  收藏代码

  1. //添加接口中定义的方法,此时方法体为空   
  2. for (int i = 0; i < this.interfaces.length; i++) {   
  3.   localObject1 = this.interfaces[i].getMethods();   
  4.   for (int k = 0; k < localObject1.length; k++) {   
  5.      addProxyMethod(localObject1[k], this.interfaces[i]);   
  6.   }   
  7. }   
  8.   
  9. //添加一个带有InvocationHandler的构造方法   
  10. MethodInfo localMethodInfo = new MethodInfo("<init>", "(Ljava/lang/reflect/InvocationHandler;)V", 1);   
  11.   
  12. //循环生成方法体代码(省略)   
  13. //方法体里生成调用InvocationHandler的invoke方法代码。(此处有所省略)   
  14. this.cp.getInterfaceMethodRef("InvocationHandler", "invoke", "Object; Method; Object;")   
  15.   
  16. //将生成的字节码,写入硬盘,前面有个if判断,默认情况下不保存到硬盘。   
  17. localFileOutputStream = new FileOutputStream(ProxyGenerator.access$000(this.val$name) + ".class");   
  18. localFileOutputStream.write(this.val$classFile);   

 

  那么通过以上分析,我们可以推出动态代理为我们生成了一个这样的代理类。把方法doSomeThing的方法体修改为调用LogInvocationHandler的invoke方法。 
清单六:生成的代理类源码

 

Java代码  收藏代码

  1. public class ProxyBusiness implements IBusiness, IBusiness2 {   
  2.   
  3. private LogInvocationHandler h;   
  4.   
  5. @Override   
  6. public void doSomeThing2() {   
  7.     try {   
  8.         Method m = (h.target).getClass().getMethod("doSomeThing", null);   
  9.         h.invoke(this, m, null);   
  10.     } catch (Throwable e) {   
  11.         // 异常处理(略)   
  12.     }   
  13. }   
  14.   
  15. @Override   
  16. public boolean doSomeThing() {   
  17.     try {   
  18.        Method m = (h.target).getClass().getMethod("doSomeThing2", null);   
  19.        return (Boolean) h.invoke(this, m, null);   
  20.     } catch (Throwable e) {   
  21.         // 异常处理(略)   
  22.     }   
  23.     return false;   
  24. }   
  25.   
  26. public ProxyBusiness(LogInvocationHandler h) {   
  27.     this.h = h;   
  28. }   
  29.   
  30. //测试用   
  31. public static void main(String[] args) {   
  32.     //构建AOP的Advice   
  33.     LogInvocationHandler handler = new LogInvocationHandler(new Business());   
  34.     new ProxyBusiness(handler).doSomeThing();   
  35.     new ProxyBusiness(handler).doSomeThing2();   
  36. }   
  37. }   

 

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操作术语

 

 

  • 0
    点赞
  • 0
    评论
  • 0
    收藏
  • 扫一扫,分享海报

参与评论 您还未登录,请先 登录 后发表或查看评论
©️2022 CSDN 皮肤主题:创作都市 设计师:CSDN官方博客 返回首页
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、C币套餐、付费专栏及课程。

余额充值