Spring AOP 的几个知识点

转自 http://my.oschina.net/sniperLi/blog/491854

一. 什么是AOP

AOP是OOP的延续,是Aspect Oriented Programming的缩写,意思是面向切面编程。可以通过预编译方式和运行期动态代理实现在不修改源代码的情况下给程序动态统一添加功能的一种技术。AOP实际是GoF设计模式的延续,设计模式孜孜不倦追求的是调用者和被调用者之间的解耦,AOP可以说也是这种目标的一种实现。

我们现在做的一些非业务,如:日志、事务、安全等都会写在业务代码中(也即是说,这些非业务类横切于业务类),但这些代码往往是重复,复制——粘贴式的代码会给程序的维护带来不便,AOP就实现了把这些业务需求与系统需求分开来做。这种解决的方式也称代理机制。

二. AOP的相关概念

  • 切面(Aspect):官方的抽象定义为“一个关注点的模块化,这个关注点可能会横切多个对象”,在本例中,“切面”就是类TestAspect所关注的具体行为,例如,AServiceImpl.barA()的调用就是切面TestAspect所关注的行为之一。“切面”在ApplicationContext中来配置。

  • 连接点(Joinpoint) :程序执行过程中的某一行为,例如,UserService.get的调用或者UserService.delete抛出异常等行为。

  • 通知(Advice) :“切面”对于某个“连接点”所产生的动作,例如,TestAspect中对com.spring.service包下所有类的方法进行日志记录的动作就是一个Advice。其中,一个“切面”可以包含多个“Advice”,例如ServiceAspect。

  • 切入点(Pointcut) :匹配连接点的断言,在AOP中通知和一个切入点表达式关联。例如,TestAspect中的所有通知所关注的连接点,都由切入点表达式execution(* com.spring.service..(..))来决定。

  • 目标对象(Target Object) :被一个或者多个切面所通知的对象。例如,AServcieImpl和BServiceImpl,当然在实际运行时,Spring AOP采用代理实现,实际AOP操作的是TargetObject的代理对象。

  • AOP代理(AOP Proxy) :在Spring AOP中有两种代理方式,JDK动态代理和CGLIB代理。默认情况下,TargetObject实现了接口时,则采用JDK动态代理,例如,AServiceImpl;反之,采用CGLIB代理,例如,BServiceImpl。强制使用CGLIB代理需要将 的 proxy-target-class属性设为true。

  • 通知(Advice)类型:

    • 前置通知(Before advice):在某连接点(JoinPoint)之前执行的通知,但这个通知不能阻止连接点前的执行。ApplicationContext中在里面使用元素进行声明。例如,TestAspect中的doBefore方法。

    • 后置通知(After advice):当某连接点退出的时候执行的通知(不论是正常返回还是异常退出)。ApplicationContext中在里面使用元素进行声明。例如,ServiceAspect中的returnAfter方法,所以Teser中调用UserService.delete抛出异常时,returnAfter方法仍然执行。

    • 返回后通知(After return advice):在某连接点正常完成后执行的通知,不包括抛出异常的情况。ApplicationContext中在里面使用元素进行声明。

    • 环绕通知(Around advice):包围一个连接点的通知,类似Web中Servlet规范中的Filter的doFilter方法。可以在方法的调用前后完成自定义的行为,也可以选择不执行。ApplicationContext中在里面使用元素进行声明。例如,ServiceAspect中的around方法。

    • 抛出异常后通知(After throwing advice):在方法抛出异常退出时执行的通知。ApplicationContext中在里面使用元素进行声明。例如,ServiceAspect中的returnThrow方法。

注:可以将多个通知应用到一个目标对象上,即可以将多个切面织入到同一目标对象。

使用Spring AOP可以基于两种方式,一种是比较方便和强大的注解方式,另一种则是中规中矩的xml配置方式。

三. 基于注解的AOP

使用注解配置Spring AOP总体分为两步:

  1. 第一步是在xml文件中声明激活自动扫描组件功能,同时激活自动代理功能(同时在xml中添加一个UserService的普通服务层组件,来测试AOP的注解功能)。
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context"
    xmlns:aop="http://www.springframework.org/schema/aop" xmlns:tx="http://www.springframework.org/schema/tx"
    xmlns:p="http://www.springframework.org/schema/p" xmlns:cache="http://www.springframework.org/schema/cache"
    xmlns:jpa="http://www.springframework.org/schema/data/jpa"

    xsi:schemaLocation="http://www.springframework.org/schema/beans     
          http://www.springframework.org/schema/beans/spring-beans-4.2.xsd     
          http://www.springframework.org/schema/context     
          http://www.springframework.org/schema/context/spring-context-4.2.xsd     
          http://www.springframework.org/schema/aop     
          http://www.springframework.org/schema/aop/spring-aop-4.2.xsd     
          http://www.springframework.org/schema/tx      
          http://www.springframework.org/schema/tx/spring-tx-4.2.xsd  
          http://www.springframework.org/schema/cache   
          http://www.springframework.org/schema/cache/spring-cache-4.2.xsd  
          http://www.springframework.org/schema/data/jpa  
          http://www.springframework.org/schema/data/jpa/spring-jpa.xsd">

    <context:annotation-config />

    <context:component-scan base-package="com.ninelephas.meerkat" />


    <!-- 激活自动代理功能 -->
    <aop:aspectj-autoproxy proxy-target-class="true" />


</beans> 
  1. 第二步是为Aspect切面类添加注解:
/**
 * @Title: AspectTestComponent.java
 * @Package com.ninelephas.meerkat.aspect
 * @Description: TODO
 *               Copyright: Copyright (c) 2016
 *               Company:九象网络科技(上海)有限公司
 * 
 * @author "徐泽宇"
 * @date 2016年7月27日 下午11:44:51
 * @version V1.0.0
 */

package com.ninelephas.meerkat.tests.aop;

import org.apache.log4j.Logger;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component;

/**
 * @ClassName: AspectTestComponent
 * @Description: TODO
 * @author Comsys-"徐泽宇"
 * @date 2016年7月27日 下午11:44:51
 *
 */
@Component("com.ninelephas.meerkat.tests.aop.AspectBean")
@Aspect
public class AspectBean {
    private static final Logger logger = Logger.getLogger(AspectBean.class);



    @Around("execution(* com.ninelephas.meerkat.tests.aop.BusinessBean.testService(..))")
    public void before(JoinPoint joinPoint) {
        logger.debug("before(JoinPoint) - start"); //$NON-NLS-1$

        logger.info("切面被执行: before " + joinPoint);

        logger.debug("before(JoinPoint) - end"); //$NON-NLS-1$
    }
}
  1. 业务service
/**
 * @Title: BusinessBean.java
 * @Package com.ninelephas.meerkat.tests.aop
 * @Description: TODO
 * Copyright: Copyright (c) 2016
 * Company:九象网络科技(上海)有限公司
 * 
 * @author "徐泽宇"
 * @date 2016年8月1日 下午2:21:03
 * @version V1.0.0
 */

package com.ninelephas.meerkat.tests.aop;

import org.apache.log4j.Logger;
import org.springframework.stereotype.Component;

/**
  * @ClassName: BusinessBean
  * @Description: TODO
  * @author Comsys-"徐泽宇"
  * @date 2016年8月1日 下午2:21:03
  *
  */


@Component("com.ninelephas.meerkat.tests.aop.BusinessBean")
public class BusinessBean {
    /**
     * @author 徐泽宇
     * @version 1.0.0
     * @date
     */
    private static final Logger logger = Logger.getLogger(BusinessBean.class);

    public void testService(){
        logger.debug("testService() - start"); //$NON-NLS-1$


        logger.debug("testService() - end"); //$NON-NLS-1$
    }

}
  1. 测试代码:
/**
 * @Title: AopTestBean.java
 * @Package com.ninelephas.meerkat.tests.aop
 * @Description: TODO
 * Copyright: Copyright (c) 2016
 * Company:九象网络科技(上海)有限公司
 * 
 * @author "徐泽宇"
 * @date 2016年7月27日 下午1:10:58
 * @version V1.0.0
 */

package com.ninelephas.meerkat.tests.aop;

import org.apache.log4j.Logger;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.AbstractJUnit4SpringContextTests;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

/**
  * @ClassName: AopTestBean
  * @Description: TODO
  * @author Comsys-"徐泽宇"
  * @date 2016年7月27日 下午1:10:58
  *
  */

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = { "classpath:META-INF/applicationContext.xml" })
public class JunitTestBean extends AbstractJUnit4SpringContextTests{
    /**
     * @author 徐泽宇
     * @version 1.0.0
     * @date
     */
    private static final Logger logger = Logger.getLogger(JunitTestBean.class);

    @Autowired
    BusinessBean businessBean;

    @Test
    public  void businessMethodOne(){
        logger.debug("businessMethodOne() - start"); //$NON-NLS-1$

        businessBean.testService();

        logger.debug("businessMethodOne() - end"); //$NON-NLS-1$
    }

}
  1. 测试结果:
 [meerkat] 2016-08-01 14:47:01,892 -com.ninelephas.meerkat.tests.aop.JunitTestBean.businessMethodOne(JunitTestBean.java:46) DEBUG - businessMethodOne() - start
[meerkat] 2016-08-01 14:47:01,904 -com.ninelephas.meerkat.tests.aop.AspectBean.before(AspectBean.java:38) DEBUG - before(JoinPoint) - start
[meerkat] 2016-08-01 14:47:01,906 -com.ninelephas.meerkat.tests.aop.AspectBean.before(AspectBean.java:40) INFO  - 切面被执行: before execution(void com.ninelephas.meerkat.tests.aop.BusinessBean.testService())
[meerkat] 2016-08-01 14:47:01,906 -com.ninelephas.meerkat.tests.aop.AspectBean.before(AspectBean.java:42) DEBUG - before(JoinPoint) - end
[meerkat] 2016-08-01 14:47:01,906 -com.ninelephas.meerkat.tests.aop.JunitTestBean.businessMethodOne(JunitTestBean.java:50) DEBUG - businessMethodOne() - end

四.些通用切入点表达式的例子。

  • 任意公共方法的执行:
execution(public * *(..))
  • 任何一个名字以“set”开始的方法的执行:
execution(* set*(..))
  • AccountService接口定义的任意方法的执行:
execution(* com.xyz.service.AccountService.*(..))
  • 在service包中定义的任意方法的执行:
execution(* com.xyz.service.*.*(..))
  • 在service包或其子包中定义的任意方法的执行:
execution(* com.xyz.service..*.*(..))
  • 在service包中的任意连接点(在Spring AOP中只是方法执行):
within(com.xyz.service.*)
  • 在service包或其子包中的任意连接点(在Spring AOP中只是方法执行):
within(com.xyz.service..*)
  • 实现了AccountService接口的代理对象的任意连接点 (在Spring AOP中只是方法执行):
this(com.xyz.service.AccountService
  • ‘this’在绑定表单中更加常用:- 请参见后面的通知一节中了解如何使得代理对象在通知体内可用。
    实现AccountService接口的目标对象的任意连接点 (在Spring AOP中只是方法执行):
target(com.xyz.service.AccountService
  • ‘target’在绑定表单中更加常用:- 请参见后面的通知一节中了解如何使得目标对象在通知体内可用。
    任何一个只接受一个参数,并且运行时所传入的参数是Serializable 接口的连接点(在Spring AOP中只是方法执行)
args(java.io.Serializable
  • ‘args’在绑定表单中更加常用:- 请参见后面的通知一节中了解如何使得方法参数在通知体内可用。
    请注意在例子中给出的切入点不同于 execution(* *(java.io.Serializable)): args版本只有在动态运行时候传入参数是Serializable时才匹配,而execution版本在方法签名中声明只有一个 Serializable类型的参数时候匹配。
    目标对象中有一个 @Transactional 注解的任意连接点 (在Spring AOP中只是方法执行)
@target(org.springframework.transaction.annotation.Transactional
  • ‘@target’在绑定表单中更加常用:- 请参见后面的通知一节中了解如何使得注解对象在通知体内可用。
    任何一个目标对象声明的类型有一个 @Transactional 注解的连接点 (在Spring AOP中只是方法执行):
@within(org.springframework.transaction.annotation.Transactional
  • ‘@within’在绑定表单中更加常用:- 请参见后面的通知一节中了解如何使得注解对象在通知体内可用。
    任何一个执行的方法有一个 @Transactional 注解的连接点 (在Spring AOP中只是方法执行)
@annotation(org.springframework.transaction.annotation.Transactional
  • ‘@annotation’在绑定表单中更加常用:- 请参见后面的通知一节中了解如何使得注解对象在通知体内可用。
    任何一个只接受一个参数,并且运行时所传入的参数类型具有@Classified 注解的连接点(在Spring AOP中只是方法执行)
@args(com.xyz.security.Classified
  • ‘@args’在绑定表单中更加常用:- 请参见后面的通知一节中了解如何使得注解对象在通知体内可用。
    任何一个在名为’tradeService’的Spring bean之上的连接点 (在Spring AOP中只是方法执行):
bean(tradeService)
  • 任何一个在名字匹配通配符表达式’*Service’的Spring bean之上的连接点 (在Spring AOP中只是方法执行):
bean(*Service
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章