Spring AOP 介绍与应用

2022-11-30 17:07:46 浏览数 (1)

        Spring的AOP想必大家都是比较清楚的,从spring 3.x版本出现之后,AOP的概念更加清晰,使用也更加方便。我看过很多书,讲解spring的aop,里面都有太多的概念,看到最后,还是不懂,有些云里雾里的,但是在使用了这么长时间以来,我觉得有些书上讲的太过繁琐了,或者说一下讲的太深入,太抽象让人难以理解。下面我会尽自己的可能让大家弄懂什么是AOP,并解释一个实际当中的应用实例,让大家真正理解AOP的灵活,有什么错误的地方欢迎大家私信(评论)我。

       AOP中有几个重要的概念,但是我认为只要弄懂两个概念,就能明白AOP的基本原理。下面我分别阐述一下这两个概念并结合实例。

       1.Advice

        Advice在国内有很多的翻译,我喜欢把它称作“增强”,顾名思义它的作用,就是把已有代码的功能增强,值得注意的是,增强可以对类增强,可以对方法增强,而spring目前只提供方法级的增强。分别是前置增强,后置增强,环绕增强以及引介增强,除了引介增强(不常用)以外我分别以一个小的实例帮大家理解这些增强。

前置增强

        在spring中提供一个接口MethodBeforeAdive,它继承了Advice,提供前置增强标准,我如果想实现在某个方法前的增强,可以实现该接口,下面我提供一个小的实例帮大家理解。比如我们在开发当中经常要记录接口的调用日志,什么时间哪个接口别调用了,理论上讲这些日志可以用同一段代码实现,我有一个类A,包含两个方法,doValidate和doBusiness,一个为了做校验,一个为了做业务,两个方法都要记录调用日志,我们的代码实现可以如下:

代码语言:javascript复制
public class TestBoke {

	public static class A{
		public void doValidate(){
			System.out.println("A do validate");
		}
		
		public void doBusiness(){
			System.out.println("A do business");
		}
	}
	
	public static class MyBeforeAdvice implements MethodBeforeAdvice{
		@Override
		public void before(Method method, Object[] args, Object target)
				throws Throwable {
			String className = target.getClass().getSimpleName();
			String methodName = method.getName();
		    Date date = new Date(System.currentTimeMillis());
			System.out.println("类   " className  " 方法  " methodName  "  调用日期:" date);	
		}
	}
	
	public static void main(String[] args) {
		A target = new A();
		MyBeforeAdvice beforeAdive  = new MyBeforeAdvice();
		ProxyFactory proxyFactory = new ProxyFactory();
		proxyFactory.setTarget(target);
		proxyFactory.setProxyTargetClass(true);
		proxyFactory.addAdvice(beforeAdive);
		A targetProxy = (A)proxyFactory.getProxy();
		targetProxy.doValidate();
		targetProxy.doBusiness();
	}
}

代码很简单,MyBeforeAdive实现MethodBeforeAdvice,实现方法前置增强,在main方法中,用到ProxyFactory,这是spring提供的生成AOP代理的工场类,只要设定目标代理对象(setTarget),增加增强(addAdvice),设定代理生成方式(setProxyTargetClass),再调用getProxy就可以获得增强过的代理对象,值得说明的是,将setProxyTargetClass的值设定为true,就是以cglib动态代理方式生成代理类,淡然设置为false,就是默认用JDK动态代理技术,这里不再详述,如果想了解这两种代理方式的区别可以参考,spring的其他资料。代码运行结果如下图:

可以看出,在doValidate,和doBusiness方法调用前,均记录了相应的调用日志,这样前置增强就讲解完了,那有人要问了,如果要将方法的返回值也记录下来,该怎么办,在方法前增强是不能够获取方法的返回值的,是的你猜对了,我们可以使用后置增强。下面继续讲解后置增强。

后置增强

        在spring中提供一个后置增强的接口AfterReturningAdvice,它提供后置增强的标准,如果我们想要实现后置增强,那么需要实现该接口,下面我们就上面的例子做一个修改增加一个后置增强,输出每个方法的返回值。代码如下:

代码语言:javascript复制
public class TestBoke {

	public static class A{
		public String doValidate(){
			System.out.println("A do validate");
		    return "doValidateReturnValue";
		}
		
		public String doBusiness(){
			System.out.println("A do business");
		    return "doBusinessReturnValue";
		}
	}
	
	public static class MyBeforeAdvice implements MethodBeforeAdvice{
		@Override
		public void before(Method method, Object[] args, Object target)
				throws Throwable {
			String className = target.getClass().getSimpleName();
			String methodName = method.getName();
		    Date date = new Date(System.currentTimeMillis());
			System.out.println("类   " className  " 方法  " methodName  "  调用日期:" date);	
		}
	}
	
	public static class MyAfterAdivce implements AfterReturningAdvice{

		@Override
		public void afterReturning(Object returnValue, Method method, Object[] args,
				Object target) throws Throwable {
			System.out.println("方法  " method.getName()  "  返回值为:" returnValue);
		}
		
	}
	
	public static void main(String[] args) {
		A target = new A();
		MyBeforeAdvice beforeAdive  = new MyBeforeAdvice();
		MyAfterAdivce  afterAdvice = new MyAfterAdivce();
		ProxyFactory proxyFactory = new ProxyFactory();
		proxyFactory.setTarget(target);
		proxyFactory.setProxyTargetClass(true);
		proxyFactory.addAdvice(0,beforeAdive);
		proxyFactory.addAdvice(1, afterAdvice);
		A targetProxy = (A)proxyFactory.getProxy();
		targetProxy.doValidate();
		System.out.println();
		targetProxy.doBusiness();
	}
}

如上代码我实现AfterReturning接口,完成一个后置增强的实现,并打印出一个方法的返回值,值得注意的是,因为目标target已经有了一个增强,所以再新增增强的话,需要把增强的顺序标明,那么要调用addAdive(pos,advice)来替换addAdive(advice),如上代码,前置增强的顺序为0,后置代码的顺序为1,意思为先进行前置增强,后进行后置增强。代码运行结果如下,打印出了方法的返回值:

这时有人还有疑问,如果要记录一个方法的运行时间,前置方法与后置方法又不能通信,怎么记录方法的运行时间呢,你可能猜到我的思路了,这时就该用到环绕增强了。

环绕增强:

         spring中虽然有环绕增强的概念,但是并没有自己实现环绕增强的接口,而是引用AOP联盟推出的环绕增强接口,一会代码中你会发现,环绕增强的命名,参数列,以及很多地方,都是和spring的advice风格不同的,AOP联盟定义环绕增强的接口是MethodInterceptor,是不是奇怪为什么不叫methedAdvice而叫MethodInterceptor,方法拦截器,这里先卖个关子,下面贴出代码你就全明白了,下面还是在上面的代码基础上,增加统计方法运行时间的环绕增强,代码如下:

代码语言:javascript复制
public class TestBoke {

	public static class A{
		public String doValidate() throws InterruptedException{
			System.out.println("A do validate");
			Thread.sleep(100);
		    return "doValidateReturnValue";
		}
		
		public String doBusiness() throws InterruptedException{
			System.out.println("A do business");
			Thread.sleep(200);
		    return "doBusinessReturnValue";
		}
	}
	
	public static class MyBeforeAdvice implements MethodBeforeAdvice{
		@Override
		public void before(Method method, Object[] args, Object target)
				throws Throwable {
			String className = target.getClass().getSimpleName();
			String methodName = method.getName();
		    Date date = new Date(System.currentTimeMillis());
			System.out.println("类   " className  " 方法  " methodName  "  调用日期:" date);	
		}
	}
	
	public static class MyAfterAdivce implements AfterReturningAdvice{

		@Override
		public void afterReturning(Object returnValue, Method method, Object[] args,
				Object target) throws Throwable {
			System.out.println("方法  " method.getName()  "  返回值为:" returnValue);
		}
		
	}
	
	public static class MyMethodInterceptor implements MethodInterceptor{

		@Override
		public Object invoke(MethodInvocation invocation) throws Throwable {
			long startTime = System.currentTimeMillis();
			Object result = invocation.proceed();
			long endTime = System.currentTimeMillis();
			long duringTime = endTime - startTime;
			System.out.println("方法  " invocation.getMethod().getName()   "  运行时间为  " duringTime  " 毫秒");
			return result;
		}
		
	}
	
	public static void main(String[] args) throws InterruptedException {
		A target = new A();
		MyBeforeAdvice beforeAdive  = new MyBeforeAdvice();
		MyAfterAdivce  afterAdvice = new MyAfterAdivce();
		MyMethodInterceptor methodInterceptor = new MyMethodInterceptor();
		ProxyFactory proxyFactory = new ProxyFactory();
		proxyFactory.setTarget(target);
		proxyFactory.setProxyTargetClass(true);
		proxyFactory.addAdvice(0, methodInterceptor);
		proxyFactory.addAdvice(1, afterAdvice);
		proxyFactory.addAdvice(2,beforeAdive);
		A targetProxy = (A)proxyFactory.getProxy();
		targetProxy.doValidate();
		System.out.println();
		targetProxy.doBusiness();
	}
}

如上代码,我增加了环绕增强MyMethodInterceptor,并且为了使结果更加明显,我再A的两个方法中各自sleep了一段时间,为了使方法运行时间变长,通过看代码大家应该知道为什么叫做interceptor了吧,因为该方法想要进行,需要调用invocation的preceed方法才可以执行,所以环绕增强是可以控制目标方法执行与否的,所以将其命名为方法拦截器就更为贴切一些,下面是运行结果:

看到这个运行结果,大家应该彻底理解了增强(advice)到底是干什么的了吧,当然可能有更加细心的人会问,增强是不是和AOP的经典实例声明式事务相比还差些什么啊,因为增强理解后,我们可以认为事务的代码应该是在增强内实现的,但是怎么实现,部分类的部分方法有事务增强,其他则不需要呢,那么这个就是我要讲述的第二个重要的概念,pointCut切入点。

2.pointCut

        第二个重要的概念,就是切入点(pointCut),它可以与增强结合,可以定义你的增强,对哪些类的那些方法生效,这样AOP的概念才能完整,实现灵活增强已有代码,当然spring中有很多类型的切入点,但是由于篇幅与时间的原因我不能一一写出实现代码,请大家谅解,下面我仅对增强与切入点的联合使用给出一个实例,该实例不是详尽介绍pointCut,而是帮助大家进一步了解,切入点的重要性与作用。

        在给出代码之前,需要补充一点,在AOP中,增强和切入点合并起来称为切面,这虽然是一个新的概念,但是我认为这不应该是理解AOP的障碍,只要理解了增强和切入点的概念,那么切面就是两个概念的联合使用,实例代码还是延续上面的实例,因为dovalidate方法不是那么重要,所以不需要对dovalidate增强,只对doBusiness增强,这里我们用一个增强和切入点结合的实例,NameMatchMethodPointcutAdvisor 这个是spring中提供的切面,该切入点,主要定义匹配方法名的切点,意思就是这个前面可以把增强对应到匹配的所有方法上面,下面给出代码:

代码语言:javascript复制
			Thread.sleep(200);
		    return "doBusinessReturnValue";
		}
	}
	
	public static class MyBeforeAdvice implements MethodBeforeAdvice{
		@Override
		public void before(Method method, Object[] args, Object target)
				throws Throwable {
			String className = target.getClass().getSimpleName();
			String methodName = method.getName();
		    Date date = new Date(System.currentTimeMillis());
			System.out.println("类   " className  " 方法  " methodName  "  调用日期:" date);	
		}
	}
	
	public static class MyAfterAdivce implements AfterReturningAdvice{

		@Override
		public void afterReturning(Object returnValue, Method method, Object[] args,
				Object target) throws Throwable {
			System.out.println("方法  " method.getName()  "  返回值为:" returnValue);
		}
		
	}
	
	public static class MyMethodInterceptor implements MethodInterceptor{

		@Override
		public Object invoke(MethodInvocation invocation) throws Throwable {
			long startTime = System.currentTimeMillis();
			Object result = invocation.proceed();
			long endTime = System.currentTimeMillis();
			long duringTime = endTime - startTime;
			System.out.println("方法  " invocation.getMethod().getName()   "  运行时间为  " duringTime  " 毫秒");
			return result;
		}
		
	}
	
	public static void main(String[] args) throws InterruptedException {
		A target = new A();
		MyBeforeAdvice beforeAdive  = new MyBeforeAdvice();
		NameMatchMethodPointcutAdvisor beforeMethodadvisor = new NameMatchMethodPointcutAdvisor();
		beforeMethodadvisor.setAdvice(beforeAdive);
		beforeMethodadvisor.addMethodName("doBusiness");
		MyAfterAdivce  afterAdvice = new MyAfterAdivce();
		NameMatchMethodPointcutAdvisor afterMethodAdvisor = new NameMatchMethodPointcutAdvisor();
		afterMethodAdvisor.setAdvice(afterAdvice);
		afterMethodAdvisor.addMethodName("doBusiness");
		MyMethodInterceptor methodInterceptor = new MyMethodInterceptor();
		NameMatchMethodPointcutAdvisor methodInterceptorAdvisor = new NameMatchMethodPointcutAdvisor();
		methodInterceptorAdvisor.setAdvice(methodInterceptor);
		methodInterceptorAdvisor.addMethodName("doBusiness");
		ProxyFactory proxyFactory = new ProxyFactory();
		proxyFactory.setTarget(target);
		proxyFactory.setProxyTargetClass(true);
		proxyFactory.addAdvisor(0, methodInterceptorAdvisor);
		proxyFactory.addAdvisor(1, afterMethodAdvisor);
		proxyFactory.addAdvisor(2, beforeMethodadvisor);
//		proxyFactory.addAdvice(0, methodInterceptor);
//		proxyFactory.addAdvice(1, afterAdvice);
//		proxyFactory.addAdvice(2,beforeAdive);
    	A targetProxy = (A)proxyFactory.getProxy();
		targetProxy.doValidate();
		System.out.println();
		targetProxy.doBusiness();
	}
}

如上代码,我修改了main方法,首先将advice转化为advisor,代码中NameMatchMethodePointcutAdvisor,有setAdvice方法,和addMethodName的方法,setAdvice意思就是和已经有的增强联系起来,而addMethodName就是匹配方法名字的字符串,我们将所有的advice均匹配doBusiness,这样就将Advice转换为Advisor了,还有值得说明的是,代码中addAdvice(pos,advice)方法已经被 注释掉了,换成了addAdvisor(pos,advisor),这样就可以实现只对doBusiness方法增强。如下是运行结果:

好了,虽然是一个简单的实例,但是却阐明了AOP的概念与作用,希望大家已经理解了AOP,需要补充一点的是,我们使用的ProxyFactory,对应的FactoryBean是ProxyFactoryBean,通过该FactoryBean可以将,生成代理、切面植入、增强植入、植入顺序维护在配置文件当中,为了代码演示简单,我没有使用FactoryBean,如果使用ProxyFactoryBean,对比声明式事务和FactoryBean的配置,你可以发现其实都是一样的,只不过用的切点类型可能不同,增强不同等等,文章就到这里了,祝大家学习愉快!

0 人点赞