Spring4.0学习笔记008——AOP的配置(基于注解)

AOP开发环境的准备

  1. 新建名为 lib 的文件夹,并往其中导入所需的jar包。

    • commons-logging-1.2.jar
    • spring-beans-4.0.0.RELEASE.jar
    • spring-context-4.0.0.RELEASE.jar
    • spring-core-4.0.0.RELEASE.jar
    • spring-expression-4.0.0.RELEASE.jar
    • spring-aop-4.0.0.RELEASE.jar
    • spring-aspects-4.0.0.RELEASE.jar
  2. 解压 springsource-tool-suite-3.7.2.RELEASE-e4.5.1-updatesite.zip 文件,再导入一些jar包。

    • org.aopalliance_1.0.0.jar(关键字为 aopalliance 的包)
    • org.aspectj.weaver_1.8.7.jar(关键字为 aspectj.weaver 的包)
    • org.aspectj.runtime_1.8.7.jar(关键字为 aspectj.runtime 的包)
  3. 将之前加入的所有jar包,都加入到build路径。

配置组件的自动扫描环境

  1. 在前一篇博文的基础上新建名为 ApplicationContext 的xml配置文件,并且另外引入context命名空间。

    xmlns:context="http://www.springframework.org/schema/context"
  2. 在xml配置文件中声明基于注解的组件扫面范围。

    <!-- 配置基于注解的组件自动扫面范围 -->
    <context:component-scan base-package="com.yfyzwr.spring.aop"></context:component-scan>
  3. 为ArithmeticCalculatorImpl类添加自动扫描的注解。

    @Component
    public class ArithmeticCalculatorImpl implements ArithmeticCalculator
  4. 修改main方法的实现。

    public class Main {
        public static void main(String[] args) {
    
            ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("ApplicationContext.xml");
            ArithmeticCalculatorImpl target = (ArithmeticCalculatorImpl)context.getBean("arithmeticCalculatorImpl");
    
            int result = target.add(1, 2);
            System.out.println("result : " + result);
        }
    }
  5. 运行程序,查看输出结果。

    result : 3

从输出结果可以得知,Spring容器已经加载并实例化ArithmeticCalculatorImpl类型的对象,基于注解的自动扫描环境配置成功。

前置通知

  1. 为xml配置文件再引入aop命名空间,并且还要使得@Aspect注解生效。

    xmlns:aop="http://www.springframework.org/schema/aop"
    
    <!-- 使@Aspect注解生效:自动为匹配的类生产代理对象 -->
    <aop:aspectj-autoproxy></aop:aspectj-autoproxy>
  2. 新建名为 LoggingAspect 的java类。

    @Aspect
    @Component
    public class LoggingAspect {
    
        @Before("execution(* com.yfyzwr.spring.aop.*.*(..))")
        public void beforeMerhod(JoinPoint joinPoint){
            String MethodName = joinPoint.getSignature().getName();
            List<Object> args = Arrays.asList(joinPoint.getArgs());
    
            System.out.println("The Method " + MethodName + "begin with" + args);
        }
    }

    需要注意的是:

    • 用作切面的类,同样需要添加@Component注解,这样Spring容器才能检测到并实例化它的对象。
    • 用作切面的类,需要添加@Aspect注解,这样才能标识出它是用作切面。
    • 前置通知使用@Before注解,并将切入点表达式的值作为注解值。
    • 第一个 * 号表示:任意修饰符及任意返回值(如果是 public * 则表示只对公有方法起作用)
    • 第二个 * 号表示:任意类
    • 第三个 * 号表示:任意方法
    • 括号里的.. 号表示:匹配任意数量的参数(如果是 double, .. 则表示只对第一个参数是double类型的方法起作用)
    • 方法的实现也可以不带JoinPoint类型的参数,但是如果带上该JoinPoint 类属于 org.aspectj.lang.JoinPoint; 包。
  3. 修改main方法的实现

    public static void main(String[] args) {
    
        ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("ApplicationContext.xml");
        //注意这一行的变化,同上一篇博文讲的那样,我们需要的是接口类型来匹配生产的Proxy对象
        ArithmeticCalculator target = (ArithmeticCalculator)context.getBean(ArithmeticCalculator.class);
    
        int result = target.add(1, 2);
        System.out.println("result : " + result);
    }

    需要注意的是:
    与之前的genBean()方法的使用有所不同,这里传入的参数是接口类型的Class对象。这个要求与上一篇博文在直接使用动态代理时一样,返回的代理对象是接口类型。

  4. 运行程序,查看输出结果。

    The Method addbegin with[1, 2]
    result : 3

    从输出结果可以得知,前置通知方法被成功调用。

后置通知

  1. LoggingAspect 类添加后置通知的方法。

    @After("execution(* com.yfyzwr.spring.aop.*.*(..))")
    public void afterMerhod(JoinPoint joinPoint){
        String MethodName = joinPoint.getSignature().getName();
    
        System.out.println("The Method " + MethodName + " end");
    }
  2. 运行程序,查看输出结果。

    The Method add begin with[1, 2]
    The Method add end
    result : 3

    需要注意的是:

    • 后置通知不论目标方法的执行是否出现异常,都会被执行。
    • 后置通知里面还不能访问目标方法的返回值。
  3. 修改main方法的实现。

    public static void main(String[] args) {
    
        ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("ApplicationContext.xml");
        //注意这一行的变化,同上一篇博文讲的那样,我们需要的是接口类型来匹配生产的Proxy对象
        ArithmeticCalculator target = (ArithmeticCalculator)context.getBean(ArithmeticCalculator.class);
    
        int result = target.add(1, 2);
        System.out.println("result : " + result);
    
        result = target.dev(12, 0);
        System.out.println("result : " + result);
    }
  4. 再运行程序,查看输入结果。

    The Method add begin with[1, 2]
    The Method add end
    result : 3
    The Method dev begin with[12, 0]
    The Method dev end
    Exception in thread “main” java.lang.ArithmeticException: / by zero

返回通知

LoggingAspect 类添加返回通知的方法。

@AfterReturning(value="execution(* com.yfyzwr.spring.aop.*.*(..))", returning="result")
public void returnMerhod(JoinPoint joinPoint, Object result){
    String MethodName = joinPoint.getSignature().getName();

    System.out.println("The Method " + MethodName + " end with " + result);
}

异常通知

LoggingAspect 类添加异常通知的方法。

@AfterThrowing(value="execution(* com.yfyzwr.spring.aop.*.*(..))", throwing="ex")
public void throwingMerhod(JoinPoint joinPoint, Exception ex){
    String MethodName = joinPoint.getSignature().getName();

    System.out.println("The Method " + MethodName + " occur exception " + ex);
}

需要注意的是:
可以为方法的形参指定具体类型的异常(如ArithmeticException、NullPointerException等),这样就会只在发生指定异常时,调用异常通知方法。

切面的优先级

上述的LoggingAspect类是切面类,如果我们的项目中有很多的切面类,并且都针对同一目标方法设置了各自的通知方法。在这种情况下,这些不同切面类的通知方法的执行顺序是不确定的,所有我们需要为不同的切面类设置各自的优先级。

切面的优先级可以通过实现 Ordered 接口或利用 @Order 注解指定。

@Order(0)       //指定切面的执行优先级
@Aspect
@Component
public class LoggingAspect {}

重用切入点表达式

上述的各种通知方法,它们都有着相同的切点表达式,所以我们应该实现对该切点表达式的重用。

需要修改 LoggingAspect 类的实现。

@Order(0)       //指定切面的执行优先级
@Aspect
@Component
public class LoggingAspect {

    //定义一个方法,用于声明切入点表达式。通常该方法的方法体是空的,因为将切入点定义与应用程序逻辑混在一起是不合理的
    @Pointcut("execution(* com.yfyzwr.spring.aop.*.*(..))")
    public void declareJoinPointExpression(){}

    @Before("declareJoinPointExpression()")
    public void beforeMerhod(JoinPoint joinPoint){
        String MethodName = joinPoint.getSignature().getName();
        List<Object> args = Arrays.asList(joinPoint.getArgs());

        System.out.println("The Method " + MethodName + " begin with" + args);
    }

    @After("declareJoinPointExpression()")
    public void afterMerhod(JoinPoint joinPoint){
        String MethodName = joinPoint.getSignature().getName();

        System.out.println("The Method " + MethodName + " end");
    }

    @AfterReturning(value="declareJoinPointExpression()", returning="result")
    public void returnMerhod(JoinPoint joinPoint, Object result){
        String MethodName = joinPoint.getSignature().getName();

        System.out.println("The Method " + MethodName + " end with " + result);
    }

    @AfterThrowing(value="declareJoinPointExpression()", throwing="ex")
    public void throwingMerhod(JoinPoint joinPoint, NullPointerException ex){
        String MethodName = joinPoint.getSignature().getName();

        System.out.println("The Method " + MethodName + " occur exception " + ex);
    }
}

其他文件、其他包都可以使用这个切入点的表达式,只需要附上该表达式的包、文件信息即可。

你可能感兴趣的:(spring)