Spring(五)AspectJ

AspectJ是一个Java语言实现的AOP框架。

支持注解配置和xml配置。

1.注解配置

还是三部曲:导包,写配置,测试。

(1)jar包

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-aspects</artifactId>
    <version>5.3.3</version>
</dependency>

(2)配置

将目标类和通知类放在Spring容器中;

假设现在有一个calculate类,里面有add+,sub-,multi*,div/四个方法:

目标类:

使用注解放入Spring容器中。

package com.dh.target;

import org.springframework.stereotype.Component;

@Component
public class Calculator {
    public void add(){
        System.out.println("add......");
    }
    public void sub(){
        System.out.println("sub......");
    }
    public void multi(){
        System.out.println("multi......");
    }
    public void div(){
        System.out.println("div......");
    }
}

通知类:

首先需要了解通知的类型:

  • @Before:前置通知;
  • @AfterReturning:后置通知;
  • @AfterThrowing:抛出通知
  • @After:最终通知;
  • @Around:环绕通知。

使用注解放在Spring容器中。

使用Aspect注解,标识为该类是通知。

package com.dh.aspect;

import org.aspectj.lang.annotation.*;

@Aspect
@Component
public class MyAspect {

    //execution()存放的是定义切点的表达式
    //指定类下的所有方法
    @Before(value = "execution(* com.dh.target.Calculator.*(..))")
    public void before(){
        System.out.println("before");
    }
    @AfterReturning(value = "execution(* com.dh.target.Calculator.*(..))")
    public void afterReturning(){
        System.out.println("afterReturning");
    }
    @AfterThrowing(value = "execution(* com.dh.target.Calculator.*(..))")
    public void afterThrowing(){
        System.out.println("afterThrowing");
    }
    @After(value = "execution(* com.dh.target.Calculator.*(..))")
    public void after(){
        System.out.println("after");
    }
}

配置文件:扫描注解

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

    <context:component-scan base-package="com.dh"/>

</beans>

测试:

(3)Spring单元测试:

导入jar包:

<!-- https://mvnrepository.com/artifact/org.springframework/spring-test -->
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-test</artifactId>
    <version>5.3.3</version>
    <scope>test</scope>
</dependency>
import com.dh.target.Calculator;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

@ContextConfiguration("classpath:applicationContext.xml")
@RunWith(SpringJUnit4ClassRunner.class)
public class MyTest01 {

    @Autowired
    public Calculator calculator;

    @Test
    public void test01(){
        calculator.add();
        System.out.println("---------------");
        calculator.sub();
        System.out.println("---------------");
        calculator.multi();
        System.out.println("---------------");
        calculator.div();
    }
}

结果:

before
add......
afterReturning
after
---------------
before
sub......
afterReturning
after
---------------
before
multi......
afterReturning
after
---------------
before
div......
afterReturning
after

可以看到,通知已经被织入到指定的切点了。

再试试看程序出异常,在add方法中添加int i = 1/0;

before
add......
afterThrowing
after

可以发现,当程序出现异常时,不会执行afterReturning了,而是会执行afterThrowing,并且就算出现异常了,也一定会执行after。

通知方法中的参数

在这些方法中,有一些参数可以帮助我们获得更多的信息:

package com.dh.aspect;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;

@Aspect
@Component
public class MyAspect {

    //前置通知可以获取切点信息
    @Before(value = "execution(* com.dh.target.Calculator.*(..))")
    public void before(JoinPoint joinPoint){
        System.out.println("切点信息:"+joinPoint);
        System.out.println("before");
    }
    //后置通知可以获取返回值
    //需要在表达式后面指定
    @AfterReturning(value = "execution(* com.dh.target.Calculator.*(..))",returning = "result")
    public void afterReturning(Object result){
        System.out.println("返回值:"+result);
        System.out.println("afterReturning");
    }
    //抛出通知可以获取异常的信息
    //也需要在表达式后面指定
    @AfterThrowing(value = "execution(* com.dh.target.Calculator.*(..))",throwing = "e")
    public void afterThrowing(Exception e){
        System.out.println("异常信息:"+e);
        System.out.println("afterThrowing");
    }
    //最终通知
    @After(value = "execution(* com.dh.target.Calculator.*(..))")
    public void after(){
        System.out.println("after");
    }
}

只测试add方法,结果:

切点信息:execution(void com.dh.target.Calculator.add())
before
add......
返回值:null
afterReturning
after

指定异常:

切点信息:execution(void com.dh.target.Calculator.add())
before
add......
异常信息:java.lang.ArithmeticException: / by zero
afterThrowing
after

表达式示例

匹配所有类public⽅法: execution(public (..)) ,第⼀个表示返回值 ..表示任意个任意类型的参数

匹配指定包下所有类所有⽅法: execution(* com.xxx.dao..(..)) ,第⼀个*表示忽略权限和返回值类型

匹配指定包下所有类所有⽅法: execution(* com.xxx.dao..*(..)) ,包含⼦包

匹配指定类所有⽅法: execution(* com.xxx.service.UserService.*(..))

匹配实现特定接⼝所有类⽅法 : execution(* com.xxx.dao.GenericDAO+.*(..))

匹配所有save开头的⽅法: execution(* save*(..)) *

匹配某个指定的⽅法: execution(* com.dh.dao.StudentService.save(..))

匹配带有⼀个整型参数的⽅法 ("execution(* *(int))")

定义切点,实现重用

像上述方法中,每一个方法的切点表达式都是一样的,我们可不可以把它提取出来呢?答案是可以的.

书写一个空白的方法,使用@Pointcut注解,写入要重用的表达式,然后在需要的地方使用value="方法()"即可。

@Pointcut(value = "execution(* com.dh.target.Calculator.*(..))")
public void pointCut(){

}

//前置通知可以获取切点信息
@Before(value = "pointCut()")
public void before(JoinPoint joinPoint){
    System.out.println("切点信息:"+joinPoint);
    System.out.println("before");
}

环绕通知

细心的你一定发现了,上面好像没有涉及到环绕通知。

没错,它被拎出来了。

 @Around(value = "pointCut()")
public void around(ProceedingJoinPoint pjp){
    try {
        System.out.println("环绕前置...");
        //执行目标方法
        Object proceed = pjp.proceed();
        System.out.println("方法返回值:"+proceed);
        System.out.println("环绕后置...");

    } catch (Throwable throwable) {
        throwable.printStackTrace();
    }
}

查看结果:

环绕前置...
切点信息:execution(void com.dh.target.Calculator.add())
before
add......
返回值:null
afterReturning
after
方法返回值:null
环绕后置...

有异常时:

环绕前置...
切点信息:execution(void com.dh.target.Calculator.add())
before
add......
异常信息:java.lang.ArithmeticException: / by zero
afterThrowing
after

可以看到,此时只执行了环绕前置。

当注释Object proceed = pjp.proceed();时:

环绕前置...
环绕后置...

惊!!!

所以明白为神马把它拎出来了吧。

环绕通知一个人差不多就可以干完所有通知的活,并且还能强大到决定是否执行切点方法。

总结

总结一下:

  • 最容易记住的就是,最终通知@After无论如何都会被执行的;

  • 然后:@Before前置通知和@Around中执行方法前的语句也都会被执行的;

  • @AfterReturing和@AfterThrowing只会二选一的出现;

  • 当出现了异常之后,剩下的都不会执行了,只会执行@After通知里的内容;

  • @Around可以决定切点方法是否执行。

2.xml配置

注解事务虽然很简单,只需要导入jar包,将目标类和通知类放入容器,然后开启注解,在通知类上加上@Aspect注解即可。

但是还是那句话,别人的类我们就不能加注解了,而且在团队协作开发的时候,xml会比注解配置更好一点点,因为可以直接在xml中看到你的配置信息,而不需要一个一个类的去找注解。

所以,xml配置还是十分有必要的。

xml的配置其实步骤和注解差不多,只是注解能完成的事情要放在xml文件中罢了。

第一步的导包完成;

第二步的配置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: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 https://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd">


<!--    目标类-->
    <bean id="calculator" class="com.dh.target.Calculator"/>
<!--    通知类-->
    <bean id="myAspect2" class="com.dh.aspect.MyAspect2"/>
    
<!--    织入信息-->
    <aop:config>
        <!--    切点信息-->
        <aop:pointcut id="point" expression="execution(* com.dh.target.Calculator.*(..))"/>
         <!--    切面信息-->
        <aop:aspect ref="myAspect2">
            <aop:before method="before" pointcut-ref="point"/>
            <aop:after-returning method="afterReturning" returning="result" pointcut-ref="point"/>
            <aop:after-throwing method="afterThrowing" throwing="e" pointcut-ref="point"/>
            <aop:around method="around" pointcut-ref="point"/>
            <aop:after method="after" pointcut-ref="point"/>
        </aop:aspect>
    </aop:config>

</beans>
posted @ 2021-04-01 10:39  deng-hui  阅读(159)  评论(0编辑  收藏  举报