码迷,mamicode.com
首页 > 其他好文 > 详细

个人对AOP概念的理解

时间:2016-05-12 23:52:01      阅读:249      评论:0      收藏:0      [点我收藏+]

标签:

一、什么是AOP

AOP 是Aspect Oriented Programing 的简称,被译为“面向方面编程”。相信看到这个术语,刚接触的人肯定是很难理解的。下面个人就按照自己的理解将其解释下,如果有什么不妥的地方,还请指出~


一般情况下,如果我们的代码出现了很多重复的,比如在 Pig、Horse、Cat 等类中,它们都拥有共同的方法 eat(),run(), 按照软件重构的思想理念,我们可以将这些方法写到一个父类 Animal中,并将Pig、Horse 和 Cat 等继承它。这样确实减少了很多重复性的代码,但是有时情况并非如此简单。如下面的代码。

Service接口:

package com.zkp.service;

public interface FormService {
	public void removeTopic(int topicId);
	
	public void removeMessage(int messageId);
}


Service 实现:

package com.zkp.service.impl;

import com.zkp.service.FormService;

public class FormServiceImpl implements FormService {
        private Monitor monitor;
	public void removeTopic(int topicId){
                monitor.start();
		System.out.println("模拟删除topic记录");
		
		try{
			Thread.currentThread().sleep(10);
		}catch(Exception e){
			throw new RuntimeException(e);
		}
                monitor.end();
	}
	
	public void removeMessage(int recordId){
		monitor.start();
                System.out.println("模拟删除消息记录");
		
		try{
			Thread.currentThread().sleep(10);
		}catch(Exception e){
			throw new RuntimeException(e);
		}
                monitor.end();
	}
        //省略各种初始化和set、get等方法
        ...
}
对上面这种情况,每个列出的方法里面都包含了启动和结束监听器monitor的代码,假设我们这个monitor是用来监视一个函数运行时的性能的。对于这种情况,我们没法采取之前以继承父类的方式去消除这些重复代码。因此便有了AOP,将这些重复的代码以横向切割的方式抽取出来,放到一个独立的模块中,让原本的业务类里面只含有业务代码。


二、AOP术语

1、连接点(Joinpoint)

在程序执行到某个特定的位置,如一个函数执行前、执行后,或者是某个类初始化前和初始化后等等,这些地方都可以叫做连接点。连接点就是AOP把横切代码植入的地方。它由两个信息确定,一个是用方法表示的程序执行点,即确定到某个方法上。二是用相对点表示的方位,能够确定到方法内的某个地方。

2、切点(Pointcut)

每个程序类都拥有一些方法,这些方法都有多个连接点。但是我们如果要找到特定的感兴趣的连接点,就要通过“切点”来定位,而且需要注意的是,一个切点可以定位多个感兴趣的连接点。在Spring中,切点是通过 org.springframework.aop.Pointcut 接口进行描述的,它使用类和方法作为连接点的查找条件,因此切点只能定位到某个方法上。

3、增强(Advice)

增强就是我们常说的植入到目标类连接点上的一段横切代码。不过,它除了拥有这段代码逻辑的属性外,也包含了植入横切代码的方位信息。结合切点信息和方位信息,一段横切代码便能被准确地植入到某些特定的位置中了。由于增强带有方位信息,因此在 spring 中,它所包含的增强接口都是带方位名的,如AfterRetuningAdvice、ThrowsAdvice和BeforeAdvice等。

4、目标类(Target)

很容易理解,目标类就是我们要植入横切代码的目标类。在AOP的帮助下,我们可以将刚刚的 FormServiceImpl 类中有关 monitor 的代码抽取出来,让AOP动态地植入到相关的一些连接点中。


三、AOP实现

利用AOP思想向目标类植入横切代码时,个人理解是:它其实是生成了一个新的类,在这个新类的被增强的方法中,就包含了业务代码和横切代码,它也是我们常说的代理。虽然我们在spring中还是向往常一样去调用自己写的方法,但是对于被植入了横切代码的类,我们实质上是调用了它的一个代理,然后执行了横切逻辑和相关的业务代码。AOP底层也用到了Java的反射技术,它的实现有以下两种,

1、基于JDK动态代理

JDK动态代理主要涉及到java.lang.reflect包中的两个类,Proxy和InvocationHandler。其中InvocationHandler是个接口,可以通过实现此接口来定义横切逻辑,并通过反射调用目标类代码,动态地将横切逻辑和业务逻辑编织在一起。其中Handler代码如下所示。在invoke方法中,proxy是最终生成的代理实例,一般不会用到,method是被代理目标实例的某个具体方法,通过它可以发起对目标实例方法的反射调用;args是传递给代理实例某个方法的入参数组,在方法反射时调用:

package com.zkp.base;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;

public class PerformanceHandler implements InvocationHandler{
	
	private Object object;
        private Monitor monitor;
	
	/**
	 * 	object作为目标业务类
	 * */
	public PerformanceHandler(Object object){
		this.object = object;
	}

	@Override
	public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
		monitor.start(); // 性能监视代码
		Object obj = method.invoke(object, args);
		monitor.end(); // 性能监视代码
		return obj;
	}
        ...
}

而在具体业务类中,再也不需要包含性能监视代码,直接去掉横切代码即可,修改后的FormServiceImpl类如下所示:

package com.zkp.service.impl;

import com.zkp.service.FormService;

public class FormServiceImpl implements FormService {
	public void removeTopic(int topicId){
		System.out.println("模拟删除topic记录");
		
		try{
			Thread.currentThread().sleep(10);
		}catch(Exception e){
			throw new RuntimeException(e);
		}
	}
	
	public void removeMessage(int recordId){
                System.out.println("模拟删除消息记录");
		
		try{
			Thread.currentThread().sleep(10);
		}catch(Exception e){
			throw new RuntimeException(e);
		}
	}
}

接下来可以写个测试类调用:

package com.zkp.test;

import java.lang.reflect.Proxy;
import com.zkp.base.PerformanceHandler;
import com.zkp.service.FormService;
import com.zkp.service.impl.FormServiceImpl;

public class TestFormService {
	public static void main(String[] args){
		FormService formServiceImpl = new FormServiceImpl(); // 希望被代理的目标业务类
		PerformanceHandler handler = new PerformanceHandler(formServiceImpl); // 将性能监视代码编织到该业务类中
		// 利用newInstance()静态方法为handler船检一个符合FormService接口的代理实例
		FormService proxy = (FormService) Proxy.newProxyInstance(formServiceImpl.getClass().getClassLoader(), 
				formServiceImpl.getClass().getInterfaces(), handler); 
		
		proxy.removeTopic(100);
		proxy.removeMessage(1000);
	}
}
由上述实现可以看出,JDK代理只能为接口创建代理实例,因为newProxyInstance()的第二个参数就是业务类的接口。


2、基于CGLib动态代理

对于没有通过接口定义业务方法的类,JDK无法搞定,因此CGLib就是一个替代者。CGLib采用非常底层的字节码技术,为一个类创建子类,并在自类中采用方法拦截的技术拦截所有父类方法的调用,并顺势织入横切逻辑。它实现了 MethodInterceptor类:

package com.zkp.base;

import java.lang.reflect.Method;

import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;

public class CGLibProxy  implements MethodInterceptor {
	
	private Enhancer enhancer = new Enhancer();
        private Monitor monitor;
	
	public Object getProxy( Class superClass ){
		enhancer.setSuperclass(superClass); // 设置需要创建子类的类
		enhancer.setCallback(this); 
		return enhancer.create(); // 通过字节码技术动态创建子类实例
	}
	
	@Override
	public Object intercept(Object arg0, Method method, Object[] arg2, MethodProxy proxy) throws Throwable {
		monitor.start();
		Object obj = proxy.invokeSuper(arg0, arg2);  // 通过代理类调用父类中方法
		monitor.end();
		return obj;
	}
        ...
}
在上面的代码中,arg0为目标类实例,method为目标类方法的反射对象,arg2为动态入参,proxy为生成的代理类实例。注意到由于CGLib底层利用到了ASM,在引入cglib包的时候,我们也需要引入asm.jar包。

接下来利用CGLib为FormServiceImpl类创建代理对象,并测试代理对象方法,代码如下:

package com.zkp.test;

import com.zkp.base.CGLibProxy;
import com.zkp.service.impl.FormServiceImpl;

public class TestCGLibProxy {
	public static void main(String[] args){
		CGLibProxy proxy = new CGLibProxy();
		FormServiceImpl formServiceImpl = (FormServiceImpl) proxy.getProxy(FormServiceImpl.class);
		
		formServiceImpl.removeMessage(10000);
		formServiceImpl.removeTopic(3000);
	}
}

利用AOP织入横切代码后Test类执行结果如下所示:

技术分享


不过以上还有以下问题:

1、目标类所有方法都被织入了横切代码,而有时我们只需要对目标类的部分方法进行动态织入;

2、通过硬编码的方式指定了横切代码的织入点,即在一个方法的开始和结束织入横切逻辑;

3、手工编写创建代理的过程,对每个接口或者实现类都需要各自编写代码,没有做到通用。

四、JDK和CGLib比较

除了JDK只能代理接口而CGLib可以代理具体实现类的差别外,CGLib创建的代理对象在性能上会比JDK创建的高不少,但是CGLib在创建代理对象的时候花费的时间却比JDK多很多。因此,对于singleton的代理对象或者是具有实现池时,我们一般选用CGLib。而当需要频繁地创建对象时,我们使用JDK动态代理技术。此外,由于CGLib采用动态创建子类的方式生成代理对象,因此没法对目标类的final、private方法进行代理

个人对AOP概念的理解

标签:

原文地址:http://blog.csdn.net/zkp0601/article/details/51345750

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!