前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Spring AOP 介绍与应用

Spring AOP 介绍与应用

作者头像
actionzhang
发布2022-11-30 17:07:46
2770
发布2022-11-30 17:07:46
举报

        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的配置,你可以发现其实都是一样的,只不过用的切点类型可能不同,增强不同等等,文章就到这里了,祝大家学习愉快!

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2016-11-20,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
相关产品与服务
云顾问
云顾问(Tencent Cloud Smart Advisor)是一款提供可视化云架构IDE和多个ITOM领域垂直应用的云上治理平台,以“一个平台,多个应用”为产品理念,依托腾讯云海量运维专家经验,助您打造卓越架构,实现便捷、灵活的一站式云上治理。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档