SpringAOP复习

1. 初识AOP

1.1 什么是AOP

AOP 为 Aspect Oriented Programming的缩写,意思为面向切面编程

AOP 是 OOP(面向对象编程) 的延续,是软件开发中的一个热点,也是Spring框架中的一个重要内
容,利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高
程序的可重用性,同时提高了开发的效率。

好处

  1. 在程序运行期间,在不修改源码的情况下对方法进行功能增强
  2. 逻辑清晰,开发核心业务的时候,不必关注增强业务的代码
  3. 减少重复代码,提高开发效率,便于后期维护

1.2 AOP底层实现

AOP 的底层是通过 Spring提供的的动态代理技术实现的。在运行期间,Spring通过动态代理技术动态的生成代理对象,代理对象方法执行时进行增强功能的介入,在去调用目标对象的方法,从而完成功能的增强。

1.3 AOP相关术语

  • Target(目标对象):代理的目标对象 (AccountServiceImpl)
  • Proxy (代理):一个类被AOP织入增强后,就产生一个结果代理类
  • Joinpoint(连接点):所谓连接点是指那些可以被拦截到的点。在spring中,这些点指的是方法,因为spring只支持方法类型的连接点
  • Pointcut(切入点):所谓切入点是指我们要对哪些 Joinpoint 进行拦截的定义 (AccountServiceImpl.transfer())
  • Advice(通知/ 增强):所谓通知是指拦截到 Joinpoint 之后所要做的事情就是通知 分类:前置通知、后置通知、异常通知、最终通知、环绕通知

(MyAdvice)

  • Aspect(切面):是切入点和通知(引介)的结合
  • Weaving(织入):是指把增强应用到目标对象来创建新的代理对象的过程,spring采用动态代理织入。

1.4 AOP开发明确事项

1.4.1 开发阶段(我们做的)

  1. 编写核心业务代码切入点 (目标类的目标方法)
  2. 把公用代码抽取出来,制作成通知(增强功能方法) 通知
  3. 在配置文件中,声明切入点与通知间的关系,即切面

1.4.2 运行阶段(Spring框架完成的)

Spring 框架监控切入点方法的执行。一旦监控到切入点方法被运行,使用代理机制,动态创建目标对
象的代理对象,根据通知类别,在代理对象的对应位置,将通知对应的功能织入,完成完整的代码逻辑
运行。

1.4.3 底层代理实现

在 Spring 中,框架会根据目标类是否实现了接口来决定采用哪种动态代理的方式。

  • 当bean实现接口时,会用JDK代理模式
  • 当bean没有实现接口,用cglib实现( 可以强制使用cglib(在spring配置中加入<aop:aspectjautoproxy proxyt-target-class=”true”/>)

2.基于XML的AOP开发

2.1 项目搭建

2.1.1 pom.xml

<dependencies>
    <!--导入spring的context坐标,context依赖aop-->
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-context</artifactId>
        <version>5.1.5.RELEASE</version>
    </dependency>
    <!-- aspectj的织入(切点表达式需要用到该jar包) -->
    <dependency>
        <groupId>org.aspectj</groupId>
        <artifactId>aspectjweaver</artifactId>
        <version>1.8.13</version>
    </dependency>
    <!--spring整合junit-->
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-test</artifactId>
        <version>5.1.5.RELEASE</version>
    </dependency>
    <dependency>
        <groupId>junit</groupId>
        <artifactId>junit</artifactId>
        <version>4.12</version>
    </dependency>
</dependencies>

复制代码

2.1.2 业务接口

public interface AccountService {
    public void transfer();
}
复制代码

2.1.3 业务实现

@Service
public class AccountServiceImpl implements AccountService {

    @Override
    public void transfer() {
        System.out.println("转账业务执行了。。。");
    }
}
复制代码

2.1.4 通知类

@Component
public class MyAdvice {
    public void before(){
        System.out.println("前置通知...");
    }
}
复制代码

2.1.5 applicationContext.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
       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="com.lagou"/>

    <aop:config>
        <!--Aspect(切面) 切点+通知-->
        <aop:aspect ref="myAdvice">
            <!--配置目标类的transfer方法执行时,使用通知类的before方法进行前置增强  pointcut(切入点)-->
            <aop:before method="before" pointcut="execution(public void com.lagou.service.impl.AccountServiceImpl.transfer())"/>
        </aop:aspect>
    </aop:config>

</beans>
复制代码

2.1.6 测试类

@RunWith(SpringRunner.class)
@ContextConfiguration("classpath:applicationContext.xml")
public class TestAccount {

    @Autowired
    private AccountService accountService;

    @Test
    public void testTransfer(){
        accountService.transfer();
    }

}

/////
前置通知...
转账业务执行了。。。
复制代码

2.2 XML配置AOP详解

2.2.1 切点表达式

(1)语法

execution([修饰符] 返回值类型 包名.类名.方法名(参数))
复制代码
  • 访问修饰符可以省略
  • 返回值类型、包名、类名、方法名可以使用星号 * 代替,代表任意
  • 包名与类名之间一个点 . 代表当前包下的类,两个点 .. 表示当前包及其子包下的类
  • 参数列表可以使用两个点 .. 表示任意个数,任意类型的参数列表

(2)示例

execution(public void com.lagou.service.impl.AccountServiceImpl.transfer())
execution(void com.lagou.service.impl.AccountServiceImpl.*(..))
execution(* com.lagou.service.impl.*.*(..))
execution(* com.lagou.service..*.*(..))
复制代码

(3)切点表达式抽取

当多个增强的切点表达式相同时,可以将切点表达式进行抽取,在增强中使用 pointcut-ref 属性代替
pointcut 属性来引用抽取后的切点表达式。

<aop:config>

    <!--抽取的切点表达式-->
    <aop:pointcut id="myPointCut" expression="execution(public void com.lagou.service.impl.AccountServiceImpl.transfer())"/>

    <!--Aspect(切面) 切点+通知-->
    <aop:aspect ref="myAdvice">
        <!--配置目标类的transfer方法执行时,使用通知类的before方法进行前置增强  pointcut(切入点)-->
        <aop:before method="before" pointcut-ref="myPointCut"/>
    </aop:aspect>
</aop:config>
    
复制代码

2.2.2 通知类型

语法:

<aop:通知类型 method=“通知类中方法名” pointcut=“切点表达式"></aop:通知类型>
复制代码
名称 标签 说明
前置通知 aop:before 用于配置前置通知。指定增强的方法在切入点方法之前执行
后置通知 aop:afterReturning 用于配置后置通知。指定增强的方法在切入点方法之后执行
异常通知 aop:afterThrowing 用于配置异常通知。指定增强的方法出现异常后执行
最终通知 aop:after 用于配置最终通知。无论切入点方法执行时是否有异常,都会执行
环绕通知 aop:around 用于配置环绕通知。开发者可以手动控制增强代码在什么时候执行

注意:通常情况下,环绕通知都是独立使用的

3.基于XML的AOP开发

3.1 项目搭建

3.1.1 在通知类中使用注解配置织入关系,升级为切面类

/**
 * @author 振帅
 * Created on 2021/06/23 15:03
 */
@Component
@Aspect
public class MyAdvice {

    @Before("execution(* com.lagou..*.*(..))")
    public void before(){
        System.out.println("前置通知...");
    }
}
复制代码

3.1.2 开启AOP的自动代理

 <!--aop的自动代理-->
<aop:aspectj-autoproxy />
复制代码

3.2 注解配置AOP详解

3.2.1 切点表达式的抽取

/**
 * @author 振帅
 * Created on 2021/06/23 15:03
 */
@Component
@Aspect
public class MyAdvice {

    @Pointcut("execution(* com.lagou..*.*(..))")
    public void myPoint() {

    }

    @Before("MyAdvice.myPoint()")
    public void before(){
        System.out.println("前置通知...");
    }
}
复制代码

3.2.2 通知类型

名称 标签 说明
前置通知 @Before 用于配置前置通知。指定增强的方法在切入点方法之前执行
后置通知 @AfterReturning 用于配置后置通知。指定增强的方法在切入点方法之后执行
异常通知 @AfterThrowing 用于配置异常通知。指定增强的方法出现异常后执行
最终通知 @After 用于配置最终通知。无论切入点方法执行时是否有异常,都会执行
环绕通知 @Around 用于配置环绕通知。开发者可以手动控制增强代码在什么时候执行

当前四个通知组合在一起时,执行顺序如下:

@Before -> @After -> @AfterReturning(如果有异常:@AfterThrowing)

3.2.3 纯注解配置

(1)spring配置类

package com.lagou.config;

import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;

/**
 * @Author 振帅
 * @Date 2021/06/23 20:37
 */
@Configuration //指定当前类是一个Spring 配置类,当创建容器时会从该类上加载注解
@ComponentScan("com.lagou") //替代 <context:component-scan base-package="com.lagou"/>
@EnableAspectJAutoProxy //替代 <aop:aspectj-autoproxy />
public class SpringConfig {


}
复制代码

(2)测试类

package com.lagou;

import com.lagou.config.SpringConfig;
import com.lagou.service.AccountService;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringRunner;

/**
 * @author 振帅
 * Created on 2021/06/22 17:26
 */
@RunWith(SpringRunner.class)
@ContextConfiguration( classes = SpringConfig.class)
public class TestAccount {

    @Autowired
    private AccountService accountService;

    @Test
    public void testTransfer(){
        accountService.transfer();
    }

}
复制代码

4.AOP优化转账案例

依然使用前面的转账案例,将两个代理工厂对象直接删除!改为spring的aop思想来实现

4.1 xml配置实现

<!--AOP配置-->
<aop:config>
    <!--切点表达式-->
    <aop:pointcut id="myPointcut" expression="execution(* com.lagou.service..*.*(..))"/>
    <!-- 切面配置 -->
    <aop:aspect ref="transactionManager">
        <aop:before method="beginTransaction" pointcut-ref="myPointcut"/>
        <aop:after-returning method="commit" pointcut-ref="myPointcut"/>
        <aop:after-throwing method="rollback" pointcut-ref="myPointcut"/>
        <aop:after method="release" pointcut-ref="myPointcut"/>
    </aop:aspect>
</aop:config>
复制代码

4.2 注解配置实现

/**
 * @Author 振帅
 * @Date 2021/06/23 20:51
 */
@Component
@Aspect
public class TransactionManager {

    @Autowired
    ConnectionUtils connectionUtils;

    @Around("execution(* com.lagou.serivce..*.*(..))")
    public Object around(ProceedingJoinPoint pjp) {
        Object object = null;
        try {
            // 开启事务
            connectionUtils.getThreadConnection().setAutoCommit(false);
            // 业务逻辑
            pjp.proceed();
            // 提交事务
            connectionUtils.getThreadConnection().commit();
        } catch (Throwable throwable) {
            throwable.printStackTrace();
            // 回滚事务
            try {
                connectionUtils.getThreadConnection().rollback();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        } finally {
            try {
                connectionUtils.getThreadConnection().setAutoCommit(true);
                connectionUtils.getThreadConnection().close();
                connectionUtils.removeThreadConnection();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
        return object;
    }
}
复制代码
© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享