frank1234 阅读(341) 评论(0)

1 何为AOP
AOP是Aspect Orinted Programming,翻译为面向切面编程。AOP能干什么呢,比如系统中有3个业务类,AService,BService和CService,如果现在要在3个Service的方法中,进入方法开始的时System.out.println("hello");离开方法时System.out.println("byebye")。
那么我们可以怎么做呢?
1)分别修改AService,BService,CService所有的方法之前和之后拷贝粘贴这两个Sysetm.out.println()。如果给byebye加一个"!",那么得修改所有的方法,那叫一个累啊。系统中首先要复用,有句话说的好:"复制粘贴设计之谬",显然这种方式不好。
2)通过委托模式实现。将System.out.println("hello")和System.out.println("bye")封装成公共的方法,ASerivce,BService,CService都委托给这个公共方法实现。这个比第1种方法好多了,但是依然得重复调用委托对象,如果此时AService不想hello和byebye了,需要修改AService删掉相关代码。客户的需求就是这么多变,唯有不变的就是变化,我们要拥抱变化不是嘛,把变化想象成美女吧。另外这种方式还有个弊端,AService更应该关注它自身的业务逻辑,对于这种事情,他不关心,这种代码不应该影响到他的核心业务逻辑代码。

AOP的出现优雅的解决了上述问题,他可以将上面的System.out.println("hello")和System.out.println("bye")定义成切面,通过AOP将这两段代码织入到AService,BService和CService中,而各Service本身并不需要修改代码。所以AOP是OOP的补充,他解决了这种交叉业务问题,比如常见的有:日志、事务、性能监控、安全处理、权限校验等等。
如下图所示:



图中安全、事务就是切面,它可以插入到CourseService等业务模块中。

2 AOP核心概念
 切面(Aspect) : 切面就是你要实现的交叉功能,例如权限切面,事务切面。
 连接点(JoinPoint) :连接点是应用程序执行过程中插入切面的地点。可以是方法调用、属性或者异常抛出等。Spring不提供属性连接点。
 通知(Advice):通知是切面的具体实现。例如:向日志文件中写入日志。
 切入点(Pointcut) : 切入点定义了通知应该应用在哪些连接点。通常通过类名和方法名来指定切入点。应用的什么地方使用通知。
 引入(Introduction):引入允许你为已存在的类添加新方法和属性。
 目标对象(Target):目标对象就是被通知对象。
 代理(Proxy):代理是将通知应用到目标对象后创建的对象。 Spring有2种代理创建方式,如果目标对象实现了接口那么就通过JDK动态代理实现,如果没有接口,那么就通过CGLIB生成目标对象的子类来实现,当然final的方法无法被通知。
 织入(Weaving):织入是将切面应用到目标对象从而创建一个新的代理对象的过程。
织入可以在编译器类装载期或者运行期发生。aspectJ发生在编译期,Spring AOP发生在运行期,通过动态生成目标对象的代理实现。

概念好多,而且貌似不太容易懂,不过没关系,下面结合实际代码分别对应一下就柳暗花明了。
3 Spring AOP之hello world
例子实现的是统计一下方法执行的时间,这个可以实际应用到性能测试中。
翠花上代码:
HelloWorldService:
public interface HelloWorldService {
    public void sayHello();
}

HelloWorldImpl:
public class HelloWorldImpl implements HelloWorldService {

    public void sayHello() {
        System.out.println("hello frank1234,begin aop");
        try{
            System.out.println("老夫累了,休息下先");
            Thread.currentThread().sleep(new Random().nextInt(1000));
        }catch (Exception e){
            e.printStackTrace();
        }
    }
}


PerformanceInterceptor:主要是这个类
public class PerformanceInterceptor implements MethodInterceptor {
    public Object invoke(MethodInvocation invocation) throws Throwable{
        long beginTime = System.currentTimeMillis();
        Object rtnObj = invocation.proceed();
        System.out.println("执行花费了:"+(System.currentTimeMillis() - beginTime));
        return rtnObj;
    }
}


helloworld.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="helloTarget" class="org.frank1234.spring.aop.HelloWorldImpl"/>

       <bean id="performanceAdvice" class="org.frank1234.spring.aop.PerformanceInterceptor"/>

       <bean id="hello" class="org.springframework.aop.framework.ProxyFactoryBean">
              <property name="proxyInterfaces">
                     <value>org.frank1234.spring.aop.HelloWorldService</value>
              </property>
              <property name="interceptorNames">
                     <list>
                            <value>performanceAdvice</value>
                     </list>
              </property>
              <property name="target">
                     <ref bean="helloTarget"/>
              </property>
       </bean>
</beans>


测试类:
public class HelloWorldAopMain {
    public static void main(String[] args) throws Exception{
        ApplicationContext factory = new ClassPathXmlApplicationContext("helloworld.xml");
        HelloWorldService service = (HelloWorldService)factory.getBean("hello");
        service.sayHello();
    }
}

输出:
hello frank1234,begin aop
老夫累了,休息下先
执行花费了:645

从输出可见统计了sayHello()方法执行的时间。

代码同概念对应关系:
 切面(Aspect) : 无对应代码,就是性能统计这个交叉功能。
 连接点(JoinPoint) : 此处的连接点是方法调用,见PerformanceInterceptor类的invocation.proceed(),这个方法会调用HelloWorldImpl的sayHello()方法。
 通知(Advice): PerformanceInterceptor类就是通知。
 切入点(Pointcut) : 配置文件中的<property name="interceptorNames">
                     <list>
                            <value>performanceAdvice</value>
                     </list>
              </property>定义了切入点,这个还可以通过正则表达式来定义。
 引入(Introduction):示例未涉及。
 目标对象(Target):HelloWorldImpl就是目标对象。
 代理(Proxy):ApplicationContext载入Bean时,Spring会创建一个JDK动态代理对象,由代理对象实际执行,并且代理对象调用目标对象(HelloWorldImpl)的执行方法(sayHello),JDK动态代理生成的代理类的名称为:$Proxy0.class。
 织入(Weaving):将性能统计这个切面应用到HelloWorldImpl,并创建动态代理的过程就叫织入。


4 Spring AOP
4.1 Spring的通知类型
1)前置通知
public interface MethodBeforeAdvice{
void before(Method method,Object[] args,Object target) throws Throwable;
}
调用时机:目标方法调用之前调用。
2)后置通知
public interface AfterReturningAdvice{
void afterReturning(Object returnValue,Method method,Object[] args,Object target) throws Throwable;
}
调用时机:目标方法调用之后调用。
3)环绕通知
public interface MethodInterceptor extends Interceptor{
Object invoke(MethodInvocation invocation) throws Throwable;
}
拦截对目标方法的调用。
这是一个AOP联盟的接口,它和其他的通知类型主要有2个不同,1:可以控制目标方法是否真正被调用。2:可以返回不同的对象。之前helloworld的例子就是基于这个通知类型。
4)异常通知
public interface ThrowsAdvice{
void afterThrowing(Throwable throwable);
void afterThrowing(Method method,Object[] args,Object target,Throwable throwable);
}
调用时机:目标方法抛出异常时调用。
4.2 通过正则表达式定义切入点
示例片段:
<bean id="xxx" class="org.springframework.aop.framework.ProxyFactoryBean">
       <property name="proxyInterfaces">
              <value>xxxdService</value>
       </property>
       <property name="interceptorNames">
              <list>
                     <value>regPointcutAdvisor</value>
              </list>
       </property>
       <property name="target">
              <ref bean="xxxTarget"/>
       </property>
</bean>
<bean id="regPointcutAdvisor" class="org.springframework.aop.support.RegexpMethodPointcutAdvisor">
       <property name="pattern">
              <value>.*get.+By.+</value>
       </property>
       <property name="advice">
              <ref bean="xxxAdvice"/>
       </property>
</bean>


<value>.*get.+By.+</value>中的符号解释:
*代表匹配字符0次到多次
+代表匹配字符1次到多次,至少包含1个
4.3 ProxyFactoryBean配置
关键属性解释:
target: 代理的目标对象
proxyInterfaces: 代理应该实现的接口列表
interceptorNames: 需要应用到目标对象上的通知Bean的名字,可以是拦截器、Advisor或者其他通知类型的名字。

示例可参见 hello world 配置文件。



《spring in action》