SpringAOP使用之初始版本

SpringAOP

1、SpringAOP中的一些核心概念

  • 连接点(JoinPoint):目标对象中的可以增强的方法,可以通俗的理解成是目标类中的所有方法
  • 切入点(pointCut):要对目标对象中的方法进行增强的方法,是在连接点的基础之上衍生而来的; 目的:需要对指定的方法进行增强;
  • 通知(Advice):进行功能增强的代码。比如说事务操作中,在切入点方法执行开启事务,在切入点方法正常执行成功之后提交事务或者在切入点方法执行失败之后回滚事务等等操作;而这里的开启事务、提交事务、回滚事务都是通知;
  • 切面(Aspect):上面的切入点+通知进行生成的效果就是切面。也就是说在切面中配置的是增强的是哪个方法,怎么来进行增强。比如说事务操作中,在切入点方法执行开启事务,在切入点方法正常执行成功之后提交事务或者在切入点方法执行失败之后回滚事务等等操作。而切面就是将通知和切入点这样子来进行配置的;
  • 织入(Weaving),把切入点和通知进行结合,生成代理对象的过程。也就是说织入之后,使用的是代理对象来调用方法的时候,而不再是原来的对象调用方法了。而Spring又是刚刚好能够提供给我们代理对象调用方法,而不是原始对象来调用方法。

画一张图来展示一下:

2、使用SpringAOP功能,开发者和Spring框架的职责

在创建对象的时候,已经将创建对象的权限交给了Spring来进行管理。那么使用AOP的时候,Spring不知道开发者要做的什么事情,所以开发者自己做些事情,让Spring也来帮开发者来着一些事情,所以开发者和Spring都需要来做一些事情:

开发者做的事情:在切面类中编写通知+切入点表达式

Spring框架做的事情:织入,将切面+切入点+原始对象结合起来,生成代理对象

3、代码演示(xml版本)

使用xml方法演示之后,再使用注解方式来进行操作。

0、导入依赖

    <dependencies>
        <!--Spring上下文核心包-->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>5.0.2.RELEASE</version>
        </dependency>
        <!--AOP的实现包-->
        <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjweaver</artifactId>
            <version>1.8.9</version>
        </dependency>
        <!--Spring和单元测试集成-->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-test</artifactId>
            <version>5.0.2.RELEASE</version>
        </dependency>
        <!--单元测试-->
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.12</version>
        </dependency>
        <!-- https://mvnrepository.com/artifact/ch.qos.logback/logback-classic -->
        <dependency>
            <groupId>ch.qos.logback</groupId>
            <artifactId>logback-classic</artifactId>
            <version>1.2.11</version>
        </dependency>


        <!--需要使用spring中的jdbc操作-->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-jdbc</artifactId>
            <version>5.1.2.RELEASE</version>
        </dependency>

        <!--连接数据库操作-->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>8.0.27</version>
        </dependency>

    </dependencies>

1、编写切面类

切面类中的方法是通知

public class LogAspect {

    private static Logger logger = LoggerFactory.getLogger(LoggerFactory.class);

    public void startTransaction(){
        logger.info("----->开始事务<----");
    }

    public void commitTransaction(){
        logger.info("----->提交事务<----");
    }

    public void rollbackTransaction(){
        logger.error("----->回滚事务<----");
    }

    public void printResult(){
        logger.info("最终得到结果");
    }

}

2、编写目标类

因为使用的是JDK的代理模式,所以需要使用到接口

public interface UserService {

    boolean save();

    boolean update();

}

对应的实现类,也叫目标类,目标类中的方法是连接点,需要增强的方法是切入点。

如一下方法中:

setBeanName和printCurrentBeanName是连接点,而现在做的事情是需要对update和save方法进行增强,所以是切入点。

public class UserServiceImpl implements UserService, BeanNameAware {

    private static Logger logger = LoggerFactory.getLogger(UserServiceImpl.class);

    private String beanName;

    @Override
    public void setBeanName(String s) {
        this.beanName = s;
    }
    
    @Override
    public boolean update() {
        try {
            // 模拟业务操作
            Thread.sleep(1230);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return false;
    }

    public boolean save(){
        logger.info("已经向数据库中插入了数据");
        int i = 1/0;
        return true;
    }
    
    public void printCurrentBeanName(){
        logger.info("当前bean的名称是:{}",beanName);
    }
    
}

3、配置切面

下面需要来编写切面中的通知和目标类中的切入点进行织入,这一步是Spring来做的。而我们需要来做的就是切入点和切面类如何来搭配进行执行。

编写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: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 id="userService" class="com.guang.spring.aop.demo1.xml.service.impl.UserServiceImpl"/>
    <!--将切面注入到容器中去-->
    <bean id="logAspect" class="com.guang.spring.aop.demo1.xml.aspect.LogAspect"/>

    <aop:config>

        <!--下面来书写下简化写法-->
        <aop:aspect ref="logAspect">

            <!--在切入点方法执行之前执行-->
            <aop:before method="startTransaction" pointcut="execution(public boolean com.guang.spring.aop.demo1.xml.service.impl.UserServiceImpl.save())"/>

            <!--为了验证最终通知的执行顺序,将其移到切入点正常执行和异常执行前面,看下打印顺序,验证了在下面的描述。但是通常写的时候会将其放在最后来进行执行-->
            <aop:after method="printResult" pointcut="execution(public public boolean com.guang.spring.aop.demo1.xml.service.impl.UserServiceImpl.save())"/>
            <!--在切入点方法正常执行之后执行。这里强调的是切入点方法正常执行,因为有可能执行报错-->
            <aop:after-returning method="commitTransaction" pointcut="execution(public boolean com.guang.spring.aop.demo1.xml.service.impl.UserServiceImpl.save())"/>
            <!--异常通知,切入点方法抛出异常之后执行-->
            <aop:after-throwing method="rollbackTransaction" pointcut="execution(public boolean com.guang.spring.aop.demo1.xml.service.impl.UserServiceImpl.save())"/>
            <!--最终通知,无论在切入点执行成功还是执行失败之后,都会来执行这个方法-->
            <!--
            需要注意的是:最终通知不一定是最后执行的,和配置的顺序是有关系的
            这里类似:
                    try{
                    前置通知;
                    目标对象.方法;
                    后置通知;       ++++++
                    最终通知;       ++++++  这里顺序是可变的  到底是最终还是后置通知先执行,是我们自定义的
                    }catch(){
                    异常通知;       ++++++
                    最终通知;       ++++++  这里顺序是可变的  到底是最终还是异常通知先执行,是我们自定义的
                    }

                    所以最终通知不是finaly代码中的内容
            -->
            <!--<aop:after method="printResult" pointcut="execution(public public boolean com.guang.spring.aop.demo1.xml.service.impl.UserServiceImpl.save())"/>-->

            <!--
            还剩最后的一种通知方式,也是最牛逼的通知方式。环绕通知!<aop:arount>。 比如说一种需求,是上面几个方法都不具备的
            如果说需要来对一个方法在前面做些功能,在后面来做些功能,但是对于前面几个功能来说是比较分散的,无法综合起来进行使用
            eg:统计一下一个方法的执行时间!上面四个方法无法将其组装到一起。如果按照上面的方式来的话,需要在前置通知中写上初始时间,在方法执行完成之后得到时间
               然后两个时间相减可以得到最终结果,虽然说可以做,比如说通过ThreadLocal方式,但是这种使用方式操作起来是比较麻烦的
               但是这种使用环绕通知来说,是最好使用的!!!
            -->
        </aop:aspect>
</beans>

4、测试

    @org.junit.Test
    public void testSaveAOP(){
        ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("classpath:springaop.xml");
        // 获取得到代理对象,只能够通过接口来进行获取得到,所以这里不能够写具体的实现类
         UserService userService = context.getBean("userService",UserService.class);
         userService.save();
    }

5、打印结果

2022-04-23 16:26:10.341  - com.guang.spring.aop.demo1.xml.aspect.LogAspect 17 ----->开始事务<---- 
2022-04-23 16:26:10.341  - com.guang.spring.aop.demo1.xml.service.impl.UserServiceImpl 36 已经向数据库中插入了数据 
2022-04-23 16:26:10.342  - com.guang.spring.aop.demo1.xml.aspect.LogAspect 29 最终得到结果 
2022-04-23 16:26:10.342  - com.guang.spring.aop.demo1.xml.aspect.LogAspect 25 ----->回滚事务<---- 

java.lang.ArithmeticException: / by zero

4、切入点表达式

经过上面的代码演示,可以发现切入点表达式的书写是比较麻烦的,下面来学习一下切入点表达式。

语法如下所示:

execution([权限修饰符] 返回值类型 包名.类名.方法名(参数列表))

在上面的xml中就是按照这种方式来书写的。虽然说麻烦,但是比较精确的指定给哪个类中的哪个方法来使用AOP。

但是也有一些情况下,因为配置起来显得相当麻烦,可以有对应的简化方式来书写。

  • 权限修饰符:可以省略。默认使用的就是public(但是并非绝对);
  • 返回值类型:因为java中的返回值太多太多,可以使用任意字符 * 来代替任何返回值,甚至如String,不需要写全,使用Stri*来替代都可以;
  • 包名:使用.表达的是使用当前包下的类或者是子包。如:com.guang.aop.xml.service.HelloWorld;使用..表示当前包下的任意包,可以是子包,也可以是孙子包。如:com..service.HelloWorld;也可以使用*号来代替任意一个包的名称,如:com.guang.星号.service.HelloWorld()
  • 类名:可以使用完整的类名,如:UserService;也可以使用*Service;也可以使用星号
  • 方法名称:可以使用完整的方法名,如addInteger;也可以使用星号,如:add星号,表示以add开头的方法;
  • 方法参数:可以写上参数的具体类型,如Integer、String。这里需要注意:写上了几个参数类型,Spring就只会取找对应参数类型的方法。而不会去找其他的,这就是方法重载。也可以使用..来代替任意方法

具体的参考:

execution(public void com.guang.dao.impl.UserDao.save())
execution(void com.guang.dao.impl.UserDao.*(..))
execution(* com.guang.dao.impl.*.*(..))
execution(* com.guang.dao..*.*(..))
execution(* com.guang.service..*.*(..)) --这种添加上service的是通常采用的方式
execution(* *..*.*(..)) --不建议使用

4.1、切入点表达式的抽取

在上面中其实有更加简单的使用方式:

<?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 id="userService" class="com.guang.spring.aop.demo1.xml.service.impl.UserServiceImpl"/>
    <!--将切面注入到容器中去-->
    <bean id="logAspect" class="com.guang.spring.aop.demo1.xml.aspect.LogAspect"/>
    <aop:config>
        <aop:aspect ref="logAspect">
            <aop:pointcut id="myExpression" expression="execution(public boolean com.guang.spring.aop.demo1.xml.service.impl.UserServiceImpl.save())"/>
            <!--在切入点方法执行之前执行-->
            <aop:before method="startTransaction" pointcut-ref="myExpression"/>

            <!--为了验证最终通知的执行顺序,将其移到切入点正常执行和异常执行前面,看下打印顺序,验证了在下面的描述。但是通常写的时候会将其放在最后来进行执行-->
            <aop:after method="printResult" pointcut-ref="myExpression" />
            <!--在切入点方法正常执行之后执行。这里强调的是切入点方法正常执行,因为有可能执行报错-->
            <aop:after-returning method="commitTransaction" pointcut-ref="myExpression"/>
            <!--异常通知,切入点方法抛出异常之后执行-->
            <aop:after-throwing method="rollbackTransaction" pointcut-ref="myExpression"/>
            <!--最终通知,无论在切入点执行成功还是执行失败之后,都会来执行这个方法-->
        </aop:aspect>
    </aop:config>
</beans>

5、通知

通知就是给切入点方法增强的方式。分为五种:

5.1、通知的类型

名称 标签 说明
前置通知 <aop:before> 通知方法在切入点方法之前执行
后置通知 <aop:after-returning> 在切入点方法正常执行之后,执行通知方法
异常通知 <aop:after-throwing> 在切入点方法抛出异常时,执行通知方法
最终通知 <aop:after> 无论切入点方法是否有异常,最终都执行通知方法
环绕通知 <aop:around> 通知方法在切入点方法之前、之后都执行

如在上面的配置文件中配置的内容使用,在上面中也指出了前置通知、后置通知、异常通知、最终通知和环绕通知,其中对于前面四种通知来说,不能够简单的理解成为try...catch...finally...这种方式

                    try{
                    	前置通知;
                    	目标对象.方法;
                    	后置通知;       ++++++
                    	最终通知;       ++++++  这里顺序是可变的  到底是最终还是后置通知先执行,是我们自定义的
                    }catch(){
                    	异常通知;       ++++++
                    	最终通知;       ++++++  这里顺序是可变的  到底是最终还是异常通知先执行,是我们自定义的
                    }

因为这里的这和在xml中配置的顺序是有关系的。

第一种情况:如果说最终通知配置在后置通知或者是异常通知前面,那么将不符合上面的方式,最终通知将会在后置通知和异常通知执行之前执行。

第二种情况:如果最终通知配置在最后,那么将符合上面的执行方式。

注意:如果是注解版的,那么将会是上面的第一种情况,无论怎么样调整顺序。

5.2、通知类型推荐

环绕通知就是可以在切入点方法执行之前、切入点方法正常执行之后、切入点方法执行出现异常之后、最终应该怎么操作等等。

比如说有个场景:需要统计一下方法的执行时间。首先需要记录一下起始时间,然后方法执行完成记录一下最终时间,然后来计算差值。

如果使用前置通知+后置通知的方式来进行操作,那么无法获取得到起始时间,虽然也可以通过ThreadLocal来进行实现,但是这样子来做相对麻烦一些。所以不需要线程携带数据,可以使用环绕通知来进行操作。

5.3、补充说明

如果想要在通知方法中,获取切入点对象。可以在通知方法里直接增加以下参数:

  • Spring提供的运行时连接点/切入点对象:
类名 介绍
org.aspectj.lang.JoinPoint 切入点对象,
用于前置、后置、异常、最终通知,作为通知方法的形参
org.aspectj.lang.ProceedingJoinPoint 切入点对象,是JoinPoint的子接口
用于环绕通知,作为通知方法的参数
  • org.aspectj.lang.JoinPoint的常用方法
返回值 方法名 说明
java.lang.Object[] getArgs() 连接点的实参值.
Signature getSignature() 连接点方法签名
java.lang.Object getTarget() Returns the target object.
java.lang.Object getThis() Returns the currently executing object.
java.lang.String toLongString() Returns an extended string representation of the join point.
java.lang.String toShortString() Returns an abbreviated string representation of the join point.
java.lang.String toString()
JoinPoint.StaticPart getStaticPart() Returns an object that encapsulates the static parts of this join point.
java.lang.String getKind() Returns a String representing the kind of join point.
  • ProceedingJoinPointJoinPoint的子接口,它除了上述方法,还有
返回值 方法名 说明
java.lang.Object proceed() 执行下一个通知;
如果后边没有通知了,调用目标方法
java.lang.Object proceed(Object[] args) 执行下一个通知;
如果后边没有通知了,调用目标方法

示例

public class MyAdvice {

    public void before(JoinPoint jp) {
        System.out.println("前置:" + jp.getSignature());
    }

    public void afterReturning(JoinPoint jp){
        System.out.println("后置:" + jp.getSignature());
    }

    public void afterThrowing(JoinPoint jp){
        System.out.println("异常:" + jp.getSignature());
    }

    public void after(JoinPoint jp){
        System.out.println("最终:" + jp.getSignature());
    }

    public Object around(ProceedingJoinPoint pjp){
        Object result = null;
        try {
            System.out.println("==环绕:前置通知==");


            //调用对象的方法,返回方法执行结果
            result = pjp.proceed(pjp.getArgs());
            
            System.out.println("==环绕:后置通知==");
        } catch (Throwable throwable) {
            System.out.println("==环绕:异常通知==");
            throwable.printStackTrace();
        } finally {
            System.out.println("==环绕:最终通知==");
        }
        return result;
    }
}

通知中绑定参数

  • 不同类型的通知,可以绑定的参数是不同的

前置通知

  • 在通知中,可以绑定参数:获取切入点方法的实参
  • 通知方法:
public void before(JoinPoint jp, Object params){
    System.out.println("==前置通知==");
    System.out.println("连接点:" + jp.getSignature());
    System.out.println("实参:" + params);
}
  • 切入点表达式:
<aop:before method="before" 
            pointcut="execution(* com.itheima..*.*(..)) and args(params)"/>

后置通知

  • 在通知中,可以绑定参数:获取切入点方法的实参和返回值
  • 通知方法:
public void afterReturning(JoinPoint jp, Object params, Object result){
    System.out.println("==后置通知==");
    System.out.println("方法参数:" + params);
    System.out.println("返回值:" + result);
}
  • 切入点表达式:
<aop:after-returning method="afterReturning" 
                     pointcut="execution(* com.itheima..*.*(..)) and args(params)" 
                     returning="result"/>

异常通知

  • 在通知中,可以绑定参数:获取切入点方法的实参,和异常信息对象
  • 通知方法:
public void afterThrowing(Exception ex, Object params){
    System.out.println("==异常通知==");
    System.out.println("方法实参:" + params);
    System.out.println("异常:" + ex);
}
  • 切入点表达式:
<aop:after-throwing method="afterThrowing" 
                    pointcut="execution(* com.itheima..*.*(..)) and args(params)" 
                    throwing="ex"/>

最终通知

  • 在通知中,可以绑定参数:获取方法的实参
  • 通知方法:
public void after(Object params){
    System.out.println("==最终通知==");
    System.out.println("方法实参:" + params);
}
  • 切入点表达式:
<aop:after method="after" 
           pointcut="execution(* com.itheima..*.*(..)) and args(params)"/>

编写配置类

@Component
@Aspect
public class CaculateTimeAspect {

    private static Logger logger = LoggerFactory.getLogger(CaculateTimeAspect.class);

    /**
     * 使用环绕通知来进行曹邹。调用者调用时,spring会执行这个方法。这里需要自己来调用目标对象
     * @param proceedingJoinPoint spring传入进来的切入点对象(切入点对象中有目标方法、目标对象、目标方法参数)
     *                            ProceedingJoinPoint:直接看对象名称,就可以很清晰的看出来是处理连接点的  
     *             // 获取得到实参
     *             Object[] args = proceedingJoinPoint.getArgs();
     *             // 目标方法执行的结果
     *             Object methodExeMethod = methodExeMethod = proceedingJoinPoint.proceed(args);              
     * @return
     */
    public Object caculateTimeForMethod(ProceedingJoinPoint proceedingJoinPoint){
        Object methodExeMethod = null;
        StopWatch stopWatch = new StopWatch("caculate method time");
        stopWatch.start();
        try {
            // 获取得到实参
            Object[] args = proceedingJoinPoint.getArgs();
            // 目标方法执行的结果
            methodExeMethod = proceedingJoinPoint.proceed(args);
        } catch (Throwable throwable) {
            // 如果有异常,那么来打印对象
            // throwable.printStackTrace();
            logger.error("异常信息是:{},最终功能是:{}",throwable.getMessage(),throwable);
        }finally {
            stopWatch.stop();
        }
        // 获取得到最终的执行时间
        double totalTimeSeconds = stopWatch.getTotalTimeSeconds();
        logger.info("最终的执行时间是:{}秒",totalTimeSeconds);
        return methodExeMethod;
    }
}

使用xml方式:

</aop:config>        
	<aop:aspect ref="caculateTimeAspect">
            <aop:around method="caculateTimeForMethod" pointcut="execution(public void com.guang.spring.aop.demo1.xml.service.impl.UserServiceImpl.update())"/>
        </aop:aspect>
    </aop:config>

使用注解:

@Component
@Aspect
public class CaculateTimeAspect {

    private static Logger logger = LoggerFactory.getLogger(CaculateTimeAspect.class);

    /**
     * 使用环绕通知来进行曹邹。调用者调用时,spring会执行这个方法。这里需要自己来调用目标对象
     * @param proceedingJoinPoint spring传入进来的切入点对象(切入点对象中有目标方法、目标对象、目标方法参数)
     *                            ProceedingJoinPoint:直接看对象名称,就可以很清晰的看出来是处理连接点的
     *             // 获取得到实参
     *             Object[] args = proceedingJoinPoint.getArgs();
     *             // 目标方法执行的结果
     *             Object methodExeMethod = methodExeMethod = proceedingJoinPoint.proceed(args);
     * @return
     */
    @Around("execution(public boolean com.guang.spring.aop.demo3.aop.service.impl.UserServcieImpl.doBussiness())")
    public Object caculateTimeForMethod(ProceedingJoinPoint proceedingJoinPoint){
        Object methodExeMethod = null;
        StopWatch stopWatch = new StopWatch("caculate method time");
        stopWatch.start();
        try {
            // 获取得到实参
            Object[] args = proceedingJoinPoint.getArgs();
            // 目标方法执行的结果
            methodExeMethod = proceedingJoinPoint.proceed(args);
        } catch (Throwable throwable) {
            // 如果有异常,那么来打印对象
            // throwable.printStackTrace();
            logger.error("异常信息是:{},最终功能是:{}",throwable.getMessage(),throwable);
        }finally {
            stopWatch.stop();
        }
        // 获取得到最终的执行时间
        double totalTimeSeconds = stopWatch.getTotalTimeSeconds();
        logger.info("最终的执行时间是:{}秒",totalTimeSeconds);
        return methodExeMethod;
    }
}

测试:

    @org.junit.Test
    public void testAnnotateAOP(){
         AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(Demo3Config.class);
        UserService userService = context.getBean(UserService.class);
        userService.doBussiness();
    }

打印:

2022-04-24 09:28:21.065  - com.guang.spring.aop.demo3.aop.aspect.CaculateTimeAspect 51 最终的执行时间是:1.235秒 

6、代码演示(xml+注解版)

1、创建接口、目标类和目标类方法

public interface UserService {

    void say();

}
@Service
public class UserServcieImpl implements UserService {

    private static Logger logger = LoggerFactory.getLogger(UserServcieImpl.class);

    @Override
    public void say() {
        logger.info("say spring annotate AOP");
    }
}

2、配置切面,抽取切入点表达式

@Component
@Aspect
public class LogAspect {

    private static Logger logger = LoggerFactory.getLogger(LogAspect.class);

    /**
     * 抽取切入点
     *
     */
    @Pointcut("execution(public void com.guang.spring.aop.demo2.aop.service.impl.UserServcieImpl.say())")
    public void pointCut(){
    }

    /**
     * 也可以是其他类中的切入点表达式,需要使用的时候添加上全限定类名 即可
     *
     * 如果是本类中的,所以不需要引入本类中的全限定类名,而是直接写上对应的方法即可!
     * 如果是其他类中的,那么应该添加上对应的全限定类名即可
     */
    @Before("pointCut()")
    public void startTransaction(){
        logger.info("----->开始事务<----");
    }

    @AfterReturning("pointCut()")
    public void commitTransaction(){
        logger.info("----->提交事务<----");
    }

    @AfterThrowing("pointCut()")
    public void rollbackTransaction(){
        logger.error("----->回滚事务<----");
    }

    /**
     * 注解版的无法调整对应的执行顺序!如果非要使用对应的顺序的话,那么应该直接使用环绕通知
     *
     * 注解版的执行顺序:before--->after--->after-returning/after-throwning
     */
    @After("pointCut()")
    public void printResult(){
        logger.info("最终得到结果");
    }

}

3、对应的配置文件

<?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">

    <!--使用xml+注解配置在bean之上,需要来使用扫描-->
    <context:component-scan base-package="com.guang.spring.aop.demo2"/>

    <!--为了让配置类上的@Aspect注解生效,还需要开启自动代理-->
    <aop:aspectj-autoproxy/>

</beans>

对应的测试类:

    @org.junit.Test
    public void testAnnotateAOP(){        
        ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("classpath:springannoteaop.xml");
        UserService userService = context.getBean(UserService.class);
        System.out.println(userService);
        userService.say();
    }

7、代码演示(纯注解版)

1、使用配置类代替配置文件

在xml+注解版的基础之上,将配置文件修改成配置类

@ComponentScan(basePackages = "com.guang.spring.aop.demo2")
// 开启切面自动代码,让@AspectJ生效
@EnableAspectJAutoProxy
public class Demo2Config {

}

2、测试类

    @org.junit.Test
    public void testAnnotateAOP(){
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(Demo2Config.class);        
        UserService userService = context.getBean(UserService.class);
        System.out.println(userService);
        userService.say();
    }

8、总结

1、要想使用SpringAOP功能,首先要将开发者和Spring框架各自需要做的事情分离开来;

2、使用注解版的切面,要想切面生效,需要添加对应的配置;

3、SpringAOP的效果是解耦合,降低代码复杂度,本质是动态代理;

posted @ 2022-04-23 17:36  写的代码很烂  阅读(88)  评论(0编辑  收藏  举报