IoC容器设计实现及Spring源码分析

358 阅读14分钟

第一部分 Spring概述

1 Spring简介

Spring是分层的full-stack(全栈)轻量级开源框架,以IoC和AOP为内核,提供了展示层SpringMVC和业务层事务管理等众多的企业级应用技术。
Spring官方地址:https://spring.io/

2. Spring 发展历程

  • 1997-2006年 EJB1.0-EJB3.0发布
  • 2002年 Expert One-to-One J2EE Design and Development阐述了J2EE使⽤EJB开发设计的优点及解决⽅案
  • 2004年 Expert One-to-One J2EE Development without EJB阐述了J2EE开发不使⽤EJB的解决⽅式(Spring雏形)

3. Spring的优势

  • 解耦,简化开发 通过Spring提供的IoC容器,可以将对象间的依赖关系交由Spring进行控制,避免硬编码(new)所造成的过度程序耦合。

  • AOP编程的支持
    通过Spring的AOP功能,方便进行面向切面的编程,许多不容易用传统OOP实现的功能可以通过AOP轻松应付

  • 声明式事务的支持
    @Transactional 可以将我们从单调烦闷的事务管理代码中解脱,通过声明式方式灵活的进行事务的管理,提高开发效率和质量

  • 方便程序测试

  • 方便集成各种优秀框架 Spring可以降低各种框架的使用难度,提供了对各种优秀框架的直接支持

  • 降低JavaEE API的使用难度 Spring对javaEE API进行了薄薄的封装,使这些API的使用难度大为降低

  • 源码是经典的Java学习范例 Spring的源代码设计精妙,结构清晰,体现了大事对Java设计模式灵活运用以及对Java技术的高深造诣。

4. Spring的核心结构

image.png

第二部分 核心思想

1. IoC

1.1 什么事IoC?

IoC Inversion of Control(控制反转/反转控制),他是一个技术思想,不是一个技术实现

  • 传统开发方式:类A依赖于类B,会在类A中new一个B的对象
  • IoC思想下开发方式:我们不用自己去new对象,而是由IoC容器(Spring框架)去帮助我们实例化对象并且管理它,我们需要使用哪个对象,去向IoC容器要即可 为什么叫控制反转
  • 控制:指的是对象创建(实例化、管理)的权利
  • 反转:控制权交给外部环境(Spring框架、IoC容器)

image.png

1.2 IoC解决的问题:对象之间的耦合问题

image.png

1.3 IoC和DI的区别

  • IoC容器是站在对象的角度,对象实例化机器管理的权利交给(反转)给了容器
  • DI是站在容器的角度,会把对象依赖的其他对象注入

2. AOP

2.1 什么是AOP

AOP:面向切面编程/面向方面编程,AOP是OOP的延续。

image.png 横切逻辑代码存在什么问题:

  • 横切代码重复
  • 横切逻辑代码和业务代码混杂在一起,代码臃肿,维护不方便 AOP提出了横向抽取鸡贼,将横切逻辑代码和业务逻辑代码分离

横切代码分离.png

2.2 AOP在解决什么问题

在不改变原有业务逻辑情况下,增强横切逻辑代码,根本上解耦合,避免横切逻辑代码重复

2.3 为什么叫做面向切面编程

:指的是横切逻辑,原有业务逻辑代码不能动,只能操作横切逻辑代码,所以面向横切逻辑 :横切逻辑代码往往要影响的是很多个方法,每个方法都如同一个点,多个点构成面。

第三部分 手写IoC和AOP

基础回顾

- A 简单工厂模式

image.png 样例代码

public interface INoodles {
    
    /**
     * 描述面条
     */
    void desc();
}

public class LaMian implements INoodles {
    @Override
    public void desc() {
        System.out.println("一碗来自兰州的拉面");
    }
}

public class PaoMian implements INoodles {
    @Override
    public void desc() {
        System.out.println("一碗来自超市的泡面");
    }
}

public class DanDanMian implements INoodles {
    @Override
    public void desc() {
        System.out.println("一碗来自四川的担担面");
    }
}

public class SimpleNoodlesFactory {
    
    public static final int LM = 1;
    public static final int PM = 2;
    public static final int DDM = 1;
    
    public static INoodles createNoodles(int noodleType) {
        switch (noodleType) {
            case 1:
                return new LaMian();
            case 2:
                return new PaoMian();
            case 3:
                return new DanDanMian();
            default:
                return null;
        }
    }
    
}

- B 单例模式

  • 恶汉模式
public class HungrySingleton {
    
    // 构造方法私有化(单例模式必须的一步)
    private HungrySingleton() {}
    // 将自身实例化对象设置为一个属性,并用static、final修饰
    private static final HungrySingleton instance = new HungrySingleton();
    // 静态方法返回该实例
    public static HungrySingleton getInstance() {
        return instance;
    }
}
  • 懒汉模式
public class LazySingleton {
    // 构造方法私有化
    private LazySingleton() {}
    // 将自身实例化对象设置为一个属性,并用static修饰
    private static LazySingleton instance;
    // 静态方法返回该实例,加synchronized关键字实现同步(不然两个线程都进来会有安全问题)
    public static synchronized LazySingleton getInstance() {
        if(instance == null) {
            instance = new LazySingleton();
        }
        return instance;
    }
}

3.1 银行转账案例(关键代码)

  • servlet
@WebServlet(name="transferServlet",urlPatterns = "/transferServlet")
public class TransferServlet extends HttpServlet {
    // 1. 实例化service层对象
    private TransferService transferService = new TransferServiceImpl();
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
    doPost(req,resp);
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse
resp) throws ServletException, IOException {
    // 设置请求体的字符编码
    req.setCharacterEncoding("UTF-8");
    String fromCardNo = req.getParameter("fromCardNo");
    String toCardNo = req.getParameter("toCardNo");
    String moneyStr = req.getParameter("money");
    int money = Integer.parseInt(moneyStr);
    Result result = new Result();
    try {
        // 2. 调⽤service层⽅法
        transferService.transfer(fromCardNo,toCardNo,money);
        result.setStatus("200");
    } catch (Exception e) {
        e.printStackTrace();
        result.setStatus("201");
        result.setMessage(e.toString());
        }
    // 响应
    resp.setContentType("application/json;charset=utf-8");
    resp.getWriter().print(JsonUtils.object2Json(result));
}
  • TransferService接口及实现类
public class TransferServiceImpl implements TransferService {
    private AccountDao accountDao = new JdbcAccountDaoImpl();
    @Override
    public void transfer(String fromCardNo, String toCardNo, int money)throws Exception {
        Account from = accountDao.queryAccountByCardNo(fromCardNo);
        Account to = accountDao.queryAccountByCardNo(toCardNo);
        from.setMoney(from.getMoney()-money);
        to.setMoney(to.getMoney()+money);
        accountDao.updateAccountByCardNo(from);
        accountDao.updateAccountByCardNo(to);
    }
  • AccountDao层接口及基于jdbc实现类
public class JdbcAccountDaoImpl implements AccountDao {
    @Override
    public Account queryAccountByCardNo(String cardNo) throws Exception {
    //从连接池获取连接
        Connection con = DruidUtils.getInstance().getConnection();
        String sql = "select * from account where cardNo=?";
        PreparedStatement preparedStatement = con.prepareStatement(sql);
        preparedStatement.setString(1,cardNo);
        ResultSet resultSet = preparedStatement.executeQuery();
        Account account = new Account();
        while(resultSet.next()) {
            account.setCardNo(resultSet.getString("cardNo"));
            account.setName(resultSet.getString("name"));
            account.setMoney(resultSet.getInt("money"));
        }
        resultSet.close();
        preparedStatement.close();
        con.close();
        return account;
    }
    @Override
    public int updateAccountByCardNo(Account account) throws Exception {
    //从连接池获取连接
        Connection con = DruidUtils.getInstance().getConnection();
        String sql = "update account set money=? where cardNo=?";
        PreparedStatement preparedStatement = con.prepareStatement(sql);
        preparedStatement.setInt(1,account.getMoney());
        preparedStatement.setString(2,account.getCardNo());
        int i = preparedStatement.executeUpdate();
        preparedStatement.close();
        con.close();
        return i;
    }
}

3.2 银行转账案例问题分析

WX20210408-095937@2x.png

3.3 问题解决思路

  • 思考
  1. new关键字将service层的实现类TransferServiceImpl和Dao层的具体实现类JdbcAccountDapImpl耦合在一起,除了new以外还可以使用反射 Class.forName("全限定类名"); 可以把全限定类名配置早xml中
  2. 使用工厂来通过反射技术生产对象,工厂模式是解耦合非常好的一种方式
  3. service层没有添加事务控制,手动控制JDBC的Connection事务,但注意将Connection和当前线程绑定(即保证一个线程只有一个Connection,这样操作才针对的是同一个Connection,进而控制的是同一个事务)

image.png

image.png

3.4 代码优化

image.png

第四部分 Spring IoC应用

4.1 Spring IoC基础

4.1.1 BeanFactory与ApplicationContext区别

BeanFactory是Spring框架中的IoC容器的顶层接口,他只是用来定义一些基本功能,定义一些基础规范,而ApplicationContext是它的一个子接口,所以ApplicationContext是具备BeanFactory提供的全部功能。通常,我们称BeanFactory为SpringIoc的基础容器,ApplicationContext是容器的高级接口,比BeanFactory要拥有更多的功能。

image.png 启动IoC容器的方式

  • Java环境下启动IoC容器
    1. ClassPathXmlApplicationContext: 从类的根路径下架子配置文件
    2. FileSystemXmlApplicationContext: 从磁盘路径上加载配置文件
    3. AnnotationConfigApplicationContext: 纯注解模式下启动Spring容器
public class IoCTest {
    @Test
    public void testIoC(){
        // 通过读取classpath下的xml文件来启动容器(xml模式SE应用下推荐)
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext("classpath:applicationContext.xml");
        // 通过哟磁盘路径加载配置文件
        ApplicationContext applicationContext1 = new FileSystemXmlApplicationContext("文件绝对路径");
        AccountDao accountDao = (AccountDao) applicationContext.getBean("accountDao");
        //System.out.println(accountDao);
        Object connectionUtils = applicationContext.getBean("connectionUtils");
        System.out.println(connectionUtils);
    }
}
  • web环境下启动IoC容器
    1. 从xml启动容器
<!DOCTYPE web-app PUBLIC
 "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
 "http://java.sun.com/dtd/web-app_2_3.dtd" >

<web-app>
  <display-name>Archetype Created Web Application</display-name>
  <!--配置SpringIoC容器的配置文件-->
  <context-param>
    <param-name>contextConfigLocation</param-name>
    <param-value>classpath:applicationContext.xml</param-value>
  </context-param>
  <!-- 使用监听器启动启动IoC容器-->
  <listener>
    <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
  </listener>
</web-app>

4.1.2 纯xml模式

  • xml文件头 可配置多个Spring组件标签配置文件,使用前缀区分
<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"
       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/beans/spring-aop.xsd
       ">
  • 实例化Bean的三种方式
  1. 使用无参构造函数
    在默认情况下,会通过反射调用无参构造函数来创建对象。如果类中没有无参构造函数,将创建失败 applicationContext.xml
<bean id="connectionUtils" class="cn.percent.utils.ConnectionUtils"/>
  1. 使用静态方法创建 在开发中,我们使用的对象有些时候并不是直接通过构造函数创建出来的,它可能在创建过程中会做很多额外的操作。此时会提供一个创建对象的方法,恰好这个方法是static修饰的方法,就是这种情况。 例如: 我们在做JDBC操作是,会用到java.sql.Connection接口的实现类,如果是mysql数据库,那么用的是JDBC4Connection,但是我们不会去写JDBC4Connection connection = new JDBC4Connection(),因为我们要注册驱动,还要提供URL和凭证信息,用DriverManager.getConnection方法来获取连接。

applicationContext.xml

<bean id="connectionUtils" class="cn.percent.factory.CreateBeanFactory" factory-method="getInstanceStatic"/>
  1. 使用实例化方法创建 此种方法和静态方法创建类似,区别是用于获取对象的方法不再是static修饰了,而是类中的一个普通方法。这种方法比静态方法创建的使用几率要高一些。
<bean id="createBeanFactory" class="cn.percent.factory.CreateBeanFactory"></bean>
<bean id="connectionUtils" factory-bean="createBeanFactory" factory-method="getInstance"></bean>
  • Bean的生命周期
  1. 作用范围的改变 Spring框架管理Bean对象的创建时,Bean对象默认都是单例的,但他是支持配置的方式改变作用范围。

image.png

<!--配置service对象-->
<bean id="transferService" class="com.lagou.service.impl.TransferServiceImpl" scope="singleton"/>

单例模式: singleton
对象出生:当创建容器时,对象就被创建了。
对象活着:只要容器在,对象就被销毁了。
对象死亡:当销毁容器时,对象就被销毁了。
单例模式的bean对象生命周期与容器相同
多例模式: prototype
对象出生:当使用对象时,创建新的对象实例。
对象活着:只要对象在使用中,就一直活着。
对象死亡:当对象长时间不用时,被Java的垃圾回收器回收了。
多例模式的bean对象,spring框架只负责创建,不负责销毁

  1. Bean标签属性
属性解释
id⽤于给bean提供⼀个唯⼀标识。在⼀个标签内部,标识必须唯⼀。
class⽤于指定创建Bean对象的全限定类名。
name⽤于给bean提供⼀个或多个名称。多个名称⽤空格分隔。
factory-bean⽤于指定创建当前bean对象的⼯⼚bean的唯⼀标识。当指定了此属性之后,class属性失效。
factory-method⽤于指定创建当前bean对象的⼯⼚⽅法,如配合factory-bean属性使⽤,则class属性失效。如配合class属性使⽤,则⽅法必须是static的。
scope⽤于指定bean对象的作⽤范围。通常情况下就是singleton。当要⽤到多例模式时,可以配置为prototype。
init-method⽤于指定bean对象的初始化⽅法,此⽅法会在bean对象装配后调⽤。必须是⼀个⽆参⽅法。
destory-method⽤于指定bean对象的销毁⽅法,此⽅法会在bean对象销毁前执⾏。它只能为scope是singleton时起作⽤
  1. DI依赖注入的xml配置
  • 依赖注入分类
    (1). 构造函数注入: 利用带参数构造函数实现对类成员的数据赋值。
   <bean id="accountDao" class="cn.percent.dao.impl.JdbcAccountDaoImpl">
        <!--name: 按照参数名注入,index按照参数索引位置注入-->
        <constructor-arg name="connectionUtils" ref="connectionUtils"></constructor-arg>
        <constructor-arg name="name" value="zhangsan"></constructor-arg>
        <constructor-arg name="sex" value="1"></constructor-arg>
        <constructor-arg name="money" value="100.6"></constructor-arg>
   </bean>

(2). set方法注入

  • 基本类型和String <property name="name" value="zhangsan"></property>
  • 其他Bean类型 <property name="ConnectionUtils" ref="connectionUtils"></property>
  • 复杂类型(集合类型) 注入的数据类型是Array,List,Set,Map,Properties中的一种类型
<property name="myMap">
    <map>
       <entry key="key1" value="value1"></entry>
       <entry key="key2" value="value2"></entry>
       <entry key="key3" value="value3"></entry>
    </map>
</property>

4.1.3 xml与注解想结合的模式

xml形式对应的注解形式
标签@Component("accountDao"),注解加在类上bean的id属性内容直接配置在注解后⾯如果不配置,默认定义个这个bean的id为类的类名⾸字⺟⼩写;另外,针对分层代码开发提供了@Componenet的三种别名@Controller、@Service、@Repository分别⽤于控制层类、服务层类、dao层类的bean定义,这四个注解的⽤法完全⼀样,只是为了更清晰的区分⽽已
标签的scope属性@Scope("prototype"),默认单例,注解加在类上
标签的initmethod属性@PostConstruct,注解加在⽅法上,该⽅法就是初始化后调⽤的⽅法
标签的destorymethod属性@PreDestory,注解加在⽅法上,该⽅法就是销毁前调⽤的⽅法
  • DI依赖注入的注解方式
    @Autowired(推荐使⽤),@Autowired为Spring提供的注解,需要导⼊包org.springframework.beans.factory.annotation.Autowired。@Autowired采取的策略为按照类型注⼊。当一个类型有多个beab值得时候,会造成无法选择具体注入哪一个的情况。这时需配合@Qualifier使用
public class TransferServiceImpl implements TransferService {

    //Autowired 按照类型注入,如果按照类型无法唯一锁定对象,可以结合Qualifier指定
    @Autowired
    @Qualifier("accountDao")
    private AccountDao accountDao;
}

4.1.4 纯注解方式

移除xml中的配置,从java配置类启动,对应注解:
@Configuration 注解,表名当前类是⼀个配置类
@ComponentScan 注解,替代 context:component-scan
@PropertySource,引⼊外部属性配置⽂件
@Import 引⼊其他配置类
@Value 对变量赋值,可以直接赋值,也可以使⽤ ${} 读取资源配置⽂件中的信息
@Bean 将⽅法返回对象加⼊ SpringIOC 容器

@Configuration
@ComponentScan({"cn.percent"})
@PropertySource({"classpath:jdbc.properties"})
public class SpringConfig {
    @Value("${jdbc.driver}")
    private String driverClassName;
    @Value(("${jdbc.url}"))
    private String url;
    @Value("${jdbc.username}")
    private String username;
    @Value(("${jdbc.password}"))
    private String password;

    @Bean("dataSource")
    public DataSource createDataSource(){
        DruidDataSource druidDataSource = new DruidDataSource();
        druidDataSource.setDriverClassName(driverClassName);
        druidDataSource.setUrl(url);
        druidDataSource.setUsername(username);
        druidDataSource.setPassword(password);
        return druidDataSource;
    }
}

4.2 Spring IoC高级特性

4.2.1 lazy-init延迟加载

ApplicationContext容器的默认行为是启动服务器时将所有singleton bean提前进行实例化,提前实例化以为着作为初始化过程的一部分,ApplicationContext实例会创建配置所有的singleton bean。

<bean id="testBean" class="cn.percent.LazyBean" />
该bean默认的设置为:
<bean id="testBean" calss="cn.percent.LazyBean" lazy-init="false" />

设置 lazy-init 为 true 的 bean 将不会在 ApplicationContext 启动时提前被实例化,⽽是第⼀次向容器 通过 getBean 索取 bean 时实例化的。如果⼀个设置了⽴即加载的 bean1,引⽤了⼀个延迟加载的 bean2 ,那么 bean1 在容器启动时被实例化,⽽ bean2 由于被 bean1 引⽤,所以也被实例化,这种情况也符合延时加载的 bean 在第⼀次调⽤时才被实例化的规则。也可以在容器层次中通过在 元素上使⽤ "default-lazy-init" 属性来控制延时初始化。如下⾯配置:

<beans default-lazy-init="true">
<!-- no beans will be eagerly pre-instantiated... -->
</beans>

如果⼀个 bean 的 scope 属性为 scope="pototype" 时,即使设置了 lazy-init="false",容器启动时也不 会实例化bean,⽽是调⽤ getBean ⽅法实例化的。

4.2.2 FactoryBean和BeanFactory

BeanFactory接⼝是容器的顶级接⼝,定义了容器的⼀些基础⾏为,负责⽣产和管理Bean的⼀个⼯⼚, 具体使⽤它下⾯的⼦接⼝类型,⽐如ApplicationContext。
Spring中Bean有两种,⼀种是普通Bean,⼀种是⼯⼚Bean(FactoryBean),FactoryBean可以⽣成 某⼀个类型的Bean实例(返回给我们),也就是说我们可以借助于它⾃定义Bean的创建过程。

FactoryBean接口

// 可以让我们⾃定义Bean的创建过程(完成复杂Bean的定义)
public interface FactoryBean<T> {
    @Nullable
    // 返回FactoryBean创建的Bean实例,如果isSingleton返回true,则该实例会放到Spring容器的单例对象缓存池中Map
    T getObject() throws Exception;
    @Nullable
    // 返回FactoryBean创建的Bean类型
    Class<?> getObjectType();
    // 返回作⽤域是否单例
    default boolean isSingleton() {
        return true;
    }
}

Company Class

public class Company {
    private String name;
    private String address;
    private int scale;
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public String getAddress() {
        return address;
    }
    public void setAddress(String address) {
        this.address = address;
    }
    public int getScale() {
        return scale;
    }
    public void setScale(int scale) {
        this.scale = scale;
    }
    @Override
    public String toString() {
    return "Company{" +
            "name='" + name + '\'' +
            ", address='" + address + '\'' +
            ", scale=" + scale +
            '}';
    }
}

CompanyFactoryBean class

public class CompanyFactoryBean implements FactoryBean<Company> {
    private String companyInfo; // 公司名称,地址,规模
    public void setCompanyInfo(String companyInfo) {
        this.companyInfo = companyInfo;
    }
    @Override
    public Company getObject() throws Exception {
        // 模拟创建复杂对象Company
        Company company = new Company();
        String[] strings = companyInfo.split(",");
        company.setName(strings[0]);
        company.setAddress(strings[1]);
        company.setScale(Integer.parseInt(strings[2]));
        return company;
    }
    @Override
    public Class<?> getObjectType() {
        return Company.class;
    }
    @Override
    public boolean isSingleton() {
        return true;
    }
}

xml配置

<bean id="companyBean" class="com.lagou.edu.factory.CompanyFactoryBean">
<property name="companyInfo" value="percent,市府恒隆,500"/>
</bean>

测试代码

Object companyBean = applicationContext.getBean("companyBean");
System.out.println("bean:" + companyBean);
// 结果如下
bean:Company{name='拉勾', address='中关村', scale=500}

如果想要获取FactoryBean,需要在id之前加”&“ &companyBean

Object companyBean = applicationContext.getBean("&companyBean");
System.out.println("bean:" + companyBean);
// 结果如下
bean:com.lagou.edu.factory.CompanyFactoryBean@53f6fd09

第五部分 SpringIoC源码分析

Spring源码太复杂,好多函数还没搞清除逻辑,大神就是大神,日后在补充注释。

    git地址: https://gitee.com/FearlessYMF/spring-source-code-analysis.git

第六部分 Spring AOP应用

1. XML方式

  • xml配置
<?xml version="1.0" encoding="UTF-8" ?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
           https://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
           ">
    <!--开启注解扫描,base-pacjage指定扫描的包路径 -->
    <context:component-scan base-package="cn.percent"></context:component-scan>
    <!--引入第三方bean-->
    <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
        <property name="driverClassName" value="com.mysql.jdbc.Driver"></property>
        <property name="url" value="jdbc:mysql://172.16.3.219:3306/ymf"></property>
        <property name="username" value="crawl"></property>
        <property name="password" value="crawl123"></property>
    </bean>


    <!--aop相关的xml配置-->
    <!-- 使用config标签表明开始配置,在内部配置切面aspect-->
    <bean id="logUtils" class="cn.percent.utils.LogUtils"></bean>
    <aop:config>
        <aop:aspect id="logAspect" ref="logUtils">
            <!--切入点锁定我们感兴趣的方法 使用aspectj表达式,参数全限定类名-->
            <aop:pointcut id="pt1" expression="execution(public void cn.percent.service.impl.TransferServiceImpl.transfer(String ,String ,int))"/>
            <!--方位信息-->
            <!--aop:before 前置通知/-->
<!--            <aop:before method="beforeMethod" pointcut-ref="pt1"></aop:before>-->
            <!--aop:after 最终通知,无论如何都执行-->
            <!--aop:after-returnning. 正常执行通知-->
            <!--aop:after-throwing, 异常通知-->
            <!--环绕通知-->
            <aop:around method="arroundmethod" pointcut-ref="pt1"></aop:around>
        </aop:aspect>
    </aop:config>

</beans>
  • 执行类
package cn.percent.utils;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;

public class LogUtils {
    public void beforeMethod(JoinPoint joinPoint){
        Object[] args = joinPoint.getArgs();

        System.out.println("业务逻辑开始执行之前执行........");
    }

    public void afterMehtod(){
        System.out.println("业务逻辑结束是执行,无论异常与否都执行........");
    }

    public void exceptionMethod(){
        System.out.println("异常时执行........");
    }


    public void successMethod(){
        System.out.println("业务正常时执行........");
    }
    // 环绕通知
    public Object arroundmethod(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
        System.out.println("环绕通知中的beforeMethod");
        Object result = null;
        try{
            result = proceedingJoinPoint.proceed(proceedingJoinPoint.getArgs());
        } catch (Exception e) {
            System.out.println("环绕通知中的exceptionMethod...");
        } finally {
            System.out.println("环绕通知中的afterMethod...");
        }
        return result;
    }
}
  • 测试函数
import cn.percent.service.TransferService;
import org.junit.Test;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import java.sql.SQLException;

public class IoCTest {
    @Test
    public void testXmlAop() throws SQLException {
        ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
        TransferService transferService = applicationContext.getBean(TransferService.class);
        transferService.transfer("6029621011001","6029621011000",2000);
    }
}

2. 注解方式

日后补充

3. 源码分析

日后补充