Spring4深入理解AOP02----AOP简介,AspectJ基于注解(5种通知,切面优先级)

参考代码下载github:https://github.com/changwensir/java-ee/tree/master/spring4

一、AOP简介

?AOP(Aspect-Oriented Programming, 面向切面编程):是一种新的方法论,是对传统OOP(Object-OrientedProgramming,面向对象编程)的补充.

?AOP 的主要编程对象是切面(aspect),而切面模块化横切关注点.

?在应用 AOP
编程时,仍然需要定义公共功能,但可以明确的定义这个功能在哪里,以什么方式应用,并且不必修改受影响的类.这样一来横切关注点就被模块化到特殊的对象(切面)里.

?AOP 的好处:

–每个事物逻辑位于一个位置,代码不分散,便于维护和升级

–业务模块更简洁,
只包含核心业务代码.

AOP 术语

?切面(Aspect): 横切关注点(跨越应用程序多个模块的功能)被模块化的特殊对象,把横切关注点的代码抽象到切面的类中

?通知(Advice): 切面必须要完成的工作,就是功能对应的方法

?目标(Target):被通知的对象

?代理(Proxy):向目标对象应用通知之后创建的对象

?连接点(Joinpoint):程序执行的某个特定位置:如类某个方法调用前、调用后、方法抛出异常后等。连接点由两个信息确定:方法表示的程序执行点;相对点表示的方位。例如ArithmethicCalculator#add()方法执行前的连接点,执行点为ArithmethicCalculator#add();方位为该方法执行前的位置

?切点(pointcut):每个类都拥有多个连接点:例如ArithmethicCalculator的所有方法实际上都是连接点,即连接点是程序类中客观存在的事务。AOP通过切点定位到特定的连接点。类比:连接点相当于数据库中的记录,切点相当于查询条件。切点和连接点不是一对一的关系,一个切点匹配多个连接点,切点通过org.springframework.aop.Pointcut接口进行描述,它使用类和方法作为连接点的查询条件。

二、AspectJ

?AspectJ:Java社区里最完整最流行的AOP框架.

?在 Spring2.0以上版本中,可以使用基于AspectJ注解或基于XML配置的AOP

1).在 Spring中启用AspectJ注解支持

?要在 Spring应用中使用AspectJ注解,必须在classpath下包含AspectJ类库:aopalliance.jar、aspectj.weaver.jar和spring-aspects.jar

?将 aop Schema添加到<beans>根元素中.

?要在 SpringIOC容器中启用AspectJ注解支持,只要在Bean配置文件中定义一个空的XML
元素<aop:aspectj-autoproxy>

?当 SpringIOC
容器侦测到 Bean 配置文件中的<aop:aspectj-autoproxy>元素时,会自动为与AspectJ切面匹配的Bean创建代理.

2).用 AspectJ注解声明切面

?要在 Spring中声明AspectJ切面,只需要在IOC容器中将切面声明为Bean实例.当在SpringIOC容器中初始化AspectJ切面之后,Spring
IOC容器就会为那些与AspectJ切面相匹配的Bean创建代理.

?在 AspectJ注解中,切面只是一个带有@Aspect注解的
Java类.

?通知是标注有某种注解的简单的 Java方法.

?AspectJ支持5种类型的通知注解:

–@Before: 前置通知,在方法执行之前执行

–@After: 后置通知,在方法执行之后执行

–@AfterRunning:返回通知,在方法返回结果之后执行

–@AfterThrowing:异常通知,在方法抛出异常之后

–@Around: 环绕通知,围绕着方法执行

3).利用方法签名编写AspectJ切入点表达式

?最典型的切入点表达式时根据方法的签名来匹配各种方法:

–execution *com.atguigu.spring.ArithmeticCalculator.*(..):匹配ArithmeticCalculator中声明的所有方法,第一个
* 代表任意修饰符及任意返回值.第二个 * 代表任意方法...匹配任意数量的参数.若目标类与接口与该切面在同一个包中,可以省略包名.

–execution public* ArithmeticCalculator.*(..):匹配ArithmeticCalculator接口的所有公有方法.

–execution publicdouble ArithmeticCalculator.*(..):匹配ArithmeticCalculator中返回double类型数值的方法

–execution publicdouble ArithmeticCalculator.*(double,..):匹配第一个参数为double类型的方法,..匹配任意数量任意类型的参数

–execution publicdouble ArithmeticCalculator.*(double,double):匹配参数类型为double,double类型的方法.

<?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"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd">

    <context:component-scan base-package="Spring4_AOP.aopAnnotation"/>

    <!--使AspectJ注解起作用:自动为匹配的类生成代理对象-->
    <aop:aspectj-autoproxy/>
</beans>

ArithmeticCalculatorImpl在参见一篇博客http://blog.csdn.net/ochangwen/article/details/52557459

所以的类都放个这个目录下Spring4_AOP.aopAnnotation

日志切面

* 4. 编写切面类(把横切关注点的代码抽象到切面的类中):

* 4.1 一个一般的 Java 类

* 4.2 在其中添加要额外实现的功能.

*

* 5. 配置切面

* 5.1 切面必须是 IOC 中的 bean: 实际添加了 @Component 注解

* 5.2 声明是一个切面: 添加 @Aspect

* 5.3 声明通知: 即额外加入功能对应的方法!!.

@Aspect
@Component
public class LoggingAspect {
    /**
     * 重用切入点定义
     * 定义一个方法,用于声明切入点表达式,一般地,该方法中再不需要添入其它的代码
     */
    @Pointcut("execution(* Spring4_AOP.aopAnnotation.*.*(..))")
    public void declareJoinPointerExpression() {}

    //1、前置通知: 在目标方法开始之前执行(就是要告诉该方法要在哪个类哪个方法前执行)
    //@Before("execution(public int Spring4_AOP.aopAnnotation.*.*(int ,int))")
    @Before("declareJoinPointerExpression()")
    public void beforeMethod(JoinPoint joinPoint) {
        String methodName = joinPoint.getSignature().getName();
        Object [] args = joinPoint.getArgs();

        System.out.println("The method " + methodName + " begins with " + Arrays.asList(args));
    }

    //2、后置通知:在目标方法执行后(无论是否发生异常),执行的通知
    //注意,在后置通知中还不能访问目标执行的结果!!!,执行结果需要到返回通知里访问
    //@After("execution(* Spring4_AOP.aopAnnotation.*.*(..))")
    @After("declareJoinPointerExpression()")
    public void afterMethod(JoinPoint joinPoint){
        String methodName = joinPoint.getSignature().getName();
        System.out.println("The method " + methodName + " ends");
    }

    //无论连接点是正常返回还是抛出异常, 后置通知都会执行. 如果只想在连接点返回的时候记录日志, 应使用返回通知代替后置通知.

    //3、返回通知:在方法正常结束后执行的代码,返回通知是可以访问到方法的返回值的!!!
    //@AfterReturning(pointcut = "execution(* Spring4_AOP.aopAnnotation.*.*(..))", returning = "result")
    @AfterReturning(value = "declareJoinPointerExpression()", returning = "result")
    public void afterReturning(JoinPoint joinPoint, Object result){
        String methodName = joinPoint.getSignature().getName();
        System.out.println("The method " + methodName + " AfterReturning ends with " + result);
    }

    //4、异常通知:在目标方法出现异常 时会执行的代码,可以访问到异常对象:且可以!!指定在出现特定异常时在执行通知!!,如果是修改为nullPointerException里,只有空指针异常才会执行
//    @AfterThrowing(pointcut = "execution(* Spring4_AOP.aopAnnotation.*.*(..))", throwing = "except")
    @AfterThrowing(value = "declareJoinPointerExpression())", throwing = "except")
    public void afterThrowing(JoinPoint joinPoint, Exception except){
        String methodName = joinPoint.getSignature().getName();
        System.out.println("The method " + methodName + "  occurs exception " + except);
    }

    /**
     * 5、环绕通知 需要携带 ProceedingJoinPoint 类型的参数.
     * 环绕通知类似于动态代理的全过程: ProceedingJoinPoint 类型的参数可以决定是否执行目标方法.
     * 且环绕通知必须有返回值, 返回值即为目标方法的返回值
     */
    //   @Around("execution(* Spring4_AOP.aopAnnotation.*.*(..))")
    @Around("declareJoinPointerExpression()")
    public Object aroundMethod(ProceedingJoinPoint pjd){

        Object result = null;
        String methodName = pjd.getSignature().getName();

        try {
            //前置通知
            System.out.println("The method " + methodName + " begins with " + Arrays.asList(pjd.getArgs()));
            //执行目标方法
            result = pjd.proceed();
            //返回通知
            System.out.println("The method " + methodName + " ends with " + result);
        } catch (Throwable e) {
            //异常通知
            System.out.println("The method " + methodName + " occurs exception:" + e);
            throw new RuntimeException(e);
        }
        //后置通知
        System.out.println("The method " + methodName + " ends");

        return result;
    }

}

注意:

?在 AspectJ中,切入点表达式可以通过操作符&&,||, !结合起来.

@Pointcut("execution(* Spring4_AOP.aopAnnotation.*.add(..)) ||execution(* Spring4_AOP.aopAnnotation.*.sub(..))")
    public void declareJoinPointerExpression() {}

返回通知:?无论连接点是正常返回还是抛出异常,后置通知都会执行.如果只想在连接点返回的时候记录日志,应使用返回通知代替后置通知.

在返回通知中访问连接点的返回值

?在返回通知中, 只要将returning属性添加到@AfterReturning注解中,就可以访问连接点的返回值.该属性的值即为用来传入返回值的参数名称.

?必须在通知方法的签名中添加一个同名参数.在运行时,Spring AOP会通过这个参数传递返回值.

?原始的切点表达式需要出现在pointcut属性中

2).异常通知

?只在连接点抛出异常时才执行异常通知

?将 throwing属性添加到@AfterThrowing注解中,也可以访问连接点抛出的异常.Throwable是所有错误和异常类的超类.所以在异常通知方法可以捕获到任何错误和异常.

如果只对某种特殊的异常类型感兴趣,可以将参数声明为其他异常的参数类型.然后通知就只在抛出这个类型及其子类的异常时才被执行

3).环绕通知

?环绕通知是所有通知类型中功能最为强大的,能够全面地控制连接点.甚至可以控制是否执行连接点.

?对于环绕通知来说, 连接点的参数类型必须是ProceedingJoinPoint.它是JoinPoint的子接口,允许控制何时执行,是否执行连接点.

?在环绕通知中需要明确调用 ProceedingJoinPoint的proceed()方法来执行被代理的方法.如果忘记这样做就会导致通知被执行了,但目标方法没有被执行.

?注意: 环绕通知的方法需要返回目标方法执行之后的结果,即调用joinPoint.proceed();的返回值,否则会出现空指针异常

    @Test
    public void testAOPAnnotation() {
        //1、创建Spring的IOC的容器
        ApplicationContext ctx = new ClassPathXmlApplicationContext("Spring4_AOP/applicationContext-aop.xml");

        //2、从IOC容器中获取bean的实例
        ArithmeticCalculator arithmeticCalculator = (ArithmeticCalculator) ctx.getBean("arithmeticCalculator");

        //3、使用bean
        int result = arithmeticCalculator.div(3, 1);
        System.out.println("result:" + result);
    }

2-1.指定切面的优先级

?在同一个连接点上应用不止一个切面时,除非明确指定,否则它们的优先级是不确定的.

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

?实现 Ordered接口,getOrder()方法的返回值越小,优先级越高,切面出的越在前面

?若使用 @Order注解,序号出现在注解中

写一个验证切面

@Order(1)
@Aspect
@Component
public class ValidationAspect {
    @Before("execution(public int Spring4_AOP.aopAnnotation.*.*(int ,int))")
    public void validateArgs(JoinPoint joinPoint){
        System.out.println("-->validate:" + Arrays.asList(joinPoint.getArgs()));
    }
}

日志切面,使用上面的那个类,只增加Order

@Order(2)
@Aspect
@Component
public class LoggingAspect {
   ......
}

2-3.重用切入点定义

?在编写 AspectJ切面时,可以直接在通知注解中书写切入点表达式.但同一个切点表达式可能会在多个通知中重复出现.

?在 AspectJ切面中,可以通过@Pointcut注解将一个切入点声明成简单的方法.切入点的方法体通常是空的,因为将切入点定义与应用程序逻辑混在一起是不合理的.

?切入点方法的访问控制符同时也控制着这个切入点的可见性.如果切入点要在多个切面中共用,最好将它们集中在一个公共的类中.在这种情况下,它们必须被声明为public.在引入这个切入点时,必须将类名也包括在内.如果类没有与这个切面放在同一个包中,还必须包含包名.

?其他通知可以通过方法名称引入该切入点.

三 、用基于 XML
的配置声明切面

?除了使用 AspectJ注解声明切面,Spring
也支持在 Bean
配置文件中声明切面.这种声明是通过aopschema
中的 XML 元素完成的.

?正常情况下,
基于注解的声明要优先于基于 XML
的声明.通过AspectJ注解,切面可以与AspectJ兼容,而基于XML的配置则是Spring专有的.由于AspectJ得到越来越多的AOP框架支持,所以以注解风格编写的切面将会有更多重用的机会.

1).基于 XML----
声明切面

?当使用 XML
声明切面时,需要在<beans>根元素中导入aopSchema

?在 Bean
配置文件中,所有的SpringAOP
配置都必须定义在 <aop:config>元素内部.对于每个切面而言,都要创建一个<aop:aspect>元素来为具体的切面实现引用后端Bean实例.

?切面 Bean
必须有一个标示符,供<aop:aspect>元素引用

2).基于 XML----
声明切入点

?切入点使用 <aop:pointcut>元素声明

?切入点必须定义在 <aop:aspect>元素下,或者直接定义在<aop:config>元素下.

–定义在 <aop:aspect>元素下:只对当前切面有效

–定义在 <aop:config>元素下:对所有切面都有效

?基于XML的AOP配置不允许在切入点表达式中用名称引用其他切入点.

3).基于 XML----
声明通知

?在 aopSchema
中, 每种通知类型都对应一个特定的XML元素.

?通知元素需要使用 <pointcut-ref>来引用切入点,或用<pointcut>直接嵌入切入点表达式. 
method 属性指定切面类中通知方法的名称.

4).声明引入

?可以利用 <aop:declare-parents>元素在切面内部声明引入

ArithmeticCalulator2与上面ArithmeticCalulator接口内容 一样

public class ArithmeticCalculatorImplXML implements ArithmeticCalculator2 {
    public int add(int i, int j) {
        return i + j;
    }

    public int sub(int i, int j) {
        return i - j;
    }

    public int mul(int i, int j) {
        return i * j;
    }

    public int div(int i, int j) {
        return i / j;
    }
}
public class LoggingAspectXML {
    public void beforeMethod(JoinPoint joinPoint){
        String methodName = joinPoint.getSignature().getName();
        Object [] args = joinPoint.getArgs();

        System.out.println("The method " + methodName + " begins with " + Arrays.asList(args));
    }

    public void afterMethod(JoinPoint joinPoint){
        String methodName = joinPoint.getSignature().getName();
        System.out.println("The method " + methodName + " ends");
    }

    public void afterReturning(JoinPoint joinPoint, Object result){
        String methodName = joinPoint.getSignature().getName();
        System.out.println("The method " + methodName + " ends with " + result);
    }

    public void afterThrowing(JoinPoint joinPoint, Exception e){
        String methodName = joinPoint.getSignature().getName();
        System.out.println("The method " + methodName + " occurs excetion:" + e);
    }
}
public class ValidationAspectXML {

    public void validateArgs(JoinPoint joinPoint){
        System.out.println("-->validate:" + Arrays.asList(joinPoint.getArgs()));
    }
}
<?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.xsd
       http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd">

       <!-- 配置 bean -->
       <bean id="arithmeticCalculatorXML" class="Spring4_AOP.aopXML.ArithmeticCalculatorImplXML">
       </bean>

       <!-- 配置切面的 bean. -->
       <bean id="loggingAspect" class="Spring4_AOP.aopXML.LoggingAspectXML">
       </bean>
       <bean id="validationAspect" class="Spring4_AOP.aopXML.ValidationAspectXML">
       </bean>

       <!-- 配置 AOP -->
       <aop:config>
              <!-- 配置切点表达式 -->
              <aop:pointcut id="pointcut"
                            expression="execution(* Spring4_AOP.aopXML.ArithmeticCalculatorImplXML.*(int ,int))"/>
              <!-- 配置切面及通知 -->
              <aop:aspect ref="loggingAspect" order="2">
                     <aop:before method="beforeMethod" pointcut-ref="pointcut"/>
                     <aop:after method="afterMethod" pointcut-ref="pointcut"/>
                     <aop:after-returning method="afterReturning" returning="result" pointcut-ref="pointcut"/>
                     <aop:after-throwing method="afterThrowing" throwing="e" pointcut-ref="pointcut"/>
              </aop:aspect>

              <aop:aspect ref="validationAspect" order="1">
                     <aop:before method="validateArgs" pointcut-ref="pointcut"/>
              </aop:aspect>
       </aop:config>
</beans>
    @Test
    public void testXML() {
        //1、创建Spring的IOC的容器
        ApplicationContext ctx = new ClassPathXmlApplicationContext("Spring4_AOP/applicationContext-xml.xml");

        //2、从IOC容器中获取bean的实例
        ArithmeticCalculatorImplXML arithmeticCalculator = (ArithmeticCalculatorImplXML) ctx.getBean("arithmeticCalculatorXML");

        //3、使用bean
        int result = arithmeticCalculator.add(3, 3);
        System.out.println("result:" + result);

        arithmeticCalculator.div(10, 0);
    }
时间: 2024-10-12 19:04:51

Spring4深入理解AOP02----AOP简介,AspectJ基于注解(5种通知,切面优先级)的相关文章

Spring AspectJ基于注解的AOP实现

对于AOP这种编程思想,很多框架都进行了实现.Spring就是其中之一,可以完成面向切面编程.然而,AspectJ也实现了AOP的功能,且实现方式更为简捷,使用更加方便,而且还支持注解式开发.所以,Spring又将AspectJ对于AOP的实现也引入到了自己的框架中.     在Spring中使用AOP开发时,一般使用AspectJ的实现方式. Spring的经典AOP配置方案  01.使用的是Aspectj第三方框架,实现了AOP思想  02.注解配置的AOP  03.纯POJO <aop:c

Spring_Spring与AOP_AspectJ基于注解的AOP实现

一.AspectJ.Spring与AOP的关系 AspectJ是一个面向切面的框架,它扩展了Java语言.AspectJ定义了AOP语法,所以它有一个专门的编译器用来生成遵守Java字节编码规范的Class文件.(百度百科) Spring又将AspectJ的对于AOP的实现引入到自己的框架中. 在Spring中使用AOP开发时,一般使用AspectJ的实现方式. 二.AspectJ的通知类型 前置通知 后置通知 环绕通知 异常通知 最终通知 三.AspectJ的切入点表达式 表达式中加[]的部分

AspectJ基于xml和基于注解

一.基于xml 执行的切入点中具体方法有返回值,则方法结束会立即执行后置通知,然后再执行环绕通知的放行之后的代码: 2.连接点即所有可能的方法,切入点是正真被切的方法,连接点方法名: 其中,只有环绕通知的切入点参数不一样,是可以放行的切入点: 3.异常通知是处理异常: 切面类中的异常通知的方法参数列表中异常参数的参数名: 4.最终通知,不管有没有异常都会执行, 后置通知:AOP :after-Returnning 异常通知:AOP :after-Throwing 最终通知:AOP :after

spring学习5:基于注解实现spring的aop

目录 spring学习5:基于注解实现spring的aop 一.基于注解+xml实现 1.1 在配置文件中开启spring对注解aop的支持 1.2 把通知类用注解配置到容器中,并用注解声明为切面 1.3 定义切入点表达式 1.4 定义通知 二.基于纯注解实现 三.多个aop的执行顺序 1.xml配置 2.注解配置 3.注意 spring学习5:基于注解实现spring的aop 上一节学习了spring aop的基本概念和如何基于xml配置来实现aop功能.这一节来学习下如何用注解实现aop 一

专门的aop框架-AspectJ

在前面的spring的aop的使用的基础上:切面类只能写一个方法,只能增强一个功能.就需要创建多个切面对象,配置多个<aop:advisor> AspectJ的注解式开发步骤 定义一个普通的类,添加@Aspect注解,表明是一个切面类 定义要增强的方法,通过注解确定增强的类型 @Before:前置通知 @AfterReturning:后置通知 @Around:环绕通知 @AfterThrowing:异常通知 @After:最终通知(相当于java中的finally) 通过aspectj的exe

【SSH进阶之路】Spring的AOP逐层深入——采用注解完成AOP(七)

上篇博文[SSH进阶之路]Spring的AOP逐层深入--AOP的基本原理(六),我们介绍了AOP的基本原理,以及5种通知的类型, AOP的两种配置方式:XML配置和Aspectj注解方式. 这篇我们使用注解方式来实现一个AOP,我们先看一下项目的目录. 我们采用的是JDK代理,所以首先将接口和实现类代码附上: package com.tgb.spring; public interface UserManager { public void addUser(String userName,St

Spring(十一)使用Spring的注解方式实现面向切面技术AOP

AOP涉及到的一些概念: Aspect(切面):横切性关注点的抽象即为切面,与类相似,只是两者的关注度不一样,类是对物体特征的抽象,而切面是横切性关注点的抽象. Joinpoint(连接点):所谓连接点指那些被拦截的点.在Spring中,这些点指的是方法,因为Spring只支持方法类型的连接点(实际上Joinpoint还可以是field或类构造器). Pointcut(切入点):所谓切入点是指我们要对那些Joinpoint进行拦截的定义. Advice(通知):所谓通知是指拦截到Joinpoin

Spring基于注解@AspectJ的AOP

Spring除了支持XML方式配置AOP,还支持注解方式:使用@AspectJ风格的切面声明. 但是用注解方式需要在XML启用对@AspectJ的支持<aop:aspectj-autoproxy/>,将在Spring上下文创建一个AnnotationAwareAspectJAutoProxyCreator类,它会自动代理一些Bean,这些Bean的方法需要与使用@Aspect注解的Bena中所定义的切点相匹配,而这些切点又是使用@Pointcut注解定义出来的,下面来看下例子(PS:我的例子都

Spring4学习笔记-AOP(基于注解的方式)

1.加入jar包 com.springsource.org.aopalliance-1.0.0.jar com.springsource.org.aspectj.weaver-1.6.8.RELEASE.jar commons-logging-1.1.3.jar spring-aop-4.1.0.RELEASE.jar spring-aspects-4.1.0.RELEASE.jar spring-beans-4.1.0.RELEASE.jar spring-context-4.1.0.RELE