Java 手写 Spring框架 IOC 和 AOP以及 高级应用 和 实际源码剖析,从思想到实现一步步演变,充分体会Spring的思想魅力,第三章:SpringAOP高级应用面试知识点,源码

本文档深入探讨了手写Spring框架的实现,包括IoC的高级应用、Spring AOP的多种配置方式(纯XML、半注解半XML、纯注解)以及事务管理的声明式实现。此外,还剖析了AOP的源码原理和实战示例,涉及Spring AOP的代理选择、通知类型和事务隔离策略。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

源码(码云):https://gitee.com/yin_zhipeng/implement-_spring_of_myself.git

一、手写Spring

篇幅限制,我将其放在这篇文章中:https://blog.csdn.net/grd_java/article/details/122625811

二、Spring IoC高级应用和源码

篇幅限制,我将其放在这篇文章中:https://blog.csdn.net/grd_java/article/details/122647693

三、Spring AOP 高级应用

引入依赖,AOP依赖aspectj包

在这里插入图片描述

        <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjweaver</artifactId>
            <version>1.8.14</version>
        </dependency>
AOP的本质:在不改变原有业务逻辑的基础上,增强横切逻辑(权限校验、日志、事务、性能监控等)
  1. 不使用AOP等横切逻辑,红色框框,框住的都是重复代码
    在这里插入图片描述
  2. 而使用AOP或动态代理等实现横切逻辑,就会和下图一样,事务代码只有一份,没有重复代码,并且最重要的是,横切逻辑与业务代码完全分离,没有耦合
    在这里插入图片描述
AOP常用术语如下:这些术语的规定主要为了完成两件事,规定切入横切逻辑的时机,然后切入对应的横切逻辑。连接点和切入点是规定切入时机的概念,通知是具体实现,用来指定切入到哪里,将横切逻辑切入。切面,是对应的横切逻辑,是整个横切的一个描述,锁定在哪个地方插入什么样的代码
术语描述
Joinpoint(连接点)可以横切的点,一般指的是方法,通过动态代理,可以根据这些点进行切入,Spring框架AOP思想中,只支持方法类型的连接点。注意只是标识可以切入的点(方法执行前,结束时,正常运行完毕,方法异常等这些点),不是真的切了
Pointcut(切入点)连接点是标识(破绽),而切入点是实实在在切下来(击中破绽),也就是我们横切逻辑最终要切入的连接点,就是切入点
Advice(通知/增强)规定切入/通知/增强的时机,就是切到切入点的那个具体位置,前面,还是后面,知道了连接点和切入点,也就知道了哪些方法可以切,接下来就是切入时机,是在方法执行前切,还是抛异常时切,还是执行完切。Spring AOP分类有:前置通知、后置通知、异常通知、返回通知、环绕通知
Target(目标对象)被代理对象,就是我们要代理的对象
Proxy(代理)代理对象,代理人,给别人做代理的,指一个类被AOP织入增强后,产生的代理类
Weaving(织入)代理的过程称为织入,就是把增强应用到目标对象来创建新的代理对象的过程,Spring采用动态代理织入,AspectJ采用编译期织入和类装载期织入
Aspect(切面)切面代码,切入点(Pointcut)+通知(Advice)+横切逻辑,增强代码关注的内容,封装到一个类中,这个类就是切面类,例如处理事务的,开启、提交、回滚事务这些封装到一个类,这个类就是负责事务的切面。我们不会在一个切面中定义两种不同的增强,要遵守一定程度的单一职责原则(一个切面负责一件事)
前面的术语不知道大家懂了没,有点难以理解,所以我这里进行一个总结
  1. 首先我们要有一个切面类(我的的切面,横切逻辑)
  2. 然后我们要有一些可能需要横切的类,业务逻辑代码(方法),这些方法就是连接点,可以被切的
  3. 然后我们从连接点中选择一些点,作为切入点,这些切入点就是我们要使用横切逻辑增强功能的方法
  4. 有了切入点后,我们需要通知横切逻辑中的方法,在指定切入点哪个位置执行,前面,还是后面,还是抛异常后等等
Spring中AOP的代理选择(动态代理)
  1. 默认,Spring会根据被代理对象是否实现接口来选择使用JDK还是CGLIB。当被代理对象没有实现任何接口,Spring会选择CGLIB。当实现接口时,会选择JDK官方的代理技术
  2. 我们可以通过配置,让Spring无论被代理对象有没有接口,都使用CGLIB。但这根本没必要
记住:环绕通知永远不要和其它通知一起使用,要么使用其它4种通知配合,要么就只使用环绕通知

1. 纯xml模式

xml中我们需要引入命名空间

在这里插入图片描述

1. 我们的连接点
  1. 依然用我们前面的类,这个transfer方法是需要被增强的,但此时我们并没有指定要对它增强,所以它仅仅只是一个连接点
    在这里插入图片描述
2. 编写横切逻辑,xml中指定切面
  1. 编写一个简单的切面类
    在这里插入图片描述
  2. 配置切面
    在这里插入图片描述
3. 配置AOP指定切入点,注意xml中配置切入点,一定要指定这个方法的全限定路径(权限修饰符+返回值+全限定类名+方法名(参数列表的类型)),参数列表类型,除了基本数据类型int,long这些,也需要指定全限定类名
例如:public void com.yzpnb.advanced_application.service.impl.TransferServiceImpl.transfer(java.lang.String,java.lang.String,int)
或者使用通配符:
不限定修饰符和返回值:* com.yzpnb.advanced_application.service.impl.TransferServiceImpl.transfer(java.lang.String,java.lang.String,int)
不限定修饰符和返回值,并且不限定包(. .表示多层路径):* * . .TransferServiceImpl.transfer(java.lang.String,java.lang.String,int)
不限定修饰符和返回值,并且不限定包,也不限定类(. .表示多层路径):* * . .*.transfer(java.lang.String,java.lang.String,int)
甚至可以不限定方法名:* * . .*. *(java.lang.String,java.lang.String,int)
不限定参数,但是必须有参数:* * . .*. *(**)
不限定参数,可以有参数,也可以没有参数:* * . .*. *(. .)
常用形式,在这里插入图片描述

在这里插入图片描述

4. 通知,横切逻辑方法,在切入点哪个位置执行,需要指定切入点

在这里插入图片描述

    <!--  aop配置  -->
    <!--配置切面类,我们的横切逻辑-->
    <bean id="logUtils" class="spring.utils.LogUtils"></bean>
    <!--    配置aop    -->
    <aop:config>
        <!--配置切面 = 切入点+通知+横切逻辑-->
        <aop:aspect id="logAspect" ref="logUtils"><!--这里ref指定了横切逻辑,切面类。id为logAspect-->
            <!--切入点-->
            <aop:pointcut id="ptl" expression="execution(public void com.yzpnb.advanced_application.service.impl.TransferServiceImpl.transfer(java.lang.String,java.lang.String,int))"/>
            <!--通知 pointcut-ref 指定切入点
                    before:前置通知,业务逻辑执行之前
                    after: 后置通知,业务逻辑执行之后
                    after-returning:返回通知,业务逻辑正常运行完成之后
                    after-throwing:异常通知,业务逻辑抛异常之后
                    around:环绕通知,这个就看你直接怎么写了
                    -->
            <aop:before method="beforeMethod" pointcut-ref="ptl"></aop:before>
            <aop:after-returning method="successMethod" pointcut-ref="ptl"></aop:after-returning>
            <aop:after-throwing method="exceptionMethod" pointcut-ref="ptl"></aop:after-throwing>
            <aop:after method="afterMethod" pointcut-ref="ptl"></aop:after>
        </aop:aspect>
    </aop:config>
5. 进行测试

在这里插入图片描述

5. 环绕通知: 很像动态代理的编写方式,可以控制业务逻辑是否执行
  1. 添加环绕通知方法,记住,如果你想在环绕通知中处理异常,一定要人为再抛出去,否则外面调用接收不到异常,会发生即使异常了,但是依然通知了正常运行,执行了正常运行完毕的横切逻辑
    在这里插入图片描述
    //环绕通知
    public void aroundMethod(ProceedingJoinPoint proceedingJoinPoint){
        System.out.println("环绕通知----可以前置通知..........");
        Object result = null;
        try {
            //执行业务逻辑
            result = proceedingJoinPoint.proceed();//相当于动态代理的method.invoke()
        } catch (Throwable throwable) {
            System.out.println("环绕通知----可以异常通知............");
            throwable.printStackTrace();
        }finally {
            System.out.println("环绕通知----可以后置通知...........");
        }
        System.out.println("环绕通知----可以返回通知..........");
    }
  1. xml配置然后测试
    在这里插入图片描述
6. 正常执行后,返回通知,横切逻辑接收返回值,通过参数
  1. 方法
    在这里插入图片描述
  2. xml
    在这里插入图片描述

2. 半注解半xml模式

通过注解将切面类扫描到IOC,开启注解驱动

在这里插入图片描述

    <context:component-scan base-package="spring"></context:component-scan>
    <aop:aspectj-autoproxy></aop:aspectj-autoproxy>
我们根据xml配置,一行行替换成注解
  1. 配置切面类,让IOC拿到,@Component
    在这里插入图片描述
    在这里插入图片描述
  2. 指定它为一个切面@Aspect
    在这里插入图片描述
    在这里插入图片描述
  3. 切入点,提供空方法,使用注解指定横切表达式
    在这里插入图片描述
    在这里插入图片描述
  4. 通知,全部使用注解
    在这里插入图片描述
  5. 测试
    在这里插入图片描述

3. 纯注解模式

1. 配置类添加注解@EnableAspectJAutoProxy

在这里插入图片描述

2. 测试

在这里插入图片描述

4. 声明式事务

先了解一下编程式事务
  1. try-catch块就是编程式事务
  2. 业务代码和事务代码耦合到一起,就是编程式事务
声明式事务
  1. 通过AOP、动态代理等技术,实现控制事务
  2. 业务代码和事务代码解耦合
事务四大特性
  1. 原子性(Atomicity):指事务是不可分割的工作单位,事务操作要么不发生,要么都发生,比如事务执行一半出错了,那么前面已经完成的任务,都会被回滚,修改不生效。
  2. 一致性(Consistency):事务必须使数据库从一个一致性状态变换到另一个一致性状态。假如A和B转账,A有1000,B有1000,转账后,无论转账成功与否,A+B==2000
  3. 隔离性(Isolation):多个用户并发访问数据库时,数据库为每个用户开启的事务
  1. 每个事务不能被其他事务的操作数据所干扰,多个并发事务之间要相互隔离
  2. 例如事务1给员工发工资2000,但是事务1尚未提交,此时员工发起事务2查询工资,此时读到是原原本本的数据,不受事务1影响。只有事务1真正提交,才能查询到
  1. 持久性(Durability):指一个事务一旦被提交,那么对数据库中数据的改变是永久性的,接下来即使数据库故障也不会有任何影响
事务隔离级别,解决事务并发的问题
  1. 不考虑事务隔离级别,会出现的状况
  1. 脏读:一个线程中事务读到另一个线程中未提交数据
  2. 不可重复读:一个线程中事务读到另一个线程已提交的update数据,(前后内容不一样)
  3. 幻读(虚读):一个线程中事务读到另一个线程中已提交insert数据(前后数据条数不一样)
  1. 四种事务隔离级别
  1. Serializable(串行化):最高级别,速度最慢,避免脏读、不可重复读、幻读。
  2. Repeatable read(可重复读):第二级,速度还行,避免脏读、不可重复读。幻读依然可能发生(对update进行加锁,其它insert,delete什么的不管)
  3. Read committed(可读已提交):第三级,速度挺好,避免涨读、不可重复读和幻读一定会发生。
  4. Read uncommitted(可读未提交):最低级别,速度最快,以上情况均无法保证。
事务隔离级别脏读不可重复读幻读
READ UNCOMMITTED
READ COMMITTED×
REPEATABLE READ(MySQL默认)××
SERIALIZABLE×××
事务传播行为
  1. 事务往往在service层控制,如果出现service层A方法调用B方法的情况,A和B分别被添加事务控制,那么A调用B的时候,到底按照谁的事务级别来执行呢?
  2. A和B两个事务需要协商,称为事务传播行为,A调用B时,我们站在B的角度来观察和定义事务传播行为
传播行为(🍅:无需记忆/🏆:需要记忆)描述
🏆PROPAGATION_REQUIRED如果当前没有事务,就新建一个,如果已经存在一个事务中,加入到这个事务是常见选择
🏆PROPAGATION_SUPPORTS支持当前事务,如果当前没有事务,就以非事务方式执行
🍅PROPAGATION_MANDATORY使用当前事务,如果当前没有事务,就抛出异常
🍅PROPAGATION_REQUIRES_NEW新建事务,如果当前已存在事务,就把当前事务挂起
🍅PROPAGATION_NOT_SUPPORTED以非事务方式执行操作,如果当前存在事务,就把当前事务挂起
🍅PROPAGATION_NEVER以非事务方式执行,如果当前存在事务,则抛出异常
🍅PROPAGATION_NESTED如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则执行与PROPAGATION_REQUIRED类似的操作
Spring中事务API
  1. 不同的框架可能采取不同事务提交方式,例如mybatis:sqlSession.commit();再例如hibernate:session.commit()
  2. Spring如何统一呢?就是定义接口
    在这里插入图片描述
  3. 作用
  1. Spring事务管理器核心接口,Spring本身不支持事务实现,只负责提供标准,应用底层支持什么事务,需要提供具体实现类,策略设计模式的应用。
  2. Spring也内置了一些具体策略(实现类),DataSourceTransactionManager(SpringJdbcTemplate、Mybatis),HibernateTransactionManager(Hibernate)等

4.1 纯xml

4.1.1 环境搭建和配置
我们使用SpringJdbc来做声明式事务(spring使用mybatis需要整合,麻烦),需要引入依赖

在这里插入图片描述

        <!--spring声明式事务-->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-jdbc</artifactId>
            <version>5.1.12.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-tx</artifactId>
            <version>5.1.12.RELEASE</version>
        </dependency>
使用SpringJdbc改造工程
  1. dao层添加两个方法,用springJdbc操作数据库
    在这里插入图片描述
    @Autowired
    private JdbcTemplate jdbcTemplate;

    //根据用户id查询用户信息,使用SpringJdbc
    @Override
    public Account springJdbcQueryAccountById(String id) throws Exception {
        String sql = "select * from account where id = ?";
        //queryForObject(sql语句,如何封装结果集,填充上面?的参数)
        return jdbcTemplate.queryForObject(sql, new RowMapper<Account>() {
            @Override
            public Account mapRow(ResultSet resultSet, int i) throws SQLException {
                Account account = new Account();
                account.setId(resultSet.getString("id"));
                account.setUsername(resultSet.getString("username"));
                account.setMoney(resultSet.getInt("money"));
                return account;
            }
        },id);
    }
    @Override
    public int springJdbcQueryUpdateAccountById(Account account) throws Exception {
        String sql = "update account set money = ? where id = ?";
        return jdbcTemplate.update(sql,account.getMoney(),account.getId());
    }
  1. 创建新的xml配置declarativeTransaction.xml
    在这里插入图片描述
<?xml version="1.0" encoding="UTF-8" ?>
<!--beans 根标签,里面有若干个bean子标签-->
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:aop="http://www.springframework.org/schema/aop"
       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
        https://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/aop
        https://www.springframework.org/schema/aop/spring-aop.xsd
        http://www.springframework.org/schema/context
        https://www.springframework.org/schema/context/spring-context.xsd">

    <context:component-scan base-package="com.yzpnb"></context:component-scan>
    <!--引入外部资源文件-->
    <context:property-placeholder location="classpath:jdbc.properties"></context:property-placeholder>
    <!--第三方bean配置到xml中-->
    <bean id="datasource" class="com.alibaba.druid.pool.DruidDataSource">
        <property name="driverClassName" value="${jdbc.driver}"></property>
        <property name="url" value="${jdbc.url}"></property>
        <property name="username" value="${jdbc.username}"></property>
        <property name="password" value="${jdbc.password}"></property>
    </bean>
    <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
        <constructor-arg name="dataSource" ref="datasource"></constructor-arg>
    </bean>
</beans>
service层调用dao层新方法

在这里插入图片描述

测试类DeclarativeTransactionTest

在这里插入图片描述

4.1.2 声明式事务
配置声明式事务就是配置aop,但是spring帮我们做了封装,所以通知什么的就不用配置了,全部封装到了一个标签中<aop:advisor >,用来专门配置事务管理器切面,事务管理器通过tx命名空间配置
  1. 配置切面类
  2. 配置增强(事务切面),也就是使用tx命名空间,把事务管理器配置了
  3. 配置aop,只需要配置切入点和指定事务管理器即可,通知什么的都封装好了
xml声明式事务,需要tx命名空间

在这里插入图片描述

<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xmlns:tx="http://www.springframework.org/schema/tx"
       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
        https://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/aop
        https://www.springframework.org/schema/aop/spring-aop.xsd
        http://www.springframework.org/schema/context
        https://www.springframework.org/schema/context/spring-context.xsd
        http://www.springframework.org/schema/tx
        https://www.springframework.org/schema/tx/spring-tx.xsd">
2. 配置声明式事务

在这里插入图片描述

    <!--aop声明式事务 就是配置aop,只不过切面类使用spring提供的,先有切面类-->
    <!--切面类,DataSourceTransactionManager,Spring提供的处理声明式事务的切面类-->
    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <constructor-arg name="dataSource" ref="datasource"></constructor-arg>
    </bean>
    <!--配置增强(事务切面)-->
    <tx:advice id="txAdvice" transaction-manager="transactionManager">
        <!--定制事务细节,传播行为、隔离级别等-->
        <tx:attributes>
            <!--如果拦截到某一个方法,要对其进行怎样事务配置-->
            <!--name="*" 对那些方法进行拦截,*表示所有,一般用于配置占比率较高的增删改操作,query*表示query开头的select*表示select开头的方法,一般指查询操作
              - read-only="false" 是否只读
              - propagation="REQUIRED" 事务传播行为,REQUIRED表示,如果当前没有事务,就新建一个,如果已经存在一个事务中,加入到这个事务是常见选择
              - isolation="DEFAULT" 隔离级别,DEFAULT表示默认,使用数据库默认隔离级别
              - rollback-for="" 当发生什么异常时回滚事务 ,一般不配置,让它使用默认配置
              - no-rollback-for="" 当发生什么异常时不回滚事务 ,一般不配置,让它使用默认配置
              - timeout="-1" 超时时间,请求超时,连接超时等,-1表示不限制
            -->
            <tx:method name="*" read-only="false" propagation="REQUIRED" isolation="DEFAULT" timeout="-1"/>
            <!--配置针对查询的细节,query*表示拦截query开头的方法,优先级更高,
                如果匹配,会覆盖上面name="*"的配置
              - SUPPORTS,事务传播行为表示,支持当前事务,如果当前没有事务,就以非事务方式执行-->
            <tx:method name="query*" read-only="true" propagation="SUPPORTS" isolation="DEFAULT" timeout="-1"/>
        </tx:attributes>
    </tx:advice>
    <aop:config>
        <!--切入点-->
        <aop:pointcut id="declarativeTransactionPit" expression="execution(* com.yzpnb.advanced_application.service.impl.TransferServiceImpl.declarativeTransactionTransfer(..))"/>
        <!--增强= 横切逻辑+切入点-->
        <aop:advisor advice-ref="txAdvice" pointcut-ref="declarativeTransactionPit"></aop:advisor>
    </aop:config>
测试

操作前数据库
在这里插入图片描述
操作后
在这里插入图片描述

4.2 半注解半xml

第三方bean配置到xml中,然后开启aop和tx注解驱动,其它aop和tx配置不需要
  1. 注释配置,开启tx注解驱动,指定事务管理器
    在这里插入图片描述
    <!--aop声明式事务 就是配置aop,只不过切面类使用spring提供的,先有切面类-->
    <!--切面类,DataSourceTransactionManager,Spring提供的处理声明式事务的切面类-->
    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <constructor-arg name="dataSource" ref="datasource"></constructor-arg>
    </bean>
    <!--注解驱动-->
    <tx:annotation-driven transaction-manager="transactionManager"></tx:annotation-driven>
通过@Transactional注解标识需要事务管理的类
标识到接口上,会对继承接口的所有实现类生效
标识到类上,会对所有方法生效
标识到方法上,只对当前方法生效
  1. 此注解拥有默认值,我们也可以单独拿出来配置事务隔离级别等等,参考上面xml配置即可
    在这里插入图片描述
  2. 我们将其加到方法上
    在这里插入图片描述
测试

在这里插入图片描述

4.3 纯注解

纯注解和半注解的区别在于,一个是在xml中开启注解配置,一个是通过@EnableTransactionManagement注解直接开启
  1. 配置类添加@EnableTransactionManagement注解,然后配置我们需要的第三方bean,JdbcTemplatehe 和DataSourceTransactionManager
    在这里插入图片描述
  2. 测试
    在这里插入图片描述

四、Spring AOP源码

由于篇幅原因,我将其放在这篇文章https://blog.csdn.net/grd_java/article/details/122878288
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

ydenergy_殷志鹏

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值