Spring框架之AOP

1 背景介绍

AOP: Aspect Oriented Programming , 面向切面的编程;

目的: 为了减少重复代码, 易维护; 为原始的业务代码进行功能增强;

场景:
(1) 比如AccountServiceImpl类中有增删改查四个方法; 最原始开发方法要在这四个方法中都增加事务管理的代码,我们称为业务代码;
使用AOP技术,我们可以把这部分事务管理的代码提取出来, 并且只有当程序运行时, 自动将事务管理这部分代码加入到业务代码中执行;

原理: 动态代理技术实现了AOP的思想, Spring框架继承了Cglib动态代理工具类.

推荐使用: 环绕通知.

关键术语:
连接点 JointPoint : 业务代码中的方法;
切入点 Pointcut : 即哪些类中的方法需要功能增强;
切面类: 通过AOP提取出来的增强功能代码;
切面: 将切面类中的增强方法 加入到 切入点中的业务代码中;

2 举例

IDEA工具下maven工程,
(1) 需要的jar包依赖

    <dependencies>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>5.1.9.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-aop</artifactId>
            <version>5.1.9.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjweaver</artifactId>
            <version>1.9.4</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-test</artifactId>
            <version>5.1.9.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.12</version>
        </dependency>
    </dependencies>

(2) 业务接口和接口实现类

public interface AccountService {

    /**
     * 模拟保存操作
     */
    void save();

    /**
     * 模拟更新操作
     * @param i
     */
    void update(int i);

    /**
     * 模拟删除操作
     * @return
     */
    int delete();

}
public class AccountServiceImpl implements AccountService {
    @Override
    public void save() {
        System.out.println("模拟保存账户...");
        int i = 1/0;
    }

    @Override
    public void update(int i) {
        System.out.println("更新账户..." + i);
    }

    @Override
    public int delete() {
        System.out.println("删除账户....");
        return 1000;
    }
}

(3) 切面类

public class LogUtil {

    //前置通知: 作用, 可以在切入点方法执行前, 获取到切入点的方法参数, 实现对参数进行增强
    public void beforePrintLog(JoinPoint joinPoint) throws Throwable {
        //获取切入点的参数
        Object[] args = joinPoint.getArgs();
        System.out.println("前置通知beforePrintLog = " + Arrays.toString(args));
    }

    //后置通知: 作用, 可以对切入点方法的返回值 进行增强
    public void afterReturningPrintLog(JoinPoint joinPoint, Object rtValue) throws Throwable {
        //获取切入点方法的返回值
        System.out.println("后置通知afterReturningPrintLog = " + rtValue);
    }

    //异常通知: 作用: 可以在切入点方法执行过程中,出现异常, 对异常进行处理, 进行增强
    public void afterThrowingPrintLog(JoinPoint joinPoint, Throwable t) throws Throwable {
        //打印异常信息
        t.printStackTrace();
        System.out.println("异常通知afterThrowingPrintLog = " + t.getMessage());
    }

    //最终通知: 作用: 用于是否资源
    public void afterPrintLog(JoinPoint joinPoint) throws Throwable {
        System.out.println("最终通知afterPrintLog");
    }

    //环绕通知: 作用: 围绕着切入点方法进行执行, 更加灵活的对切入点方法进行增强
    public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
        Object obj = null;
        try {
            //前置通知
            this.beforePrintLog(joinPoint);

            //业务方法执行 (获取参数, 执行方法)
            Object[] args = joinPoint.getArgs();
            obj = joinPoint.proceed(args);

            //后置通知
            this.afterReturningPrintLog(joinPoint, obj);
        } catch (Throwable throwable) {
            throwable.printStackTrace();
            //异常通知
            this.afterThrowingPrintLog(joinPoint, throwable);

        } finally {
            //最终通知
            this.afterPrintLog(joinPoint);
        }

        return obj;
    }
}

3 xml实现AOP

AOP支持的5种通知:
try{
事前通知: aop:before, 常用于开启事务
事后通知: aop:after-returning , 常用于提交事务, 对返回值进行增强;
}catch{
异常通知: aop:after-throwing, 常用于异常时,异常信息提取;
}finally{
最终通知: aop:after, 常用于释放资源
}

环绕通知: round, 更加灵活, 可以指定每个通知的具体顺序

<?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
       https://www.springframework.org/schema/aop/spring-aop.xsd">

    <!--配置Service-->
    <bean id="accountService" class="com.itheima.service.impl.AccountServiceImpl"></bean>


    <!--配置AOP-->
    <!--1.配置日志切面类-->
    <bean id="logUtil" class="com.itheima.utils.LogUtil"></bean>

    <!--2.AOP的配置-->
    <aop:config>
        <!--配置通用的切入点表达式-->
        <aop:pointcut id="pc" expression="execution(* com.itheima.service..*.*(..))"></aop:pointcut>

        <!--配置切面-->
        <aop:aspect id="logUtil" ref="logUtil">
            <!--配置Service中的业务方法 与 日志切面类中的增强方法 进行关联-->
            <!--前置通知: 执行时机, 在切入点方法执行前 执行-->
            <aop:before method="beforePrintLog" pointcut-ref="pc"></aop:before>
            <!--后置通知: 执行时机, 在切入点方法执行完成后, 执行-->
            <aop:after-returning method="afterReturningPrintLog" pointcut-ref="pc" returning="rtValue"></aop:after-returning>
            <!--异常通知: 执行时机, 在切入点方法执行过程中抛出异常时, 执行-->
            <aop:after-throwing method="afterThrowingPrintLog" pointcut-ref="pc" throwing="t"></aop:after-throwing>
            <!--最终通知: 执行时机, 在切入点方法执行完成后, 最终执行(不关心是否出现了异常, 都会执行)-->
            <aop:after method="afterPrintLog" pointcut-ref="pc"></aop:after>

            <!--环绕通知: 执行时机, 围绕着切入点方法进行执行, 更加灵活的对切入点方法进行增强-->
            <aop:around method="around" pointcut-ref="pc"></aop:around>

        </aop:aspect>
    </aop:config>
</beans>

4. 半注解半XML方式实现AOP

(1) spring核心配置文件开启SpringIOC和AOP的注解扫描

开启SPringIOC的 注解扫描

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

开启AOP的注解扫描

<aop:aspectj-autoproxy></aop:aspectj-autoproxy>

(2) 其他AOP配置改为注解配置

@Component("logUtil")
//切面注解
@Aspect
public class LogUtil {

    //配置通用的切入点表达式
    @Pointcut("execution(* com.itheima.service..*.*(..))")
    public void pointCut(){
    }

    //前置通知: 作用, 可以在切入点方法执行前, 获取到切入点的方法参数, 实现对参数进行增强
    //@Before("pointCut()")
    public void beforePrintLog(JoinPoint joinPoint) throws Throwable {
        //获取切入点的参数
        Object[] args = joinPoint.getArgs();
        System.out.println("前置通知beforePrintLog = " + Arrays.toString(args));
    }

    //后置通知: 作用, 可以对切入点方法的返回值 进行增强
    //@AfterReturning(value = "pointCut()", returning = "rtValue")
    public void afterReturningPrintLog(JoinPoint joinPoint, Object rtValue) throws Throwable {
        //获取切入点方法的返回值
        System.out.println("后置通知afterReturningPrintLog = " + rtValue);
    }

    //异常通知: 作用: 可以在切入点方法执行过程中,出现异常, 对异常进行处理, 进行增强
    //@AfterThrowing(value = "pointCut()", throwing="t")
    public void afterThrowingPrintLog(JoinPoint joinPoint, Throwable t) throws Throwable {
        //打印异常信息
        t.printStackTrace();
        System.out.println("异常通知afterThrowingPrintLog = " + t.getMessage());
    }

    //最终通知: 作用: 用于是否资源
    //@After("pointCut()")
    public void afterPrintLog(JoinPoint joinPoint) throws Throwable {
        System.out.println("最终通知afterPrintLog");
    }
    //环绕通知: 作用: 围绕着切入点方法进行执行, 更加灵活的对切入点方法进行增强
    @Around("pointCut()")
    public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
        Object obj = null;
        try {
            //前置通知
            this.beforePrintLog(joinPoint);
            //业务方法执行 (获取参数, 执行方法)
            Object[] args = joinPoint.getArgs();
            obj = joinPoint.proceed(args);
            //后置通知
            this.afterReturningPrintLog(joinPoint, obj);
        } catch (Throwable throwable) {
            throwable.printStackTrace();
            //异常通知
            this.afterThrowingPrintLog(joinPoint, throwable);
        } finally {
            //最终通知
            this.afterPrintLog(joinPoint);
        }
        return obj;
    }
}

5. 纯注解实现AOP

SpringIOC和AOP注解扫描 这两句xml语句, 改为注解方法,
在类前@Configuration 注解表示该类时一个Spring配置文件;

//表示当前类是一个SPring配置文件
@Configuration
//开启SpringIOC的注解扫描
@ComponentScan(value ="com.itheima")
//开启SpringAOP注解的扫描的支持
@EnableAspectJAutoProxy
public class SpringConfig {
}

6. 测试类

半注解半xml的测试类写法

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = {"classpath:applicationContext.xml"})

纯注解的测试类写法

@RunWith(SpringJUnit4ClassRunner.class)
//加载配置文件 注解的形式( 加载的是 java的字节码文件)
@ContextConfiguration(classes= {SpringConfig.class })
public class AccountServiceTest {

    //依赖注入
    @Autowired
    private AccountService accountService;

    @Test
    public void save() {
        accountService.save();
    }

}

7. 事务管理的xml的AOP实现

在init()方法中,配置事务同步管理器, 底层是ThreadLocal, 以此确保service层使用的connection和mybatis执行sql时使用的connection是同一个连接.

TransactionSynchronizationManager.initSynchronization();
    <!--1.配置切面类 TranscationManager-->
    <bean id="transcationManager" class="com.itheima.utils.TranscationManager" init-method="init">
        <!--配置相关内容 (数据源)-->
        <property name="dataSource" ref="druidDataSource"/>
    </bean>
public class TranscationManager {

    //依赖注入一个连接对象
    private DataSource dataSource;

    public void setDataSource(DataSource dataSource) {
        this.dataSource = dataSource;
    }

    //保证Service层使用的Connection 与 Mybatis执行sql语句时 用的Connection 是同一个对象
    public void init(){
        TransactionSynchronizationManager.initSynchronization();
    }


    //开启事务 ( 前置通知 )
    public void begin(){
        try {
            DataSourceUtils.getConnection(dataSource).setAutoCommit(false);
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }

    //提交事务 ( 后置通知 )
    public void commit(){
        try {
            DataSourceUtils.getConnection(dataSource).commit();
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }

    //回滚事务 ( 异常通知 )
    public void rollback(){
        try {
            DataSourceUtils.getConnection(dataSource).rollback();
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }

    //释放资源 ( 最终通知 )
    public void close(){
        try {
            DataSourceUtils.getConnection(dataSource).close();
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值