1 AOP的引入

1.1 Step1:项目aop_leadin1

  • 先定义好接口与一个实现类,该实现类中除了要实现接口中的方法外,还要再写两个非业务方法,非业务方法也称之为交叉业务逻辑:
  • doTransaction():用于事务处理;
  • doLog():用于日志处理;
  • 然后,再使用接口方法调用它们。接口方法也称之为主业务逻辑。

    public class StudentServiceImpl implements IService{
    //主业务逻辑,接口方法
    @Override
    public void doSome() {doTransaction();System.out.println("StudentServiceImpl执行doSome()");doLog();
    }
    //主业务逻辑,接口方法
    @Override
    public void doOther() {doTransaction();System.out.println("StudentServiceImpl执行do  Other()");doLog();
    }
    // 交叉业务逻辑,事务处理代码
    private void doLog() {System.out.println("我是事务代码");
    }
    // 交叉业务逻辑,日志处理代码
    private void doTransaction() {System.out.println("我是日志代码");}
    }

    1.2 Step2:项目aop_leadin2

  • 此时提出一个问题,若有其他实现类同样也要调用这些事务,日志等处理方法该怎么办?
  • 解决办法是,将这些日志、事务等方法包装到另一个类中,让实现类继承这个类。

    public class BaseService {
    // 交叉业务逻辑,事务处理代码
    public void doLog() {System.out.println("我是事务代码");
    }
    // 交叉业务逻辑,日志处理代码
    public void doTransaction() {System.out.println("我是日志代码");}
    }
    public class StudentServiceImpl extends BaseService implements IService{
    //主业务逻辑,接口方法
    @Override
    public void doSome() {doTransaction();System.out.println("StudentServiceImpl执行doSome()");doLog();
    }
    //主业务逻辑,接口方法
    @Override
    public void doOther() {doTransaction();System.out.println("StudentServiceImpl执行do  Other()");doLog();
    }
    }
    public class TeacherServiceImpl extends BaseService implements IService{
    //主业务逻辑,接口方法
    @Override
    public void doSome() {doTransaction();System.out.println("TeacherServiceImpl执行doSome()");doLog();
    }
    //主业务逻辑,接口方法
    @Override
    public void doOther() {doTransaction();System.out.println("TeacherServiceImpl执行do  Other()");doLog();
    }
    }

    1.3 Step3:项目aop_leadin3

    • 以上的解决方案,也有一个弊端:若实现类还需要继承其他类,就不行了。因为Java技术不支持多继承。
    • 当然,也可以有另一种解决方案:将这些交叉业务逻辑代码放到专门的工具类或者处理类中,由主业务逻辑调用。
      public class MyTransactionProcessor {
      // 交叉业务逻辑,日志处理代码
      public static void doTransaction() {
      System.out.println("我是日志代码");
      }
      }
      public class MyLogProcessor {
      // 交叉业务逻辑,事务处理代码
      public static void doLog() {
      System.out.println("我是事务代码");
      }
      }
      public class StudentServiceImpl implements IService{
      //主业务逻辑,接口方法
      @Override
      public void doSome() {
      MyTransactionProcessor.doTransaction();
      System.out.println("StudentServiceImpl执行doSome()");
      MyLogProcessor.doLog();
      }
      //主业务逻辑,接口方法
      @Override
      public void doOther() {
      MyTransactionProcessor.doTransaction();
      System.out.println("StudentServiceImpl执行do  Other()");
      MyLogProcessor.doLog();
      }
      }

      1.4 Step4:项目aop_leadin4

  • 以上的解决方案,还是存在弊端:交叉业务与主业务深度耦合在一起。当交叉业务逻辑较多时,在主业务代码中会出现大量的交叉业务逻辑代码调用语句,大大影响了主业务逻辑的可读性,降低了代码的可维护性,同时也增加了开发难度。
  • 所以,可以采用动态代理方式。动态代理是OCP开发原则的一个重要体现:在不修改主业务逻辑的前提下,扩展和增强其功能。

    public class MyTest {
    public static void main(String[] args) {//创建目标类StudentServiceImpl target = new StudentServiceImpl();//创建代理类IService serviceProxy = (IService)Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), new MyInvocationHandler(target));serviceProxy.doOther();
    }
    }
    public class MyInvocationHandler implements InvocationHandler {
    private Object target;public MyInvocationHandler(Object target) {super();this.target = target;
    }
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {//调用交叉业务逻辑MyTransactionProcessor.doTransaction();//调用主业务逻辑Object result = method.invoke(target, args);//调用交叉业务逻辑MyLogProcessor.doLog();return result;
    }
    }
    public class StudentServiceImpl implements IService{
    //目标类的主业务逻辑执行过程中,看不到任何交叉业务逻辑。
    //目标类中变得非常“干净”,便于开发、维护、可读性强。
    //主业务逻辑,接口方法
    @Override
    public void doSome() {System.out.println("StudentServiceImpl执行doSome()");
    }
    //主业务逻辑,接口方法
    @Override
    public void doOther() {System.out.println("StudentServiceImpl执行do  Other()");
    }
    }

    2 AOP概述

    2.1 AOP简介

  • AOP(Aspect Orient Programming),面向切面编程,是面向对象编程OOP的一种补充。面向对象编程是从静态角度考虑程序的结构,而面向切面编程是从动态角度考虑程序运行过程。
  • AOP底层,就是采用动态代理模式实现的。采用了两种代理:JDK的动态代理,与CGLIB的动态代理。
  • 百度百科解释:
  • 前面切面编程中,就是将交叉业务逻辑封装成切面,利用AOP容器的功能将切面织入到主业务逻辑中。所谓交叉业务逻辑是指,通用的、与主业务逻辑无关的代码,如安全检查、事务、日志等。
  • 若不使用AOP,则会出现代码纠缠,即交叉业务逻辑与主业务逻辑混合在一起。这样,会使得主业务逻辑变得混杂不清。
  • 例如,转账,在真正转账业务逻辑前后,需要权限控制、日志记录、加载事务、结束事务等交叉业务逻辑,而这些业务逻辑与主业务逻辑之间并无直接关系。但是,它们的代码量所占比重能达到总代码量的一半甚至还多。它们的存在,不仅产生了大量的“亢余”代码,还大大干扰了主业务逻辑--转账。

    2.2 AOP编程术语

    1、切面(Aspect):切面泛指交叉业务逻辑。上例中的事务处理,日志处理就可以理解为切面。常用的切面有通知和顾问。实际就是对主业务逻辑的一种增强。
    2、织入(Weaving):织入是指切面代码插入到目标对象的过程。上例中的MyInvocationHandler类中的invoke()方法完成的工作,就可以称之为织入。
    3、连接点(JointPoint):连接点指的是被切面织入的方法。通常业务接口中的方法均为连接点。
    4、切入点(Pointcut):切入点指的是具体织入的方法,在StudentServiceImpl类中,若doSome()将被增强,而doOther()不被增强,则doSome()为切入点,而doOther仅为连接点。

  • 被标记为final的方法是不能够作为连接点和切入点的。因为最终的是不能够修改的。不能被增强的。
    5、目标对象(Target):目标对象指的是将要被增强的对象。即包含主业务逻辑的类的对象。上例中的StudentServiceImpl的对象若被增强,则该类称之为目标类,该类对象称之为目标对象。当然,不被增强,也就无所谓目标不目标的了。
    6、通知(Advice):通知是切面的一种实现,可以完成简单织入功能(织入功能就是在这里完成的)。上例中的MyInvocationHandler就可以理解为是一种通知。换个角度来说,通知定义了增强代码切入到目标代码的时间点,是目标方法执行之前执行,还是之后执行等。通知类型不同,切入时间不同。
  • 切入点定义切入的位置,通知定义切入的时间。
    7、顾问(Adivisor):顾问是切面的另一种实现,能够将通知以更为复杂的方式织入到目标对象中,是将通知包装为更为复杂切面的装配器。

    2.3 AOP编程环境搭建

    2.3.1 导入Jar包

  • 在原有Spirng基本Jar包的基础上再导入两个Jar包:
  • AOP是由AOP联盟提出的一种编程思想,提出的一套编程规范。而Spring是AOP这套规范的一种实现。所以,需要导入AOP联盟的规范(接口)包以及Spring对其的实现包。
  • AOP联盟包在Spring框架支持库spring-framework-3.0.2.RELEASE-dependencies中的org.aopalliance\com.springsource.org.aopalliance\1.0.0\com.springsource.org.aopalliance-1.0.0.jar。
  • Spring对AOP的实现包中Spring框架的解压包中:spring-aop-4.2.1.RELEASE.jar。

    2.3.2 使用原beans的约束

  • 配置文件的文件头,还使用原来的基础头部就可以,即只包含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"
    xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
    </beans>

    3 通知Advice

  • 通知(Advice),切面的一种实现,可以完成简单织入功能(织入功能就是在这里完成的。)常用通知有:前置通知、后置通知、环绕通知、异常处理通知。

    3.1 通知的用法步骤

  • 对于通知的定义,配置和使用,主要分为以下几步:
    1、定义目标类:定义目标类,就是定义之前的普通Bean类,也就是即将被增强的Bean类。

2、定义通知类:通知类是指,实现了相应通知类型接口的类。当前,实现了这些接口,就要实现这些接口中的方法,而这些方法的执行,则是根据不同类型的通知,其执行时机不同。

  • 前置通知:在目标方法执行之前执行;
  • 后置通知:在目标方法执行之后执行;
  • 环绕通知:在目标方法执行之前与执行之后执行;
  • 异常处理通知:在目标方法执行过程中,若发生指定异常,则执行通知中的方法。

3、注册目标类:即在Spring配置文件中注册目标对象Bean。

         <!-- 配置目标对象 --><bean id="userServiceTarget" class="com.eason.spring4.service.impl.UserServiceImpl"></bean>

4、注册通知切面:即在Spring配置文件中注册定义的通知对象Bean。

        <!-- 配置切面:通知 --><bean id="myMethodBeforeAdvice" class="com.eason.spring4.advice.MyMethodBeforeAdvice"></bean>

5、注册代理工厂Bean类对象ProxyFactoryBean。

        <!-- 配置代理 --><bean id="userServiceProxy" class="org.springframework.aop.framework.ProxyFactoryBean"><property name="target" ref="userServiceTarget"></property><property name="interfaces" value="com.eason.spring4.service.IUserService"></property><property name="interceptorNames" value="myMethodBeforeAdvice"></property></bean>
  • 这里代理的使用的是ProxyFactoryBean类。代理对象的配置,是与JDK的Proxy代理参数是一致的,都需要指定三部分:目标类,接口,切面。
  • 设置目标对象所实现的业务接口,要求给出接口的全限定性类名。此属性可以不进行设置,因为打开ProxyFactoryBean的源码,可以看到其有个自动检测目标类的所有接口属性autodetectInterfaces,默认值为true。即不设置也可以自动检测到。当然,此时使用的是jdk的Proxy动态代理。
  • 如果目标对象没有实现业务接口,则可以不设置。此时使用的是CGLIB动态代理。
  • <property name="interceptorNames" value="通知的id"></property>指定切面,这里指的是通知。注意,这里对于id的指定使用的是value属性,而非ref。因为该属性名为xxxNames,是名称,所以其值为字符串,而非对象。

6、客户端访问动态代理对象:客户端访问的是动态代理对象,而非原目标对象。因为代理对象可以将交叉业务逻辑按照通知类型,动态的织入到目标对象的的执行中。

3.2 通知详解

3.2.1 前置通知MethodBeforeAdvice

  • 定义前置通知,需要实现MethodBeforeAdvice接口。该接口有一个方法before(),会在目标方法执行之前执行。前置通知的特点:
    1、在目标方法执行之前先执行;
    2、不改变目标方法的执行流程,前置通知代码不能阻止目标方法执行;
    3、不改变目标方法执行的结果;
  • 定义业务接口与目标类:
    //业务接口
    public interface IUserService {
    //主业务方法
    void doSome();
    //主业务方法
    void doOther();
    //主业务方法
    void doThird();
    }
    //目标类
    public class UserServiceImpl implements IUserService{
    //目标方法
    @Override
    public void doSome() {System.out.println("执行doSome()");
    }
    //目标方法
    @Override
    public void doOther() {System.out.println("执行doOther()");
    }
    //目标方法
    @Override
    public void doThird() {System.out.println("执行doThird()");
    }
    }
  • 定义前置通知:
    public class MyMethodBeforeAdvice implements MethodBeforeAdvice{
    //  织入的具体过程在这里完成
    /** method:代表业务方法* args:业务方法的参数列表* target:目标对象*/
    @Override
    public void before(Method method, Object[] args, Object target) throws Throwable {System.out.println("执行前置增强");
    }
    }
  • 配置文件配置:
        <!-- 配置目标对象 --><bean id="userServiceTarget" class="com.eason.spring4.service.impl.UserServiceImpl"></bean><!-- 配置切面:通知 --><bean id="myMethodBeforeAdvice" class="com.eason.spring4.advice.MyMethodBeforeAdvice"></bean><!-- 配置代理 --><bean id="userServiceProxy" class="org.springframework.aop.framework.ProxyFactoryBean"><property name="target" ref="userServiceTarget"></property><property name="interfaces" value="com.eason.spring4.service.IUserService"></property><property name="interceptorNames" value="myMethodBeforeAdvice"></property></bean>
  • 对于测试类,需要注意,从容器中获取的是代理对象,而非目标对象。
    @org.junit.Test
    public void test() {        ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");//从Spring容器中获取factoryIUserService userService =  (IUserService) context.getBean("userServiceProxy");userService.doSome();userService.doOther();userService.doThird();
    }
  • 查看后台运行情况,可以看到代理生成使用的是JDK代理机制。

    3.2.2 后置通知AfterReturningAdvice

  • 定义后置通知,需要实现接口AfterReturningAdvice。该接口中有一个方法afterReturning(),会在目标方法执行之后执行。后置通知的特点:
    1、在目标方法执行之后执行;
    2、不改变目标方法的执行流程,后置通知代码不能阻止目标方法执行;
    3、不改变目标方法执行的结果;
  • 修改业务接口和实现类:
    //业务接口
    public interface IUserService {
    //主业务方法
    void doSome();
    //主业务方法
    int doOther(int a);
    //主业务方法
    void doThird();
    }
    //目标类
    public class UserServiceImpl implements IUserService{
    //目标方法
    @Override
    public void doSome() {System.out.println("执行doSome()");
    }
    //目标方法
    @Override
    public int doOther(int a) {System.out.println("执行doOther()");return a;
    }
    //目标方法
    @Override
    public void doThird() {System.out.println("执行doThird()");
    }
    }
  • 定义切面:通知

    public class MyAfterReturningAdvice implements AfterReturningAdvice{
    //returnValue:目标方法的返回值。目标方法若没有返回值,该值为null
    @Override
    public void afterReturning(Object returnValue, Method method,Object[] args, Object target) throws Throwable {System.out.println("执行后置增强:" + returnValue);}
    }
  • 配置文件配置
        <!-- 配置目标对象 --><bean id="userServiceTarget" class="com.eason.spring4.service.impl.UserServiceImpl"></bean><!-- 配置切面:通知 --><bean id="myAfterReturningAdvice" class="com.eason.spring4.advice.MyAfterReturningAdvice"></bean><!-- 配置代理 --><bean id="userServiceProxy" class="org.springframework.aop.framework.ProxyFactoryBean"><property name="target" ref="userServiceTarget"></property><property name="interfaces" value="com.eason.spring4.service.IUserService"></property><property name="interceptorNames" value="myAfterReturningAdvice"></property></bean>
  • 测试类:
    @org.junit.Test
    public void test() {        ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");//从Spring容器中获取factoryIUserService userService =  (IUserService) context.getBean("userServiceProxy");userService.doSome();userService.doOther(2);userService.doThird();
    }

    3.2.3 环绕通知MethodInterceptor

  • 定义环绕通知。需要实现MethodInterceptor接口。环绕通知,也叫方法拦截器,可以在目标方法调用之前以及之后做处理,可以改变目标方法的返回值,也可以改变程序执行流程。
  • 需要注意,org.aopalliance.intercept.MethodInterceptor才是需要的包
    //定义环绕通知,即方法拦截器
    public class MyMethodInterceptor implements MethodInterceptor {
    @Override
    public Object invoke(MethodInvocation invocation) throws Throwable {System.out.println("执行前的增强");//执行目标方法Object result = invocation.proceed();if(result != null) {result = (Integer)result + 10;}System.out.println("执行后的增强");return result;
    }
    }
        <!-- 配置目标对象 --><bean id="userServiceTarget" class="com.eason.spring4.service.impl.UserServiceImpl"></bean><!-- 配置切面:通知 --><bean id="myMethodInterceptor" class="com.eason.spring4.advice.MyMethodInterceptor"></bean><!-- 配置代理 --><bean id="userServiceProxy" class="org.springframework.aop.framework.ProxyFactoryBean"><property name="target" ref="userServiceTarget"></property><property name="interfaces" value="com.eason.spring4.service.IUserService"></property><property name="interceptorNames" value="myMethodInterceptor"></property></bean>
    @org.junit.Test
    public void test() {        ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");//从Spring容器中获取factoryIUserService userService =  (IUserService) context.getBean("userServiceProxy");userService.doSome();int result = userService.doOther(2);System.out.println(result);userService.doThird();
    }

    3.2.4 异常通知 ThrowsAdvice

  • 定义异常通知,需要实现ThrowsAdvice接口。该接口的主要作用是,在目标方法抛出异常后,根据异常的不同做出相应的处理。当该接口处理完毕异常后,会简单地将异常再次抛出给目标方法。
  • 不过,这个接口较为特殊,从形式上看,该接口中没有必须要实现的方法,但是,这个接口却确实有必须要实现的方法afterThrowing()。这个方法重载了四种形式。由于使用时,一般只使用其中的一种,若要都定义在接口中,则势必要使程序员在使用时实现这四个方法,这是很麻烦的。所以就将该接口定义为了标识接口(没有方法的接口)。
  • 这四个方法在打开ThrowsAdvice源码后,上侧的注释部分可以看到:
  • 不过,在这四种形式中,常用的形式如下:
  • public void afterThrowing(自定义的异常类 e),这里的参数e为,与具体业务相关的用户自定义的异常类对象。容器会根据异常类型的不同,自动选择不同的方法执行。这些方法的执行时在目标方法执行结束后执行的。
  • 其他参数则与前面两个通知中方法的参数意义相同。
  • 举例:本例实现用户身份验证,当用户名不正确时,抛出用户名有误异常;当密码不正确时,抛出密码有误异常。当然,在抛出这些异常后,都要做一些其他处理。
    1、定义异常类的父类:

    public class UserException extends Exception{
    public UserException() {
    }
    public UserException(String message) {super(message);
    }
    }

    2、定义两个异常类的子类:

    public class UsernameException extends UserException {
    public UsernameException() {super();
    }
    public UsernameException(String message) {super(message);
    }
    }
    public class PasswordException extends UserException {
    public PasswordException() {super();
    }
    public PasswordException(String message) {super(message);
    }
    }

    3、定义业务接口,要抛出异常父类:

    //业务接口
    public interface IUserService {
    //业务方法
    boolean check(String username, String password) throws UserException;
    }

    4、定义目标类:

    //目标类
    public class UserServiceImpl implements IUserService{
    //定义目标方法
    @Override
    public boolean check(String username, String password) throws UserException {if(!"beijing".equals(username)) {throw new UsernameException("用户名有误!");}if(!"111".equals(password)) {throw new PasswordException("密码有误!");}System.out.println("用户身份合法!");return true;
    }
    }

    5、定义异常通知:

    public class MyThrowsAdvice implements ThrowsAdvice {
    public void afterThrowing(UsernameException e) {System.out.println("异常通知捕获到UsernameException," + e.getMessage());
    }
    public void afterThrowing(PasswordException e) {System.out.println("异常通知捕获到PasswordException," + e.getMessage());
    }
    }

    6、配置文件配置:

        <!-- 配置目标对象 --><bean id="userServiceTarget" class="com.eason.spring4.service.impl.UserServiceImpl"></bean><!-- 配置切面:通知 --><bean id="myThrowsAdvice" class="com.eason.spring4.advice.MyThrowsAdvice"></bean><!-- 配置代理 --><bean id="userServiceProxy" class="org.springframework.aop.framework.ProxyFactoryBean"><property name="target" ref="userServiceTarget"></property><property name="interfaces" value="com.eason.spring4.service.IUserService"></property><property name="interceptorNames" value="myThrowsAdvice"></property></bean>

    7、测试类:

    @org.junit.Test
    public void test() throws UserException {       ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");//从Spring容器中获取factoryIUserService userService =  (IUserService) context.getBean("userServiceProxy");boolean result = userService.check("beijing", "111");System.out.println("result = " + result);
    }

    3.3 通知的其他用法

    3.3.1 给其他方法织入多个切面

  • 若要给目标方法织入多个切面,则需要在配置代理对象的切面属性时,设定为list。
        <!-- 配置目标对象 --><bean id="userServiceTarget" class="com.eason.spring4.service.impl.UserServiceImpl"></bean><!-- 配置切面:通知 --><bean id="myMethodBeforeAdvice" class="com.eason.spring4.advice.MyMethodBeforeAdvice"></bean><bean id="MyAfterReturningAdvice" class="com.eason.spring4.advice.MyAfterReturningAdvice"></bean><!-- 配置代理 --><bean id="userServiceProxy" class="org.springframework.aop.framework.ProxyFactoryBean"><property name="target" ref="userServiceTarget"></property><property name="interfaces" value="com.eason.spring4.service.IUserService"></property><property name="interceptorNames"><list><value>myMethodBeforeAdvice</value>                                                   <value>MyAfterReturningAdvice</value></list></property></bean>

    3.3.2 无接口的CGLIB代理生成

  • 若不存在接口,则ProxyFactoryBean会自动采用CGLIB方式生成动态代理。
  • 查看后台运行情况,可以看到代理生成使用的是CGLIB代理机制。

    3.3.2 无接口的CGLIB代理生成

  • 若存在接口,但是有需要使用CGLIB生成代理对象,此时,只需要在配置文件中增加一个proxyTargetClass属性设置,用于指定强制使用CGLIB代理机制。
        <!-- 配置目标对象 --><bean id="userServiceTarget" class="com.eason.spring4.service.impl.UserServiceImpl"></bean><!-- 配置切面:通知 --><bean id="myThrowsAdvice" class="com.eason.spring4.advice.MyThrowsAdvice"></bean><!-- 配置代理 --><bean id="userServiceProxy" class="org.springframework.aop.framework.ProxyFactoryBean"><property name="target" ref="userServiceTarget"></property><property name="interfaces" value="com.eason.spring4.service.IUserService"></property><property name="interceptorNames" value="myThrowsAdvice"></property><!-- 指定是否对类进行代理 --><property name="proxyTargetClass" value="true"></property></bean>
  • 也可以指定optimize(优化)的值为true,强制使用CGLIB代理机制。
        <!-- 配置代理 --><bean id="userServiceProxy" class="org.springframework.aop.framework.ProxyFactoryBean"><property name="target" ref="userServiceTarget"></property><property name="interfaces" value="com.eason.spring4.service.IUserService"></property><property name="interceptorNames" value="myThrowsAdvice"></property><!-- 指定是否对类进行代理 --><property name="optimize" value="true"></property></bean>
  • 查看后台运行情况,可以看到代理生成使用的是CGLIB代理机制。

    4 顾问Advisor

  • 通知(Advice)是Spring提供的一种切面(Aspect)。但其功能过于简单:只能将切面织入到目标类的所有目标方法中,无法完成将切面织入到指定目标方法中。
  • 顾问(Advisor)是Spring提供的另一种切面。其可以完成更为负责的切面织入功能。PointAdvisor是顾问的一种,可以指定具体的切入点。顾问将通知进行了包装,会根据不同的通知类型,在不同的时间点,将切面织入到不同的切入点中。
  • PointAdvisor接口有两个较为常用的实现类:
    1、NameMatchMethodPointcutAdvisor名称匹配方法切入点顾问;
    2、RegexMethodPointcutAdvisor正则表达式匹配方法切入点顾问;

    4.1 名称匹配方法切入点顾问

  • NameMatchMethodPointcutAdvisor,即名称匹配方法切入点顾问。容器可根据配置文件中指定的方法名来设置切入点。
  • 使用3.2.1节前置通知用例,代码不用修改,只在配置文件中注册一个顾问,然后使用通知属性advice与切入点的方法名mappedName对其进行匹配。代理中的切面,使用这个顾问即可。
        <!-- 配置目标对象 --><bean id="userServiceTarget" class="com.eason.spring4.service.impl.UserServiceImpl"></bean><!-- 配置通知 --><bean id="myMethodBeforeAdvice" class="com.eason.spring4.advice.MyMethodBeforeAdvice"></bean><!-- 配置切面:顾问 --><bean id="myAdvisor" class="org.springframework.aop.support.NameMatchMethodPointcutAdvisor"><property name="advice" ref="myMethodBeforeAdvice"></property><property name="mappedNames" value="doSome,doOther"></property></bean><!-- 配置代理 --><bean id="userServiceProxy" class="org.springframework.aop.framework.ProxyFactoryBean"><property name="target" ref="userServiceTarget"></property><property name="interfaces" value="com.eason.spring4.service.IUserService"></property><property name="interceptorNames" value="myAdvisor"></property></bean>
  • 对于切入点的指定,有多种方式:


    4.2 正则表达式方法切入点顾问

  • RegexMethodPointcutAdvisor,即正则表达式方法顾问。容器可以根据正则表达式来设置切入点。注意,与正则表达式进行匹配的对象是接口中的方法名,而非目标类(接口的实现类)的方法名。
  • 使用3.2.1节前置通知用例,代码不用修改。
            <!-- 配置目标对象 --><bean id="userServiceTarget" class="com.eason.spring4.service.impl.UserServiceImpl"></bean><!-- 配置通知 --><bean id="myMethodBeforeAdvice" class="com.eason.spring4.advice.MyMethodBeforeAdvice"></bean><!-- 配置切面:顾问 --><bean id="myAdvisor" class="org.springframework.aop.support.RegexpMethodPointcutAdvisor"><property name="advice" ref="myMethodBeforeAdvice"></property><property name="pattern" value=".*doS.*"></property></bean><!-- 配置代理 --><bean id="userServiceProxy" class="org.springframework.aop.framework.ProxyFactoryBean"><property name="target" ref="userServiceTarget"></property><property name="interfaces" value="com.eason.spring4.service.IUserService"></property><property name="interceptorNames" value="myAdvisor"></property></bean>

  • 这里正则表达式常用的运算符有三个,如下表:

    5 自动代理生成器

  • 前面代码中所使用的代理对象,均是由ProxyFactoryBean工具类生成的。而该代理工具类存在如下缺点:
    1、一个代理对象只能够代理一个Bean,即如果有两个Bean同时都要织入同一个切面,这时,不仅要配置这两个Bean,即两个目标对象,同时还要配置两个代理对象。
    2、在客户类中获取Bean时,使用的是代理类的id,而非我们定义的目标对象Bean的id。我们真正想要执行的应该是目标对象。从形式上看,不符合正常的逻辑。
  • Spring提供了自动代理生成器,用于解决ProxyFactoryBean的问题。常用的自动代理生成器有两个:
    1、默认advisor自动代理生成器;
    2、Bean名称自动代理生成器;
  • 需要注意的是,自动代理生成器均继承自Bean后处理器BeanPostProcessor。容器中所有Bean在初始化时均会自动执行Bean后处理器中的方法,故其无需id属性。所以自动代理生成器的Bean也没有id属性,客户类直接使用目标对象bean的id。
  • 自动代理生成器,均是继承自BeanPostProcessor,Bean后处理器,查看源码:

    5.1 默认advisor自动代理生成器

  • DefaultAdvisorAutoProxyCreator代理的生成方式是,将所有对象与Advisor自动结合,生成代理对象。无需给生成器做任何的注入配置。注意,只能与Advisor配合使用。
  • 这种代理的配置很简单,如下所示:

            <!-- 配置目标对象 --><bean id="userServiceTarget" class="com.eason.spring4.service.impl.UserServiceImpl"></bean><!-- 配置通知 --><bean id="myMethodBeforeAdvice" class="com.eason.spring4.advice.MyMethodBeforeAdvice"></bean><!-- 配置切面:顾问 --><bean id="myAdvisor" class="org.springframework.aop.support.NameMatchMethodPointcutAdvisor"><property name="advice" ref="myMethodBeforeAdvice"></property><property name="mappedName" value="doSome"></property></bean><!-- 配置代理 --><bean id="userServiceProxy" class="org.springframework.aop.framework.ProxyFactoryBean"><property name="target" ref="userServiceTarget"></property><property name="interfaces" value="com.eason.spring4.service.IUserService"></property><property name="interceptorNames" value="myAdvisor"></property></bean><bean class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator"></bean>

    5.2 Bean名称自动代理生成器

  • DefaultAdvisorAutoProxyCreator会为每一个目标对象织入所有匹配的Advisor,不具有选择性,且切面只能是顾问Advisor。而BeanNameAutoProxyCreator的代理生成方式是,根据bean的id,来为符合相应名称的类生成相应代理对象,且切面既可以是顾问Advisor又可以是通知Advice。
  • 注意,只需要修改配置文件中的代理生成配置,以及测试类中的通过getBean()获取的bean的id为目标类beanId即可。
    //目标类
    public class UserServiceImpl implements IUserService{
    //目标方法
    @Override
    public void doSome() {System.out.println("user:执行doSome()");
    }
    //目标方法
    @Override
    public void doOther() {System.out.println("user:执行doOther()");
    }
    //目标方法
    @Override
    public void doThird() {System.out.println("user:执行doThird()");
    }
    }
    //目标类
    public class TeacherServiceImpl implements IUserService{
    //目标方法
    @Override
    public void doSome() {System.out.println("teacher:执行doSome()");
    }
    //目标方法
    @Override
    public void doOther() {System.out.println("teacher:执行doOther()");
    }
    //目标方法
    @Override
    public void doThird() {System.out.println("teacher:执行doThird()");
    }
    }
        <!-- 配置目标对象 --><bean id="userServiceTarget" class="com.eason.spring4.service.impl.UserServiceImpl"></bean><bean id="teacherServiceImpl" class="com.eason.spring4.service.impl.TeacherServiceImpl"></bean><!-- 配置通知 --><bean id="myMethodBeforeAdvice" class="com.eason.spring4.advice.MyMethodBeforeAdvice"></bean><bean id="myAfterReturningAdvice" class="com.eason.spring4.advice.MyAfterReturningAdvice"></bean><!-- 配置切面:顾问 --><bean id="myBeforeAdvisor" class="org.springframework.aop.support.NameMatchMethodPointcutAdvisor"><property name="advice" ref="myMethodBeforeAdvice"></property><property name="mappedName" value="doSome"></property></bean><bean id="myAfterAdvisor" class="org.springframework.aop.support.NameMatchMethodPointcutAdvisor"><property name="advice" ref="myAfterReturningAdvice"></property><property name="mappedName" value="doSome"></property></bean><!-- 配置自动代理 --><bean class="org.springframework.aop.framework.autoproxy.BeanNameAutoProxyCreator"><property name="beanNames" value="userServiceTarget,teacherServiceImpl"></property><property name="interceptorNames" value="myMethodBeforeAdvice,myAfterReturningAdvice"></property></bean>
  • beanNames:指定要增强的目标类的id。
  • interceptorNames:指定切面。可以是顾问Advisor,也可以是通知Advice。
    @org.junit.Test
    public void test() {        ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");//从Spring容器中获取factoryIUserService userService =  (IUserService) context.getBean("userServiceTarget");userService.doSome();userService.doOther();userService.doThird();IUserService teacherService =  (IUserService) context.getBean("teacherServiceImpl");teacherService.doSome();teacherService.doOther();teacherService.doThird();
    }
  • 对于自动代理生成器在配置时,需要注意,即使只有一个目标对象,属性也为beanNames,且对于Bean的名称的指定,可以使用通配符*号。
        <!-- 配置自动代理 --><bean class="org.springframework.aop.framework.autoproxy.BeanNameAutoProxyCreator"><property name="beanNames" value="teacherServiceImpl"></property><property name="interceptorNames" value="myMethodBeforeAdvice,myAfterReturningAdvice"></property></bean>
        <!-- 配置自动代理 --><bean class="org.springframework.aop.framework.autoproxy.BeanNameAutoProxyCreator"><property name="beanNames" value="*Service*"></property><property name="interceptorNames" value="myMethodBeforeAdvice,myAfterReturningAdvice"></property></bean>
  • 当然,对于多个Bean名称的情况,还可以使用<list/>标签的形式
        <!-- 配置自动代理 --><bean class="org.springframework.aop.framework.autoproxy.BeanNameAutoProxyCreator"><property name="beanNames"><list><value>userServiceTarget</value><value>teacherServiceImpl</value></list></property><property name="interceptorNames" value="myMethodBeforeAdvice,myAfterReturningAdvice"></property></bean>

    6 AscpectJ对AOP的实现

  • 对于AOP这种编程思想,很多框架都进行了实现。Spring就是其中之一,可以完成面向切面编程。然而,AspectJ也实现了AOP的功能,且其实现方式更为简洁,使用更为方便,而且还支持注解式开发。所以,Spring又将AspectJ的对于AOP的实现也引入到了自己的框架中。
  • 在Spring中使用AOP开发时,一般使用AspectJ的实现方式。

    6.1 AscpectJ简介

  • 百度百科关于AspectJ介绍:

    6.2 AspectJ的通知类型

  • AscpectJ中常用的通知有五种类型:
    1、前置通知;
    2、后置通知;
    3、环绕通知;
    4、异常通知;
    5、最终通知;
  • 其中最终通知是指,无论程序程序执行是否正常,该通知都会执行。类似于try...catch中的finally代码块。

    6.3 AspectJ的切入点表达式

  • AspectJ除了提供了六种通知外,还定义了专门的表达式用于指定切入点。表达式的原型为:
    execution ([modifiers-pattern] 访问权限类型 ret-type-pattern 返回值类型 [declaring-type-pattern] 全限定性类名  name-pattern(param-pattern) 方法名(参数名)  [throws-pattern] 抛出异常类型)
  • 切入点表达式要匹配的对象就是目标方法的方法名。所以,execution表达式中明显就是方法的签名。注意,表达式中加入[]的部分表示可忽略部分,各部分间用空格分开。在其中可以使用以下符号:

    举例:
    1、execution(public (..))指定切入点为:任意公共方法
    2、execution( set (..))指定切入点为:任何一个以”set”开始的方法。
    3、execution( com.xyz.service..())指定切入点为:定义在service包中的任意类的任意方法。
    4、execution(
    com.xyz.service...(..))指定切入点为:定义在service包或者子包里的任意类的任意方法。“..”出现在类名中时,后面必须跟“”,表示包、子包中的所有类。
    5、execution(
    .service..(..))指定切入点为:只有一级包下的service子包下所有类(接口)中所有方法为切入点。
    6、execution(
    ...Service..(..))指定切入点为:指定是所有包下的service子包下的所有类(接口)中的所有方法为切入点。
    7、execution(
    .ISomeService.(..)):指定只有一级包下的ISomeService接口中所有方法为切入点。
    8、execution( ..ISomeService.(..)):指定所有包下的ISomeService接口中所有方法为切入点。
    9、execution(
    com.xyz.service.IAccountService.(..))指定切入点为:IAccountService接口中的任意方法。
    10、execution(
    com.xyz.service.IAccountService+.(..))指定切入点为:IAccountService若为接口,则为接口中的任意方法以及所有实现类中的任意方法;若为类,则为该类以及子类中的任意方法。
    11、execution(
    joke(String,int))指定切入点为:所有的joke(String,int)方法,且joke()方法的第一个参数是String,第二个参数是int。如果方法中的参数类型是java.lang包中的类,可以直接使用类名,否则必须使用全限定性类名,如joke(java.util.List, int)。
    12、execution( joke(String, ))指定切入点为:所有的joke()方法,该方法第一个参数为String,第二个参数可以是任意类型,如joke(String s1, String s2)和joke(String s1, double d2)都是可以的,但是joke(String s1, double d2, String s3)不是。
    13、execution( joke(String,..))指定切入点为:所有的joke()方法,该方法第一个参数为String,后面可以有任意个参数且参数类型不限,如joke(String s1)、joke(String s1,String s2)和joke(String s1,double d2,Strings3)都是。
    14、execution(
    joke(Object))指定切入点为:所有的joke()方法,方法拥有一个参数,且参数是Object类型。joke(Object ob)是,但是,joke(String s)与joke(User u)均不是。
    15、execution(* joke(Object+))指定切入点为:所有的joke()方法,方法拥有一个参数,且参数Object类型或者该类的子类。不仅joke(Object ob)是,joke(String s)与joke(User u)也是。

    6.4 AspectJ的开发环境

    6.4.1 导入两个jar包

  • AspectJ是专门针对AOP问题的,所以其运行时需要AOP环境的,即需要之前的AOP的两个jar包。另外,还需要AscpectJ自身的jar包:在Spring支持库解压目录中的子目录org.aspectJ下有两个子包:
  • 一般情况下,使用weaver包中的jar即可。tools中的jar除了包含weaver中类库外,还包含了其他工作,但是一般不用。所以,使用weaver包中的jar即可。
  • 当然,在Spring中使用AspectJ,还需要将它们联系在一起的整合jar包。在Spring框架解压目录的libs中。

    6.4.2 引入AOP约束

  • 在配置文件头部,要引入关于aop的约束。在Spring框架的解压目录中,\docs\spring-framework-reference\html下的xsd-configuration.html文件中。
  • 在前面Spring实现AOP时,并未引入AOP的约束,而在AspectJ实现AOP时,才提出要引入AOP的约束。说明,配置文件中使用的AOP约束中的标签,均是AspectJ框架使用的,而非Spring框架本身在实现AOP时使用的。
  • AspectJ对于AOP的实现有两种方式:1、注解方式;2、XML方式;

    6.5 AspectJ基于注解的AOP实现

  • AspectJ提供了以注解方式对于AOP的实现。

    6.5.1 实现步骤

    1、定义业务接口与实现类:

    public interface IUserService {
    //主业务方法
    void doSome();
    //主业务方法
    void doOther();
    //主业务方法
    String doThird();
    }
    //目标类
    public class UserServiceImpl implements IUserService{
    //目标方法
    @Override
    public void doSome() {System.out.println("user:执行doSome()");
    }
    //目标方法
    @Override
    public void doOther() {System.out.println("user:执行doOther()");
    }
    //目标方法
    @Override
    public String doThird() {System.out.println("user:执行doThird()");return "333";
    }
    }

    2、定义切面POJO类:需要在切面类上添加@Aspect注解,以指定当前POJO类将作为切面;

  • 同时需要在POJO类的普通方法上添加通知注解。
  • 切面类是用于定义增强代码的,即用于定义增强目标类中目标方法的增强方法。这些增强方法使用不同的“通知”注解,会在不同的时间点完成织入。当然,对于增强代码,还要通过execution表达式指定具体应用的目标类和目标方法,即切入点。
    @Aspect
    public class MyAspect {
    @Before("execution(* *..UserServiceImpl.doSome())")
    public void beforeSome() {System.out.println("前置增强");
    }
    @AfterReturning(value="execution(* *..UserServiceImpl.doThird())")
    public void afterReturning() {System.out.println("后置增强");
    }
    }

    5、注册目标对象和POJO切面类:

    <!-- 配置目标对象 -->
    <bean id="myUserService" class="com.eason.spring4.service.impl.UserServiceImpl"></bean>
    <!-- 配置切面 -->
    <bean id="myAspect" class="com.eason.spring4.aspect.MyAspect"></bean>

    6、注册AspectJ的自动代理:

  • 在定义好切面Aspect后,需要通知Spring容器,让容器生成“目标类 + 切面”的代理对象。这个代理是由容器自动生成的。只需要在Spring配置文件中注册一个基于aspectJ的自动代理生成器,其就会自动扫描到@Aspect注解,并按通知类型与切入点,将其织入,并生成代理。
    <!-- 配置aspectJ的自动代理 -->
    <aop:aspectj-autoproxy></aop:aspectj-autoproxy>
  • <aop:aspectj-autoproxy/>的底层是由AnnotationAwareAspectJAutoProxyCreator实现的。从其类名就可以看出,是基于AspectJ的注释适配自动代理生成器。
  • 其工作原理是,<aop:aspectj-autoproxy/>通过扫描找到@Aspect定义的切面类,再由切面类根据切入点找到目标类的目标方法,再由通知类型找到切入的时间点。
    7、测试类中使用目标对象的id:

    @org.junit.Test
    public void test() {        ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");//从Spring容器中获取factoryIUserService userService =  (IUserService) context.getBean("myUserService");userService.doSome();
    }

    6.5.2 @Before前置通知-方法有JointPoint参数

  • 在目标方法执行之前执行。被注解为前置通知的方法,可以包含一个JoinPoint类型参数。该类型的对象本身就是切入点表达式。通过该参数,可以获取切入点表达式、方法签名、目标对象等。
  • 不光前置通知的方法,可以包含一个JointPoint类型参数,所有的通知方法均可包含该参数。

    @Before("execution(* *..UserServiceImpl.doSome())")
    public void beforeSome() {System.out.println("前置增强");
    }
    @Before("execution(* *..UserServiceImpl.doOther())")
    public void beforeOther(JoinPoint jp) {System.out.println("前置增强(切入点表达式为):" + jp);System.out.println("前置增强(方法签名为):" + jp.getSignature());System.out.println("前置增强(目标对象为):" + jp.getTarget());Object[] args = jp.getArgs();if(args.length != 0){System.out.println("前置增强(方法参数为):");for(Object object : args) {System.out.println(object + " ");}}
    }

    6.5.3 @AfterReturning后置通知-注解有returning属性

  • 在目标方法执行之后执行。由于是目标方法之后执行,所以可以获取到目标方法的返回值。该注解的returning属性就是用于指定接收方法返回值的变量名的。所以,被注解为后置通知的方法,除了可以包含JoinPoint参数外,还可以包含用于接收返回值的变量。该变量最好为Object类型,因为目标方法的返回值可能是任何类型。
    @AfterReturning(value="execution(* *..UserServiceImpl.doThird())" ,returning="result")
    public void afterReturning(Object result) {System.out.println("后置增强,目标方法执行结果为:" + result);
    }

    6.5.4 @Around环绕通知-增强方法由ProceedingJoinPoint参数

  • 在目标方法执行之前之后执行。被注解为环绕增强的方法要有返回值,Object类型。并且方法可以包含一个ProceedingJoinPoint类型的参数。接口ProceedingJoinPoint其由一个proceed()方法,用于执行目标方法。若目标方法有返回值,则该方法的返回值就是目标方法的返回值。最后,环绕增强方法将其返回值返回。该增强方法实际是拦截了目标方法的执行。
    @Around("execution(* *..UserServiceImpl.doThird())")
    public Object around(ProceedingJoinPoint pjp) throws Throwable {System.out.println("环绕通知:前");Object result = pjp.proceed();System.out.println("环绕通知:后");return result;
    }

    6.5.5 @AfterThrowing异常通知-注解中有throwing属性

  • 在目标方法抛出异常后执行。该注解的throwing属性用于指定所发生的异常类对象。当然,被注解为异常通知的方法可以包含一个参数Throwable,参数名称为throwing指定的名称,表示发生的异常对象。
    //目标方法
    @Override
    public String doThird() {System.out.println("user:执行doThird()" + (3/0));return "333";
    }
    @AfterThrowing(value="execution(* *..UserServiceImpl.doThird())", throwing="e")
    public void afterThrowing(Throwable e) throws Throwable {System.out.println("异常通知:" + e.getMessage());
    }

    6.5.6 @After最终通知

  • 无论目标方法是否抛出异常,该增强均会被执行。
    @After("execution(* *..UserServiceImpl.doThird())")
    public void after() {System.out.println("最终方法执行");
    }

    6.5.7 @Pointcut定义切入点

  • 当较多的通知增强方法使用相同的execution切入点表达式时,编写、维护均较为麻烦。AspectJ提供了@Pointcut注解,用于定义execution切入点表达式。
  • 其用法是,将@Pointcut注解在一个方法之上,以后所有的execution的value属性值均可使用该方法名作为切入点。代表的就是@Pointcut定义的切入点。这个使用@Pointcut注解的方法一般使用private的标识方法,即没有实际作用的方法。
    @After(value="mypointcut()")
    public void after() {System.out.println("最终方法执行");
    }
    @Pointcut("execution(* *..UserServiceImpl.doThird())")
    private void mypointcut() {}

    6.6 AspectJ基于注解的AOP的实现

  • AspectJ除了提供了基于注解的AOP外,还提供了以XML方式的实现。
  • 切面就是一个POJO类,而用于增强的方法就是普通的方法。通过配置文件,将切面中的功能增强织入到目标类的目标方法中。

    6.6.1 实现步骤

    1、定义业务接口和实现类:

    public interface IUserService {
    //主业务方法
    void doSome();
    //主业务方法
    void doOther(int a, String b);
    //主业务方法
    String doThird();
    }
    //目标类
    public class UserServiceImpl implements IUserService{//目标方法@Overridepublic void doSome() {System.out.println("user:执行doSome()");}//目标方法@Overridepublic void doOther(int a, String b) {System.out.println("user:执行doOther()");}//目标方法@Overridepublic String doThird() {System.out.println("user:执行doThird()");return "333";}
    }

    2、定义切面POJO类:

  • 该类为一个POJO类,将作为切面出现,其中定义了若干普通方法,将作为不同的通知方法。
    public class MyAspect {
    public void before() {System.out.println("前置增强");
    }
    public void afterReturning(Object result) {System.out.println("后置增强:目标方法执行结果为:" + result);
    }
    //other method
    }

    3、注册目标对象和POJO切面类

    <!-- 配置目标对象 -->
    <bean id="myUserService" class="com.eason.spring4.service.impl.UserServiceImpl"></bean>
    <!-- 配置切面 -->
    <bean id="myAspect" class="com.eason.spring4.aspect.MyAspect"></bean>

    4、在容器中定义AOP配置:

    <!-- 配置AOP -->
    <aop:config><!-- 定义切入点 --><aop:pointcut expression="execution(* *..UserServiceImpl.doSome())" id="mydoSomePointcut"/><aop:pointcut expression="execution(* *..UserServiceImpl.doOther(..))" id="mydoOtherPointcut"/><aop:pointcut expression="execution(* *..UserServiceImpl.doThird())" id="mydoThirdPointcut"/><!-- 定义切面 --><aop:aspect ref="myAspect"><aop:before method="before" pointcut-ref="mydoSomePointcut"/><aop:after-returning method="afterReturning" pointcut-ref="mydoThirdPointcut" returning="result"/></aop:aspect>
    </aop:config>
  • 配置文件中,除了要定义目标类和切面的Bean外,最主要的是在<aop:config/>中进行aop的配置。而该标签的底层,会根据其子标签的配置,生成自动代理。
  • 通过其子标签<aop:pointcut/>定义切入点,该标签有两个属性,id与expression。分别用于指定该切入点的名称以及切入点的值。expression的值为execution表达式。
  • 通过子标签<aop:aspect/>定义具体的织入规则:method指定该通知使用的切面中的增强方法;pointcut-ref指定该通知要应用的切入点。
  • Aspect的6种通知的XML标签如下:
    <aop:before/>前置通知;<aop:after-returning/>后置通知;<aop:after-throwing/>异常通知;<aop:around>环绕通知;<aop:after/>最终通知;<aop:declare-parents/>引入通知。
    5、测试类中使用目标对象的id

        @org.junit.Testpublic void test() {        ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");//从Spring容器中获取factoryIUserService userService =  (IUserService) context.getBean("myUserService");userService.doSome();userService.doOther(5, "abc");String result = userService.doThird();System.out.println("doThird result = " + result);}

    6.6.2 <aop:before/>前置通知

  • 选择重载的方法:在method属性赋值时,不仅可以放方法名,还可以放入方法的参数类型。
    public void before() {System.out.println("前置增强");
    }
    public void before(JoinPoint jp) {System.out.println("前置增强:" + jp);
    }
    <aop:aspect ref="myAspect"><aop:before method="before" pointcut-ref="mydoSomePointcut"/><aop:before method="before(org.aspectj.lang.JoinPoint)" pointcut-ref="mydoSomePointcut"/>
    </aop:aspect>

    6.6.3 <aop:after-returning/>后置通知

  • 其xml的配置中,有一个属性returning,指定用于接收目标方法的返回值所使用的变量名。其可作为增强方法的参数出现。
    public void afterReturning(Object result) {System.out.println("后置增强:目标方法执行结果为:" + result);
    }   
    <aop:aspect ref="myAspect">
    <aop:after-returning method="afterReturning" pointcut-ref="mydoThirdPointcut" returning="result"/>
    </aop:aspect>

    6.6.4 <aop:around/>环绕通知

  • 环绕通知的增强方法一般返回类型为Object,是目标方法的返回值。并且可以包含一个参数ProceedingJoinPoint,其方法proceed()可执行目标方法。
    <aop:aspect ref="myAspect">
    <aop:around method="around" pointcut-ref="mydoSomePointcut"/>
    </aop:aspect>
    //定义环绕通知的增强方法
    public Object around(ProceedingJoinPoint pjp) throws Throwable{
    System.out.println("环绕通知:前");
    Object result = pjp.proceed();
    System.out.println("环绕通知:后");
    return result;
    }

    6.6.5 <aop:after-throwing/>异常通知

  • 其XML的配置中,有一个属性throwing,指定用于接收目标方法所抛出异常的变量名。其可作为增强方法的参数出现,该参数为Throwable类型。
    //目标方法
    @Override
    public String doThird() {System.out.println("user:执行doThird()" + (2/0));return "333";
    }
    //定义异常通知的增强方法
    public void afterThrowing(Throwable e) {System.out.println("异常通知:" + e.getMessage());
    }
    <aop:aspect ref="myAspect">
    <aop:after-throwing method="afterThrowing" pointcut-ref="mydoThirdPointcut" throwing="e"/>
    </aop:aspect>

    6.6.6 <aop:after/>最终通知

    //定义最终通知的增强方法
    public void after() {System.out.println("最终方法执行");
    }
    <aop:aspect ref="myAspect">
    <aop:after method="after" pointcut-ref="mydoThirdPointcut"/>
    </aop:aspect>

转载于:https://blog.51cto.com/12402717/2090526

SSH框架之Spring4专题3:Spring与AOP相关推荐

  1. SSH框架之Spring4专题4:Spring与DAO

    本专题内容主要包含两部分:Spring所使用的操作数据库的技术之一,JDBC模版的使用:另一部分则为Spring对于事务的管理. Spring与Dao部分,是Spring的两大核心技术loC与AOP的 ...

  2. 框架源码专题:Spring的Aop实现原理,Spring AOP 与 AspectJ 的关系

    文章目录 1. Spring AOP 与 AspectJ 的关系 2. JDK和Cglib动态代理的区别 3. Spring AOP应用案例 4. Spring AOP有几种配置方式? 5. Spri ...

  3. SSH框架整合过程(Struts2+Spring+Hibernate)

    (尊重劳动成果,转载请注明出处:http://blog.csdn.NET/qq_25827845/article/details/53929601 冷血之心的博客) 在学习java框架的过程中,我总结 ...

  4. 框架源码专题:Spring声明式事务Transactional的原理

    文章目录 1. @Transactional的使用 2. spring事务的原理 2.1 开启事务,注册bean的后置处理器和相关bean对象,封装Advisor 2.2 匹配并创建动态代理 2.3 ...

  5. SSH框架搭建 笔记 (含spring注解驱动)

    分类: web 开发2014-04-27 12:33 354人阅读 评论(0) 收藏 举报 框架springinterface注解 好久没有搭建框架了,今天整理下以前的知识,整合下SSH,没想到手生了 ...

  6. 框架源码专题:Spring是如何集成Mybatis的?Spring怎么管理Mapper接口的动态代理

    文章目录 1. Spring集成Mybatis代码示例 2. Spring 如何解析Mybatis配置文件 3. Spring是怎么管理Mapper接口的动态代理的 4. Spring整合Mybati ...

  7. 框架源码专题:Spring的事件监听、发布机制 ApplicationListener

    文章目录 1.Spring内置事件 2.自定义事件 3.事件监听器 4.事件发布 publishEvent 4.Spring事件原理 5. 面试题:怎么样可以在所有Bean创建完后做扩展代码? 6. ...

  8. 框架源码专题:Spring是如何解决循环依赖的?

    文章目录 1.什么是循环依赖? 2.解决循环依赖思路 3. 使用了三级缓存还有什么问题?怎么解决的? 4. 手写伪代码解决缓存依赖 5. 二级缓存能否解决循环依赖,三级缓存存在的意义 6. Sprin ...

  9. SSH框架整合——基于XML配置文件

    SSH框架整合--基于XML配置文件 @(Spring)[spring, struts2, hibernate, 框架整合, ssh, Spring] SSH框架整合基于XML配置文件 SSH框架整合 ...

最新文章

  1. 用python编写ios应用
  2. 华胜天成1.18亿美元收购美国GD公司
  3. mysql mof_关于mysql mof提权研究
  4. mongodb集群 java_MongoDB集群JavaAPI插入数据
  5. Python高手必读,做一个精通规则的玩家
  6. 快速入门python_一天快速入门 Python
  7. jQuery 1.4官方文档中文版
  8. poj 1656 Counting Black
  9. java中String、StringBuffer和StringBuilder的区别(简单介绍)
  10. 问题记录:图片加载快速滑动闪动问题(Android-APP)
  11. html css js 注释符号,js 注释怎么写 javascript注释格式|js注释
  12. Android APK安装后资源文件(res/assets)位置
  13. .net分流抢票助手
  14. 「解决方案」运维、能耗、网关整体解决方案
  15. 强大的多线程和倒计时程序
  16. IDA pro与x64dbg地址对齐
  17. Arqit公司将于2023年用卫星发送量子密钥;QC Ware发布量子线性代数API | 全球量子科技与工业快讯第二十六期
  18. 我跟Python的孽缘
  19. python 使用爬虫下载京东图片
  20. 肌电信号的包络matlab程序_基于matlab的肌电信号处理程序:

热门文章

  1. Nagios 安装方法
  2. 极其简便的PHP HTML DOM解析器PHP Simple HTML DOM Parser/有中文手册
  3. HIBERNATE与 MYBATIS的对比
  4. Handler BlockViewHandler has a bad module ManagedPipelineHandler in its module list
  5. Sql Server系列:键和约束
  6. 好记性不如烂笔头——.NET运行原理
  7. Logic-算法-XX部队XX侦察队员
  8. 使用eclipse开发web需要搭建什么环境
  9. Java Stream API进阶篇
  10. Java编程思想—第十二十三章