spring AOP详解

1、AOP:AOP(Aspect Orient Programming),也就是面向切面编程。可以这样理解,面向对象编程(OOP)是从静态角度考虑程序结构,面向切面编程(AOP)是从动态角度考虑程序运行过程

2.Spring 中 AOP 的实现

  Spring 有如下两种选择来定义切入点和增强处理。

  • 基于 Annotation 的“零配置”方式:使用@Aspect、@Pointcut等 Annotation 来标注切入点和增强处理。
  • 基于 XML 配置文件的管理方式:使用 Spring 配置文件来定义切入点和增强点。

  1、基于 Annotation 的“零配置”方式。

  (1)、首先启用 Spring 对 @AspectJ 切面配置的支持。

<?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:aop="http://www.springframework.org/schema/aop"      
       xsi:schemaLocation="http://www.springframework.org/schema/beans
           http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
           http://www.springframework.org/schema/aop
       http://www.springframework.org/schema/beans/spring-aop-3.0.xsd">
        <!-- 启动对@AspectJ注解的支持 -->
        <aop:aspectj-autoproxy/>
</beans>

2)、定义切面 Bean。

当启动了@AspectJ 支持后,只要在 Spring 容器中配置一个带@Aspect 注释的 Bean, Spring 将会自动识别该 Bean 并作为切面处理。

// 使用@Aspect 定义一个切面类
@Aspect
public class LogAspect {
        // 定义该类的其他内容
        ...
}

3)、定义 Before 增强处理。

// 定义一个切面
@Aspect
public class BeforeAdviceTest {
    // 匹配 com.wicresoft.app.service.impl 包下所有类的所有方法作为切入点
    @Before("execution(* com.wicresoft.app.service.impl.*.*(..))")
    public void authorith(){
        System.out.println("模拟进行权限检查。");
    }
}

上面使用@Before Annotation 时,直接指定了切入点表达式,指定匹配 com.wicresoft.app.service.impl包下所有类的所有方法执行作为切入点。

4)、定义 AfterReturning 增强处理。

// 定义一个切面
@Aspect
public class AfterReturningAdviceTest {
    // 匹配 com.wicresoft.app.service.impl 包下所有类的所有方法作为切入点
    @AfterReturning(returning="rvt", pointcut="execution(* com.wicresoft.app.service.impl.*.*(..))")
    public void log(Object rvt) {
        System.out.println("模拟目标方法返回值:" + rvt);
        System.out.println("模拟记录日志功能...");
    }
}

5)、定义 AfterThrowing 增强处理。

// 定义一个切面
@Aspect
public class AfterThrowingAdviceTest {
    // 匹配 com.wicresoft.app.service.impl 包下所有类的所有方法作为切入点
    @AfterThrowing(throwing="ex", pointcut="execution(* com.wicresoft.app.service.impl.*.*(..))")
    public void doRecoverActions(Throwable ex) {
        System.out.println("目标方法中抛出的异常:" + ex);
        System.out.println("模拟抛出异常后的增强处理...");
    }
}

(6)、定义 After 增强处理。

After 增强处理与AfterReturning 增强处理有点相似,但也有区别:

  • AfterReturning 增强处理处理只有在目标方法成功完成后才会被织入。
  • After 增强处理不管目标方法如何结束(保存成功完成和遇到异常中止两种情况),它都会被织入。
// 定义一个切面
@Aspect
public class AfterAdviceTest {
    // 匹配 com.wicresoft.app.service.impl 包下所有类的所有方法作为切入点
    @After("execution(* com.wicresoft.app.service.impl.*.*(..))")
    public void release() {
        System.out.println("模拟方法结束后的释放资源...");
    }
}

7)、Around 增强处理

// 定义一个切面
@Aspect
public class AroundAdviceTest {
    // 匹配 com.wicresoft.app.service.impl 包下所有类的所有方法作为切入点
    @Around("execution(* com.wicresoft.app.service.impl.*.*(..))")
    public Object processTx(ProceedingJoinPoint jp) throws java.lang.Throwable {
        System.out.println("执行目标方法之前,模拟开始事物...");
        // 执行目标方法,并保存目标方法执行后的返回值
        Object rvt = jp.proceed(new String[]{"被改变的参数"});
        System.out.println("执行目标方法之前,模拟结束事物...");
        return rvt + "新增的内容";
    }
}

8)、访问目标方法的参数。

访问目标方法最简单的做法是定义增强处理方法时将第一个参数定义为 JoinPoint 类型,当该增强处理方法被调用时,该 JoinPoint 参数就代表了织入增强处理的连接点。JoinPoint 里包含了如下几个常用方法。

  • Object[] getArgs(): 返回执行目标方法时的参数。
  • Signature getSignature(): 返回被增强的方法的相关信息。
  • Object getTarget(): 返回被织入增强处理的目标对象。
  • Object getThis(): 返回 AOP 框架为目标对象生成的代理对象。

提示当时使用 Around 处理时,我们需要将第一个参数定义为 ProceedingJoinPoint 类型,该类型是 JoinPoint 类型的子类

9)、定义切入点。

所谓切入点,其实质就是为一个切入点表达式起一个名称,从而允许在多个增强处理中重用该名称。

Spring 切入点定义包含两个部分:

  • 一个切入点表达式。
  • 一个包含名字和任意参数的方法签名。
// 使用@Pointcut Annotation 时指定切入点表达式
@pointcut("execution * transfer(..)")
// 使用一个返回值为void,方法体为空的方法来命名切入点
private void anyOldTransfer(){}

// 使用上面定义的切入点
@AfterReturning(pointcut="anyOldTransfer()", returning="reVal")
public void writeLog(String msg, Object reVal){
    ...
}

2、基于 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"
       xmlns:aop="http://www.springframework.org/schema/aop"      
       xsi:schemaLocation="http://www.springframework.org/schema/beans
           http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
           http://www.springframework.org/schema/aop
       http://www.springframework.org/schema/beans/spring-aop-3.0.xsd">
        <aop:config>
            <!-- 将 fourAdviceBean 转换成切面 Bean, 切面 Bean 的新名称为:fourAdviceAspect,指定该切面的优先级为2 -->
            <aop:aspect id="fourAdviceAspect" ref="fourAdviceBean" order="2">
                <!-- 定义个After增强处理,直接指定切入点表达式,以切面 Bean 中的 Release() 方法作为增强处理方法 -->
                <aop:after pointcut="execution(* com.wicresoft.app.service.impl.*.*(..))" method="release" />
                
                <!-- 定义个Before增强处理,直接指定切入点表达式,以切面 Bean 中的 authority() 方法作为增强处理方法 -->
                <aop:before pointcut="execution(* com.wicresoft.app.service.impl.*.*(..))" method="authority" />
                
                <!-- 定义个AfterReturning增强处理,直接指定切入点表达式,以切面 Bean 中的 log() 方法作为增强处理方法 -->
                <aop:after-returning pointcut="execution(* com.wicresoft.app.service.impl.*.*(..))" method="log" />
                
                <!-- 定义个Around增强处理,直接指定切入点表达式,以切面 Bean 中的 processTx() 方法作为增强处理方法 -->
                <aop:around pointcut="execution(* com.wicresoft.app.service.impl.*.*(..))" method="processTx" />
                
            </aop:aspect>
        </aop:config>
        
        <!-- 省略各个Bean 的配置 -->
        <!-- ... -->
        
</beans>

  配置切入点

<?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:aop="http://www.springframework.org/schema/aop"      
       xsi:schemaLocation="http://www.springframework.org/schema/beans
           http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
           http://www.springframework.org/schema/aop
       http://www.springframework.org/schema/beans/spring-aop-3.0.xsd">
        <aop:config>
            <!-- 定义一个切入点,myPointcut,直接知道它对应的切入点表达式 -->
            <aop:pointcut id="myPointcut" expression="execution(* com.wicresoft.app.service.impl.*.*(..))" method="release" />
            <aop:aspect id="afterThrowingAdviceAspect" ref="afterThrowingAdviceBean" order="1">
                <!-- 使用上面定于切入点定义增强处理 -->
                <!-- 定义一个AfterThrowing 增强处理,指定切入点以切面 Bean 中的 doRecovertyActions() 方法作为增强处理方法 -->
                <aop:after-throwing pointcut-ref="myPointcut" method="doRecovertyActions" throwing="ex" />
            </aop:aspect>
        </aop:config>
        
        <!-- 省略各个Bean 的配置 -->
        <!-- ... -->
        
</beans>


--------------------------------------------------------------------------------------------------------------

如果不能表达在应用系统得什么地方应用通知的话,通知将毫无用处,这就是切入点的用处。切入点决定了一个特点的类的特定方法是否满足一定的规则。若符合,通知就应用到该方法上。
Spring根据需要织入通知的类和方法来定义切入点。通知是根据其特性织入目标类和方法。Spring的切入点框架的核心接口是Pointcut
接口MethodMatcher三个方法,用在被代理对象生命周期的特定时期
1、    matches(Method,Class)方法根据目标和方法决定一个方法是否被通知,在AOP代理被创建时调用一次,其结果决定通知是否被织入
2、    若matches(Method,class)返回true,isRuntime()调用来决定MethodMatcher的类型:静态和动态(静态切入点通知总是被执行,如一个切入点是静态的,isRuntime()方法返回false;动态切入点根据运行时方法的参数值决定通知是否执行,若一个切入点是动态的,isRuntime()方法返回true),在代理类创建时调用一次。
3、    如一个切入点是静态的,matches(Method,Class,Object[])方法被调用
下面是一些在xml文件中配置切入点的一些代码
在业务bean指定的方法上引入通知
方式一:
<bean id="nameMatchMethodPointcutAdvisor" class="org.springframework.aop.support.NameMatchMethodPointcutAdvisor">
<!-- 属性配置    一个切入点 -->
    <property name="mappedNames">
        <list>
            <value>study</value>
        </list>
    </property>
<!-- 切入点上执行的通知       环绕通知 -->
    <property name="advice">
        <ref bean="myAroundAdvice"/>
    </property>
</bean>
方式二:采用的是正则表达式匹配器
.匹配任何单个字符
+匹配前一个字符一次或多次
*匹配前一个字符0次或多次
\匹配任何正则表达式符号
<bean id="regexpMethodPointcutAdvisor" class="org.springframework.aop.support.RegexpMethodPointcutAdvisor">
    <property name="patterns">
        <list>
        <!-- 正则表达式必须以.*或.+等开头 -->
            <value>.*stu.*</value>
        </list>
    </property>
    <property name="advice">
        <ref bean="myBeforeAdvice"/>
    </property>
</bean>
通过上面的配置就可以在指定的方法调用时添加相应的通知
其实上面在xml文件中的配置算是一种手动的插入切入点的方法,下面说一种自动插入的方法
方式一:名称代理,根据bean  id=”….”
<bean id="beanNameAutoProxyCreator" class="org.springframework.aop.framework.autoproxy.BeanNameAutoProxyCreator">
    <property name="beanNames">
        <list>
            <value>*Bean</value>
        </list>
    </property>
    <property name="interceptorNames">
        <list>
        加环绕通知
            <value>myAroundAdvice</value>
        </list>
    </property>
</bean>
方式二:自动装配advisor
<bean id="defaultAdvisorAutoProxyCreator" class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator"></bean>

利用注解引入通知----------整个案例
EmployeeService接口
public interface EmployeeService {
    //工作
    public void work();
}
EmployeeServiceBean类实现上述接口
public class EmployeeServiceBean implements EmployeeService{
    @Override
    public void work() {
        // TODO Auto-generated method stub
        //int i=1/0;
        System.out.println("++++++++++++++++++工作中+++++++++++++++++");
    }
}
定义一个切面类
@Aspect
public class AspectUtil {
    //execution切入点指示符    第一个参数方法的修饰符      第二个参数代表方法返回类型【*.*work代表----包名+类名 . 包下的所有类中带work的方法】    第三个参数代表方法参数
    /*
     * (..)表示参数任意
     * 包名后面有两个..代表当前包与当前包的子包    
     */
    //声明一个切入点
    @Pointcut("execution(* *.*work(..))")
    public void disp(){}
    /*@Before("execution(* cn.csdn.service.EmployeeServiceBean.*(..))")*/
        @Before("disp()")
        public void getUp() {
            // TODO Auto-generated method stub
            System.out.println("小爽     该起床啦***********************");
        }
        @Around("disp()")
        public Object punchCard(ProceedingJoinPoint jp) throws Throwable{
            System.out.println("===============上班打卡================");
            Object obj=jp.proceed();
            System.out.println("===============下班打卡================");
            return obj;
        }
        @After("disp()")
        public void goHome() {
            // TODO Auto-generated method stub
            System.out.println("终于可以回家啦 -_-");
        }
        @AfterThrowing("disp()")
        public void unwell(){
            System.out.println("。。。。。。。。。。。。。不舒服啦。。。。。。。。。。。。");
        }
}
在xml文件中配置
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
        xmlns:aop="http://www.springframework.org/schema/aop"
    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-2.5.xsd
       http://www.springframework.org/schema/aop
       http://www.springframework.org/schema/aop/spring-aop-2.5.xsd" >
<!-- 声明通知 -->
<bean id="AspectUtil" class="cn.csdn.advice.AspectUtil"></bean>
<bean id="myAspectAdvice" class="cn.csdn.service.MyAspectAdvice"></bean>
<!-- 配置一个业务bean -->
<bean id="employeeServiceBean" class="cn.csdn.service.EmployeeServiceBean"></bean>
<!-- 添加     能够使用注解 -->
<aop:aspectj-autoproxy/>
</beans>
注:使用spring的静态切入点静态切入点只在代理创建的时候执行一次而不是在运行期间每次方法调用都执行,所以性能 比动态切入点好,是首选的切入点方式。spring为创建静态切入点提供了方便的父类
当调用的方法名字与给出的映射名字匹配时, 切点才匹配。也可在名字的开始和结束使用通配符
原文地址:https://www.cnblogs.com/hwj2wj/p/2879825.html