Spring学习总结(九):AspectJ 开发 AOP 的两种方式

一、基于XML的声明式

       基于 XML 的声明式是指通过 Spring 配置文件的方式定义切面、切入点及声明通知,而所有的切面和通知都必须定义在 <aop:config> 元素中。

       步骤如下:

       (1)导入jar包。

   <!-- https://mvnrepository.com/artifact/org.springframework/spring-aop -->
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-aop</artifactId>
      <version>5.2.6.RELEASE</version>
    </dependency>
    <!-- https://mvnrepository.com/artifact/aopalliance/aopalliance -->
    <dependency>
      <groupId>aopalliance</groupId>
      <artifactId>aopalliance</artifactId>
      <version>1.0</version>
    </dependency>
    <!-- https://mvnrepository.com/artifact/org.aspectj/aspectjweaver -->
    <dependency>
      <groupId>org.aspectj</groupId>
      <artifactId>aspectjweaver</artifactId>
      <version>1.8.13</version>
    </dependency>
    <!-- https://mvnrepository.com/artifact/org.aspectj/aspectjrt -->
    <dependency>
      <groupId>org.aspectj</groupId>
      <artifactId>aspectjrt</artifactId>
      <version>1.8.9</version>
    </dependency>

       (2)创建Book类

public class Book {
    public void addBook(){
        System.out.println("add Book……");
    }
    public void updateBook(){
        System.out.println("update Book……");
    }

}

       (3)创建BookProxy类

public class BookProxy {
    public void before(){
        System.out.println("before……");
    }
    public void after(){
        System.out.println("after……");
    }
    public void afterReturing(){
        System.out.println("afterReturing……");
    }
    public void afterThrowing(){
        System.out.println("afterThrowing……");
    }
    public void around(ProceedingJoinPoint point) throws Throwable {
        System.out.println("around  之前……");
        point.proceed();
        System.out.println("around  之后……");
    }
}

       (4)在bean.xml中进行bean配置和aop的配置。

    <!--  基于XML的AOP实现  -->
    <!--  配置bean  -->
    <bean id="book" class="com.yht.example6.Book"></bean>
    <bean id="bookProxy" class="com.yht.example6.BookProxy"></bean>
    <!-- aop配置   -->
    <aop:config>
        <!--
            expression : 配置切入点的具体位置
            id:切入点的id
         -->
        <aop:pointcut id="p1" expression="execution(* com.yht.example6.Book.*(..))"/>
        <!--    -->
        <aop:aspect ref="bookProxy">
            <aop:before method="before" pointcut-ref="p1"></aop:before>
            <aop:after method="after" pointcut-ref="p1"></aop:after>
            <aop:after-returning method="afterReturing" pointcut-ref="p1"></aop:after-returning>
            <aop:after-throwing method="afterThrowing" pointcut-ref="p1"></aop:after-throwing>
            <aop:around method="around" pointcut-ref="p1"></aop:around>
        </aop:aspect>
    </aop:config>

       (5)进行单元测试

    @Test
    public void testBook(){
        ApplicationContext context = new ClassPathXmlApplicationContext("bean.xml");
        Book book = context.getBean("book", Book.class);
        book.addBook();
    }

       执行结果如下:

 

二、基于注解的声明式

       在 Spring 中,尽管使用 XML 配置文件可以实现 AOP 开发,但是如果所有的相关的配置都集中在配置文件中,势必会导致 XML 配置文件过于臃肿,从而给维护和升级带来一定的困难。AspectJ 框架为 AOP 开发提供了另一种开发方式——基于 Annotation 的声明式。AspectJ 允许使用注解定义切面、切入点和增强处理,而 Spring 框架则可以识别并根据这些注解生成 AOP 代理。常用注解介绍如下:

名称 说明
@Aspect 用于定义一个切面
@Before 用于定义前置通知,相当于BeforeAdvice
@AfterReturning 用于定义后置通知,相当于AfterReturningAdvice
@Around 用于定义环绕通知,相当于MethodInterceptor
@AfterThrowing 用于定义异常抛出通知,相当ThrowAdvice
@After 用于定义最终(final)通知,不管是否异常,该通知都会执行
@DeclareParents 用于定义引介通知,相当于IntroductionInterceptor(了解)

       (1)在bean.xml中配置。

<!--  配置注解扫描的包  -->
    <context:component-scan base-package="com.yht.example6"></context:component-scan>
    <!--   
        通过aop命名空间的<aop:aspectj-autoproxy />声明自动为spring容器中那些配置@aspectJ切面的bean创建代理,织入切面。
     -->
    <aop:aspectj-autoproxy></aop:aspectj-autoproxy>

       通过aop命名空间的<aop:aspectj-autoproxy />声明自动为spring容器中那些配置@aspectJ切面的bean创建代理,织入切面。在<aop:aspectj-autoproxy />有一个proxy-target-class属性,默认为false,如果proxy-target-class属值被设置为false或者这个属性被省略,那么标准的JDK 基于接口的代理将起作用。当配为<aop:aspectj-autoproxy  poxy-target-class="true"/>时,表示使用CGLib动态代理技术织入增强。不过即使proxy-target-class设置为false,如果目标类没有声明接口,则spring将自动使用CGLib动态代理。

       (2)创建User类

@Component
public class User {
    public void addUser(){
        System.out.println("添加用户");
    }
    public void updateUser(){
        System.out.println("修改用户");
    }
    public void deleteUser(){
        System.out.println("删除用户");
    }
    public void searchUser(){
        System.out.println("查找用户");
    }

}

       (3)创建UserProxy

@Component
@Aspect
public class UserProxy {
    @Before(value = "execution(* com.yht.example6.User.addUser(..))")
    public void before(){
        System.out.println("方法执行之前");
    }
    @After(value = "execution(* com.yht.example6.User.addUser(..))")
    public void after(){
        System.out.println("方法执行之后");
    }
    @AfterReturning(value = "execution(* com.yht.example6.User.addUser(..))")
    public void afterReturning(){
        System.out.println("返回后通知");
    }
    @AfterThrowing(value = "execution(* com.yht.example6.User.addUser(..))")
    public void afterThrowing(){
        System.out.println("抛出异常后通知");
    }
    @Around(value = "execution(* com.yht.example6.User.addUser(..))")
    public void around(ProceedingJoinPoint point) throws Throwable {
        System.out.println("环绕通知——方法执行之前");
        point.proceed();
        System.out.println("环绕通知——方法执行之后");
    }
}

       (4)进行单元测试。

    @Test
    public void testUser(){
        ApplicationContext context = new ClassPathXmlApplicationContext("bean.xml");
        User user = context.getBean("user", User.class);
        user.addUser();
    }

       执行结果如下:

几点说明:

       (1)afterThrowing方法是在程序出现异常时执行的,在User的addUser()让程序出现异常。检测该方法是否会执行。

    @AfterThrowing(value = "execution(* com.yht.example6.User.addUser(..))")
    public void afterThrowing(){
        System.out.println("抛出异常后通知");
    }

       制造一个NumberFormatException:

    public void addUser(){
        int val = Integer.parseInt("123ab");//异常
        System.out.println("添加用户");
    }

        结果如下:

(2)@Order的介绍

       @Order 注解用来声明组件的顺序,值越小,优先级越高,越先被执行/初始化。如果没有该注解,则优先级最低。对于上述代码,再添加一个PersonProxy类,给定@Order(1),UserProxy给定@Order(5),则执行结果PersonProxy的before方法会在UserProxy的before方法之前执行。

       1)定义PersonProxy类:

@Component
@Aspect
@Order(1)
public class PersonProxy {
    @Before(value = "execution(* com.yht.example6.User.addUser(..))")
    public void beforeMethod(){
        System.out.println("我是PersonProxy的before方法");
    }
}

       2) 给UserProxy添加@Order(5):

       3)进行测试。得到结果如下:

       (3)相同切入点的提取

       在上面的例子中,UserProxy中的五种通知的配置,它们的切入点表达式是相同的,我们可以将其提取出来,然后在每个通知的注解中引用即可。所以UserProxy类就如下面:

@Component
@Aspect
@Order(5)
public class UserProxy {
    
    @Pointcut(value = "execution(* com.yht.example6.User.addUser(..))")
    public void commonMsg(){}
    
    @Before(value = "commonMsg()")
    public void before(){
        System.out.println("方法执行之前");
    }
    @After(value = "commonMsg()")
    public void after(){
        System.out.println("方法执行之后");
    }
    @AfterReturning(value = "commonMsg()")
    public void afterReturning(){
        System.out.println("返回后通知");
    }
    @AfterThrowing(value = "commonMsg()")
    public void afterThrowing(){
        System.out.println("抛出异常后通知");
    }
    @Around(value = "commonMsg()")
    public void around(ProceedingJoinPoint point) throws Throwable {
        System.out.println("环绕通知——方法执行之前");
        point.proceed();
        System.out.println("环绕通知——方法执行之后");
    }
}

       最终得到的结果和前面也将是一样的。

       (4)AOP纯注解开发 

       AOP纯注解开发 和IOC注解开发基本类似,提供一个配置类。加入注解:

@Configuration
@ComponentScan(basePackages = "com.yht.example6")
//@EnableAspectJAutoProxy标记在主配置类上,表示开启基于注解的aop模式
@EnableAspectJAutoProxy
public class AopConfig {
}

测试如下:

    @Test
    public void testUserAnno(){
        ApplicationContext context = new AnnotationConfigApplicationContext(AopConfig.class);
        User user = context.getBean("user", User.class);
        user.addUser();
    }

猜你喜欢

转载自blog.csdn.net/weixin_47382783/article/details/112821681