鲁春利的工作笔记,好记性不如烂笔头



面向方面的编程(AOP)是一种编程范式,旨在通过允许横切关注点的分离,提高模块化。

packagecom.invicme.apps.aop.proxy;importorg.apache.log4j.Logger;/****@authorlucl**数学计算实现类**/publicclassArithmeticCalculateImplimplementsArithmeticCalculate{privatestaticfinalLoggerlogger=Logger.getLogger(ArithmeticCalculateImpl.class);privateinti=0;privateintj=0;publicArithmeticCalculateImpl(){this(0,0);}publicArithmeticCalculateImpl(inti,intj){this.i=i;this.j=j;}@Overridepublicintadd(inti,intj){logger.info("Themethodaddwasinvokewithargs["+i+","+j+"]");intsum=i+j;logger.info("Themethodaddendswithresult["+sum+"]");returnsum;}@Overridepublicintdiv(inti,intj)throwsDivisorIsZeroException{logger.info("Themethoddivwasinvokewithargs["+i+","+j+"]");if(j==0){thrownewDivisorIsZeroException("除数不可为0");}intresult=i/j;logger.info("Themethoddivendswithresult["+result+"]");returnresult;}@OverridepublicStringvalidateNum(Stringlevel,inti){logger.info("ThemethodvalidateNumwasinvokewithargs["+level+","+i+"]");Stringresult=this.getMsg(i);logger.info("ThemethodvalidateNumendswithresult["+result+"]");returnresult;}privateStringgetMsg(inti){if(i>0){return"正数";}return"负数";}}

对于类ArithmeticCalculateImpl的日志通过在方法中加入logger.info来记录的,存在大量的冗余代码,特别是如果在项目开发阶段未通过这种方式记录日志,那么希望记录日志时需要修改代码加入日志逻辑(OOP开发模式)。


AOP为开发者提供一种进行横切关注点分离并织入的机制,把横切关注点分离,然后通过某种技术织入到系统中,从而无耦合的完成了我们的功能。


Aspect(切面):指横切性关注点的抽象即为切面,它与类相似,只是两者的关注点不一样,类是对物体特征的抽象,而切面横切性关注点的抽象.
Joinpoint(连接点):所谓连接点是指那些被拦截到的点。在Spring中,这些点指的是方法,因为spring只支持方法类型的连接点,实际上Joinpoint还可以是Field或类构造器。
Pointcut(切入点):所谓切入点是指我们要对哪些Joinpoint进行拦截的定义。
Advice(通知):所谓通知是指拦截到Joinpoint之后所要做的事情。通知分为前置通知,后置通知,异常通知,最终通知,环绕通知。
Target(目标对象):代理的目标对象。
Weave(织入):指将aspects应用到target对象并导致proxy对象创建的过程称为织入。
Introduction(引入):在不修改类代码的前提下, Introduction可以在运行期为类动态地添加一些方法或Field。


Spring提供了4种实现AOP的方式:

经典的基于代理的AOP

@AspectJ注解驱动的切面

纯POJO切面

注入式AspectJ切面


前三种都是Spring基于代理的AOP变体,因此Spring对AOP的支持局限于方法拦截。如果AOP需求超过了简单方法的拦截范畴(如构造器或属性拦截),那么应该考虑在AspectJ里实现切面,利用Spring的DI把Spring Bean注入到AspectJ切面中。


经典的基于代理的AOP:

Spring支持五种类型的通知:Before(前)org.apringframework.aop.MethodBeforeAdviceAfter-returning(返回后)org.springframework.aop.AfterReturningAdviceAfter-throwing(抛出后)org.springframework.aop.ThrowsAdviceArround(周围)org.aopaliance.intercept.MethodInterceptorIntroduction(引入)org.springframework.aop.IntroductionInterceptor


实现步骤:

1.创建通知:实现这几个接口,把其中的方法实现了
2.定义切点和通知者:在Spring配制文件中配置这些信息
3.使用ProxyFactoryBean来生成代理。


业务代码(接口):

packagecom.invicme.apps.aop.proxy;/****@authorlucl**数学计算接口类**/publicinterfaceArithmeticCalculate{publicintadd(inti,intj);publicintdiv(inti,intj)throwsDivisorIsZeroException;publicStringvalidateNum(Stringlevel,inti);}

业务代码(实现类):

packagecom.invicme.apps.aop.proxy;importorg.apache.log4j.Logger;/****@authorlucl**数学计算实现类**/publicclassArithmeticCalculateImplimplementsArithmeticCalculate{privatestaticfinalLoggerlogger=Logger.getLogger(ArithmeticCalculateImpl.class);privateinti=0;privateintj=0;publicArithmeticCalculateImpl(){this(0,0);}publicArithmeticCalculateImpl(inti,intj){this.i=i;this.j=j;}@Overridepublicintadd(inti,intj){logger.info("Themethodaddwasinvokewithargs["+i+","+j+"]");intsum=i+j;logger.info("Themethodaddendswithresult["+sum+"]");returnsum;}@Overridepublicintdiv(inti,intj)throwsDivisorIsZeroException{logger.info("Themethoddivwasinvokewithargs["+i+","+j+"]");if(j==0){thrownewDivisorIsZeroException("除数不可为0");}intresult=i/j;logger.info("Themethoddivendswithresult["+result+"]");returnresult;}@OverridepublicStringvalidateNum(Stringlevel,inti){logger.info("ThemethodvalidateNumwasinvokewithargs["+level+","+i+"]");Stringresult=this.getMsg(i);logger.info("ThemethodvalidateNumendswithresult["+result+"]");returnresult;}privateStringgetMsg(inti){if(i>0){return"正数";}return"负数";}}

希望切入的日志操作:

packagecom.invicme.apps.aop.proxy;importjava.lang.reflect.Method;importjava.util.Arrays;importorg.apache.log4j.Logger;importorg.springframework.aop.AfterReturningAdvice;importorg.springframework.aop.MethodBeforeAdvice;/****@authorlucl**/publicclassLogAdapterimplementsMethodBeforeAdvice,AfterReturningAdvice{privatestaticfinalLoggerlogger=Logger.getLogger(LogAdapter.class);/***目标对象的方法执行之前打印日志*/@Overridepublicvoidbefore(Methodmethod,Object[]args,Objecttarget)throwsThrowable{logger.info(target.getClass().getName()+"@"+method.getName()+"withargs"+Arrays.asList(args)+"invoke.");}/***目标对象的方法执行之后打印日志*/@OverridepublicvoidafterReturning(ObjectreturnValue,Methodmethod,Object[]args,Objecttarget)throwsThrowable{logger.info(target.getClass().getName()+"@"+method.getName()+"endswithresult"+returnValue+".");}}

Spring配置文件:

<?xmlversion="1.0"encoding="UTF-8"?><beansxmlns="http://www.springframework.org/schema/beans"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xmlns:context="http://www.springframework.org/schema/context"xsi:schemaLocation="http://www.springframework.org/schema/beanshttp://www.springframework.org/schema/beans/spring-beans.xsdhttp://www.springframework.org/schema/contexthttp://www.springframework.org/schema/context/spring-context.xsd"><!--业务类--><beanid="calculate"class="com.invicme.apps.aop.proxy.ArithmeticCalculateImpl"/><!--切入的日志类--><beanid="logAdapter"class="com.invicme.apps.aop.proxy.LogAdapter"/><!--定义正则表达式切点--><beanid="loggerPointcut"class="org.springframework.aop.support.JdkRegexpMethodPointcut"><!--单个方法--><!--<propertyname="pattern"value=".*add"/>--><propertyname="patterns"><list><value>.*add</value><value>.*iv</value><value>.validate*</value></list></property></bean><beanid="loggerAdapterAdvisor"class="org.springframework.aop.support.DefaultPointcutAdvisor"><propertyname="advice"ref="logAdapter"/><propertyname="pointcut"ref="loggerPointcut"/></bean><beanid="proxyFactoryBean"class="org.springframework.aop.framework.ProxyFactoryBean"><propertyname="target"ref="calculate"/><propertyname="interceptorNames"value="loggerAdapterAdvisor"/><propertyname="proxyInterfaces"value="com.invicme.apps.aop.proxy.ArithmeticCalculate"/></bean></beans>

Junit单元测试类:

packagecom.test.apps.spring.aop;importorg.junit.Test;importorg.springframework.context.ApplicationContext;importorg.springframework.context.support.ClassPathXmlApplicationContext;importcom.invicme.apps.aop.proxy.ArithmeticCalculate;importcom.invicme.apps.aop.proxy.DivisorIsZeroException;/****@authorlucl**/publicclassTestSpringProxyFactoryBean{@TestpublicvoidtestProxyFactoryBean(){ApplicationContextcontext=newClassPathXmlApplicationContext("classpath:spring/spring-context-aop.xml");ArithmeticCalculatecalculate=context.getBean("proxyFactoryBean",ArithmeticCalculate.class);calculate.add(1,2);System.out.println("----------------------------------------------------------------");try{calculate.div(12,3);}catch(DivisorIsZeroExceptione){e.printStackTrace();}}}

输出结果:

说明:

ProxyFactoryBean是一个代理,我们可以把它转换为proxyInterfaces中指定的实现该interface的代理对象。

通过代理模式可以获取到业务日志中希望输出的日志内容。


OK!这是我们想要的结果,但是上面这个过程貌似有点复杂,尤其是配置切点跟通知,Spring提供了一种自动代理的功能,能让切点跟通知自动进行匹配,修改配置文件如下:

<?xmlversion="1.0"encoding="UTF-8"?><beansxmlns="http://www.springframework.org/schema/beans"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xmlns:context="http://www.springframework.org/schema/context"xsi:schemaLocation="http://www.springframework.org/schema/beanshttp://www.springframework.org/schema/beans/spring-beans.xsdhttp://www.springframework.org/schema/contexthttp://www.springframework.org/schema/context/spring-context.xsd"><!--业务类--><beanid="calculate"class="com.invicme.apps.aop.proxy.ArithmeticCalculateImpl"/><!--切入的日志类--><beanid="logAdapter"class="com.invicme.apps.aop.proxy.LogAdapter"/><!--定义正则表达式切点--><beanid="loggerPointcut"class="org.springframework.aop.support.JdkRegexpMethodPointcut"><!--单个方法--><!--<propertyname="pattern"value=".*add"/>--><propertyname="patterns"><list><value>.*add</value><value>.*iv</value><value>.validate*</value></list></property></bean><beanid="loggerAdapterAdvisor"class="org.springframework.aop.support.DefaultPointcutAdvisor"><propertyname="advice"ref="logAdapter"/><propertyname="pointcut"ref="loggerPointcut"/></bean><beanclass="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator"/><!--<beanid="proxyFactoryBean"class="org.springframework.aop.framework.ProxyFactoryBean"><propertyname="target"ref="calculate"/><propertyname="interceptorNames"value="loggerAdapterAdvisor"/><propertyname="proxyInterfaces"value="com.invicme.apps.aop.proxy.ArithmeticCalculate"/></bean>--></beans>


执行程序:

packagecom.test.apps.spring.aop;importorg.junit.Test;importorg.springframework.context.ApplicationContext;importorg.springframework.context.support.ClassPathXmlApplicationContext;importcom.invicme.apps.aop.proxy.ArithmeticCalculate;importcom.invicme.apps.aop.proxy.DivisorIsZeroException;/****@authorlucl**/publicclassTestSpringProxyFactoryBean{@TestpublicvoidtestProxyFactoryBean(){ApplicationContextcontext=newClassPathXmlApplicationContext("classpath:spring/spring-context-aop.xml");/*ArithmeticCalculatecalculate=context.getBean("proxyFactoryBean",ArithmeticCalculate.class);*/ArithmeticCalculatecalculate=context.getBean("calculate",ArithmeticCalculate.class);calculate.add(1,2);System.out.println("----------------------------------------------------------------");try{calculate.div(12,3);}catch(DivisorIsZeroExceptione){e.printStackTrace();}}}


说明:org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator能够为方法匹配的bean自动创建代理!