第二十八天 月出惊山鸟 —Spring的AOP

来源:互联网 发布:java base64编解码 编辑:程序博客网 时间:2024/05/17 07:29

              6月13日,阴转细雨。“人闲桂花落,夜静春山空。月出惊山鸟,时鸣春涧中。”  

             不管在面向过程还是在面向对象里,神奇的“纯”字,似乎永远都充满了无限的可能性。除了函数之所调用、类之所封装,在程序员文化里,对于“纯粹”的感知和定义,既起自于代码,又超越了代码。也就是说,能够真真切切地感觉到纯净的,不仅是我们的每一个Bean和每一个Class,还包括每个Coder的心。

              然而,客户的需求是千变万化和千奇百怪的,Spring在为Coder在应对和处理各自不同的要求时,提供了一种特殊的解决方式-AOP。在OOP和AOP里,最佳存在方式,并不是让其中有某一个显得格外突出,而是OOP和AOP的调和以及平衡,不仅是Spring不断寻求的完美状态,也是每个Coder所追求的理想境界。编程语言最终极的目标就是能以更自然,更灵活的方式模拟世界,从原始机器语言到过程语言再到面向对象的语言,编程语言一步步地用更自然,更强大的方式描述软件。

             AOPAspect Oriented Programing 的简称,即面向切面编程虽然AOP作为一项编程技术已经有多年的历史,但一直长时间停顿在学术领域,直到近几年,AOP才作为一项真正的实用技术在应用领域开疆拓土。AOP是软件开发饲养发展到一定阶段的产物,但Aop的出现并不是要完全代替OOP,而仅仅是作为OOP的有益补充。需要指出的是AOP的应用场合是受限的,它一般只适合用于那些具有横切逻辑的应用场合:如性能监测,访问控制,事物管理以及日志记录(虽然很多讲解日志记录的例子用于AOP的讲解,但很多人认为很难用AOP编写实用日志。)不过,这丝毫不影响AOP作为一种新的软件开发思想在软件开发领域所占的地位。

             下面通过一个简单的例子,一窥AOP的精妙。

        1、使用Log4j,我们可以在控制台查看日志信息,观察程序运行过程。

             (1)下载log4j.jar,下载链接http://logging.apache.org/log4j/2.x/

               (2) 在src下面,编写log4j.properties(网上找一个改一下)

log4j.rootLogger=debug, stdoutlog4j.appender.stdout=org.apache.log4j.ConsoleAppenderlog4j.appender.stdout.layout=org.apache.log4j.PatternLayout# Pattern to output the caller's file name and line number.log4j.appender.stdout.layout.ConversionPattern=%5p [%t] (%F:%L) - %m%n
        2、编写一个业务接口文件WatchBall.java
package edu.eurasia.aop;public interface WatchBall {      public void watchfootball(String city);      public void watchbasketball(String city); }

       其中一个方法是看足球,一个是看篮球。

     3、编写一个业务实现文件WatchBallImpl.java

package edu.eurasia.aop;import org.apache.log4j.Logger;public class WatchBallImpl implements WatchBall {Logger logger = Logger.getLogger(this.getClass());@Overridepublic void watchfootball(String city) {logger.debug("去 " + city + " 看2014巴西世界杯");}@Overridepublic void watchbasketball(String city) {logger.debug(" 去" + city + " 看2014NBA总决赛");}}

      实现接口中的方法。在执行watchfootball()和watchbasketball()这些目标对象的方法前后,会织入一些其它业务方法。下面创建这些方法,在执行这些方法前要执行的方法  BeforeAdvice.java 。

       4、编写一个切面文件BeforeAdvice.java

package edu.eurasia.aop;import java.lang.reflect.Method;import org.apache.log4j.Logger;import org.springframework.aop.MethodBeforeAdvice;public class BeforeAdvice implements MethodBeforeAdvice {Logger logger = Logger.getLogger(this.getClass());@Overridepublic void before(Method method, Object[] objects, Object o)throws Throwable {logger.debug(" 看球前,先去  " + objects[0] + "  咖啡馆喝杯巴西咖啡 ...");}}

     在看球前先去咖啡馆喝杯巴西咖啡大笑...

     5、编写另一个切面文件AfterAdvice.java

package edu.eurasia.aop;import java.lang.reflect.Method;import org.apache.log4j.Logger;import org.springframework.aop.AfterReturningAdvice;public class AfterAdvice implements AfterReturningAdvice {@Overridepublic void afterReturning(Object o, Method method, Object[] objects,Object o1) throws Throwable {Logger logger = Logger.getLogger(this.getClass());logger.debug("看完球去  " + objects[0] + "  麦当劳吃汉堡包!");}}
        在看完球后去麦当劳吃汉堡包大笑...
     6、编写spring的配置文件applicationContext.xml

<?xml version="1.0" encoding="UTF-8"?><beans xmlns="http://www.springframework.org/schema/beans"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://www.springframework.org/schema/beans               http://www.springframework.org/schema/beans/spring-beans.xsd"><!-- 代理类.调用的时候则是调用这个代理类 --><bean id="ball" class="org.springframework.aop.framework.ProxyFactoryBean"><!-- 代理接口(业务类接口) --><property name="proxyInterfaces"><value>edu.eurasia.aop.WatchBall</value></property><!-- interceptorNames 属性的list集合里面只能放 通知/通知者 -被称为拦截者(拦截器)。--><property name="interceptorNames"><list><value>beforeBallAdvisor</value><value>afterBallAdvisor</value></list></property><!-- 业务实现类 --><property name="target"><ref bean="ballTarget"></ref></property></bean>    <!-- 通用正则表达式切入点 --><bean id="beforeBallAdvisor"class="org.springframework.aop.support.RegexpMethodPointcutAdvisor"><!-- 需要对那些方法进行拦截 --><property name="advice" ref="beforeBallAdvice"></property><property name="pattern" value=".*.*foot.*"></property></bean>       <!-- 通用正则表达式切入点 --><bean id="afterBallAdvisor"class="org.springframework.aop.support.RegexpMethodPointcutAdvisor"><!-- 需要对那些方法进行拦截 --><property name="advice" ref="afterBallAdvice"></property><property name="pattern" value=".*.*basket.*"></property></bean><!-- 业务实现 --><bean id="ballTarget" class="edu.eurasia.aop.WatchBallImpl"></bean><!-- 切面类 --><bean id="beforeBallAdvice" class="edu.eurasia.aop.BeforeAdvice"></bean><!-- 切面类 --><bean id="afterBallAdvice" class="edu.eurasia.aop.AfterAdvice"></bean></beans>

          这个配置文件是AOP的核心,重点解说一下。首先,建立了一个“ball”的bean,这是一个代理bean,因为spring AOP是基于代理的。在里面有代理接口属性,接口可以多个,可以用<list>来表示,本例只有一个就是WatchBall。

interceptorNames属性,这是一个拦截器属性,就是访问者,在执行目标对象方法时,进行拦截。目标(target)属性对象,就是执行这个对象的方法时,在其前、后再织入一些其它的业务方法。

      本例的访问者是beforeBallAdvisor和afterBallAdvisor,访问者定义了通知对象,以及通知何时和何地进行访问target的问题。以beforeBallAdvisor为例,其定义的“通知”是advice,这个advice定义了在业务方法之前执行一些逻辑(何时),pattern则通过正则表达式,可以知道在任何业务(.*.*)方法(何地)执行时,都触发advice。

      拦截器bean,先看beforeBallAdvisor,其中pattern是一个正则表达式,说明对WatchBall对象的方法中含有foot关键字则执行beforeBallAdvice这个对象的方法。 afterBallAdvisor是执行target目标方法之后执行的方法,通过正则表达式(<property name="pattern" value=".*.*basket.*">)可以看出,target对象的方法中含有basket关键字,都要执行这个方法。

     7、编写测试类TestBall.java

package edu.eurasia.aop;import org.apache.log4j.Logger;import org.junit.After;import org.junit.Before;import org.junit.Test;import org.springframework.context.ApplicationContext;import org.springframework.context.support.ClassPathXmlApplicationContext;public class TestBall {static Logger logger = Logger.getLogger(TestBall.class);@Testpublic void testball() {logger.debug("test begin ");ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");// 这里写的必须是代理类WatchBall watchball = (WatchBall) ctx.getBean("ball");watchball.watchfootball("里约热内卢");logger.debug("----------------------------");watchball.watchbasketball("德克萨斯州");}}

         8、运行结果和环境配置

         运行结果如下:

         DEBUG [main] (BeforeAdvice.java:13) -  看球前,先去  里约热内卢  咖啡馆喝杯巴西咖啡 ...
         DEBUG [main] (WatchBallImpl.java:10) - 去 里约热内卢 看2014巴西世界杯
         DEBUG [main] (TestBall.java:34) - ----------------------------
         DEBUG [main] (WatchBallImpl.java:15) -  去德克萨斯州 看2014NBA总决赛
         DEBUG [main] (AfterAdvice.java:15) - 看完球去  德克萨斯州  麦当劳吃汉堡包!

          本例使用spring 2.5.6,只需找出spring.jar,commons-logging-1.1.1.jar两个jar包,外加一个log4j.jar即可。

         目录结构如下:



         9、改进一下

         上述例子的配置文件里面,代理类ProxyFactoryBean的配置显得啰嗦。Advisor已经把AOP所控制的东西都描述了吗,advice描述了“何时”触发target的对象(before、after、throw等),pattern描述了“何地”触发的问题(正则表达式),为什么还要有ProxyFactoryBean呢?如何解决?

         Spring提供了BeanPostProcessor的一个方便实现:DefaultAdvisorAutoProxyCreator,它会自动检查访问者(advisor)的切点是否匹配Bean方法,并且用使用通知的代理来替换这个bean的定义。

      这样,将applicationContext.xml改为下面即可:

<?xml version="1.0" encoding="UTF-8"?><beans xmlns="http://www.springframework.org/schema/beans"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://www.springframework.org/schema/beans               http://www.springframework.org/schema/beans/spring-beans.xsd"><!-- 自动应用于当前容器中的advisor,不需要定义指定目标Bean的名字字符串 --><bean class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator"></bean>        <!-- 通用正则表达式切入点 --><bean id="beforeBallAdvisor"class="org.springframework.aop.support.RegexpMethodPointcutAdvisor"><!-- 需要对那些方法进行拦截 --><property name="advice" ref="beforeBallAdvice"></property><property name="pattern" value=".*.*foot.*"></property></bean>           <!-- 通用正则表达式切入点 --><bean id="afterBallAdvisor"class="org.springframework.aop.support.RegexpMethodPointcutAdvisor"><!-- 需要对那些方法进行拦截 --><property name="advice" ref="afterBallAdvice"></property><property name="pattern" value=".*.*basket.*"></property></bean><!-- 业务实现 --><!-- <bean id="ballTarget" class="edu.eurasia.aop.WatchBallImpl"></bean> --><bean id="ball" class="edu.eurasia.aop.WatchBallImpl"></bean><!-- 切面类 --><bean id="beforeBallAdvice" class="edu.eurasia.aop.BeforeAdvice"></bean><!-- 切面类 --><bean id="afterBallAdvice" class="edu.eurasia.aop.AfterAdvice"></bean></beans>




0 0
原创粉丝点击