开发基础 | Spring 基本使用总结 && 手动实现 Spring

admin 2024年7月15日15:05:07评论9 views字数 77130阅读257分6秒阅读模式

Spring 基本使用总结 && 手动实现 Spring

前两天在准备期末周, 所以有一段时间没有更新文章, 本篇文章简洁的总结了Spring使用方式, 无过多废话.

Spring 环境部署方式

官方源码下载: https://github.com/spring-projects/spring-frameworkMaven 仓库:https://mvnrepository.com/artifact/org.springframework/spring-core/5.3.8 # spring 框架所依赖的https://mvnrepository.com/artifact/org.springframework/spring-beans/5.3.8https://mvnrepository.com/artifact/org.springframework/spring-context/5.3.8https://mvnrepository.com/artifact/org.springframework/spring-expression/5.3.8https://mvnrepository.com/artifact/commons-logging/commons-logging/1.1.3 # spring 写日志需要的 jar

可以通过官方源码下载的方式, 也可以通过Maven进行部署, 在这里准备好pom.xml, 如下:

<dependencies>    <dependency>        <groupId>org.springframework</groupId>        <artifactId>spring-core</artifactId>        <version>5.3.8</version>    </dependency>    <dependency>        <groupId>org.springframework</groupId>        <artifactId>spring-beans</artifactId>        <version>5.3.8</version>    </dependency>    <dependency>        <groupId>org.springframework</groupId>        <artifactId>spring-context</artifactId>        <version>5.3.8</version>    </dependency>    <dependency>        <groupId>org.springframework</groupId>        <artifactId>spring-expression</artifactId>        <version>5.3.8</version>    </dependency>    <dependency>        <groupId>commons-logging</groupId>        <artifactId>commons-logging</artifactId>        <version>1.1.3</version>    </dependency>    <dependency> <!-- 引入 Junit 单元测试 -->        <groupId>junit</groupId>        <artifactId>junit</artifactId>        <version>4.7</version>        <scope>compile</scope>    </dependency></dependencies>

Spring 框架 API 配置

IOC 各 API

调用对象的 setter 方法进行配置 bean 对象

创建com.heihu577.beans包, 并创建如下类:

public class Monster {    private Integer id;    private String name;    // getter && setter && 有参构造 && 无参构造 && toString 方法}

随后在Maven仓库中的resources目录中创建beans.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"       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">    <bean id="monster01" class="com.heihu577.beans.Monster"> <!-- 配置好 id 以及 class -->        <property name="id" value="1"/> <!-- property 标签默认调用对象的 setter 方法进行创建 -->        <property name="name">            <value>张三</value> <!-- 当然, 也可以通过 value 标签配置字符串 -->        </property>    </bean></beans>

创建测试代码如下:

public class MyTester {    private ApplicationContext ioc = new ClassPathXmlApplicationContext("beans.xml");    @Test    public void testBasicBean() {        Monster monster01 = ioc.getBean("monster01", Monster.class);        System.out.println(monster01);        /*            运行结果: Monster{id=1, name='张三'} -> hashCode = 767010715        */    }}

运行流程图:开发基础 | Spring 基本使用总结 && 手动实现 Spring

使用 p 进行属性注入

默认也是调用的 setter 方法, 直接给出代码案例:

<bean id="monster03" class="com.heihu577.beans.Monster" p:name="张三" p:id="222"/>

调用对象的 constructor 方法配置 bean 对象

当然, 这里介绍的就是 <constructor-arg>标签, 只需要如下配置即可:

<!-- 使用 setter 方法传递属性 --><bean id="monster01" class="com.heihu577.beans.Monster">    <property name="id" value="1"/>    <property name="name">        <value>张三</value>    </property></bean><!-- 使用 p 进行属性赋值 --><bean id="monster03" class="com.heihu577.beans.Monster" p:name="张三" p:id="222"/><!-- 使用构造方法方法传递属性 --><bean id="monster02" class="com.heihu577.beans.Monster">    <constructor-arg index="0" value="2"/>    <constructor-arg index="1" value="李四"/></bean>

那么我们只需要调用ioc.getBean("monster02")即可得到第二个对象, 如图:开发基础 | Spring 基本使用总结 && 手动实现 Spring

bean 与 bean 之间的依赖关系

准备如下类关系:

public class Son {    private String name;    // getter && setter && 有参构造 && 无参构造 && toString 方法}public class Father {    private Son son;    private String name;    // getter && setter && 有参构造 && 无参构造 && toString 方法}

准备如下 xml:

<bean id="son01" class="com.heihu577.beans.Son">    <property name="name" value="儿子"/></bean><bean id="father01" class="com.heihu577.beans.Father">    <property name="name" value="爸爸"/>    <property name="son" ref="son01"/> <!-- 使用 ref 进行依赖关系 --></bean>

当然也可以这样:

<bean id="father01" class="com.heihu577.beans.Father">    <property name="name" value="爸爸"/>    <property name="son">        <bean class="com.heihu577.beans.Son">            <property name="name" value="儿子"/>        </bean>    </property></bean>

运行结果:开发基础 | Spring 基本使用总结 && 手动实现 Spring

生成 list, map, set, array, props 类型的引用对象

准备如下JavaBean:

public class Obey {    private String name;}

以及包含多个类型的类:

public class Master {    private String name;    private Map<String, Obey> maps;    private Set<Obey> sets;    private List<Obey> lists;    private Obey[] obeys;    private Properties props;    // 有参构造 && 无参构造 && getter && setter && toString}

那么所对应的xml配置为:

<bean id="obey01" class="com.heihu577.beans.Obey"/> <!-- 准备一个 JavaBean --><bean id="master01" class="com.heihu577.beans.Master">    <property name="name">        <value>字符串用value</value>    </property>    <property name="lists">        <list> <!-- List 类型用 list 标签 -->            <ref bean="obey01"/> <!-- lists.get(0) -->            <bean class="com.heihu577.beans.Obey"/> <!-- lists.get(1) -->        </list>    </property>    <property name="obeys">        <array> <!-- 数组类型用 array 标签 -->            <ref bean="obey01"/> <!-- obeys[0] -->            <bean class="com.heihu577.beans.Obey"/> <!-- obeys[1] -->        </array>    </property>    <property name="sets">        <set> <!-- set 类型 用 set 标签 -->            <ref bean="obey01"/>            <bean class="com.heihu577.beans.Obey"/>        </set>    </property>    <property name="maps">        <map> <!-- Map 类型用 map 标签 -->            <entry> <!-- map 类型包含 n 个 entry -->                <key><value>key01</value></key> <!-- entry 中的 key 指明了是 string 类型, 所以用 value 标签 -->                <ref bean="obey01"/> <!-- map 中的 value 部分 -->            </entry>        </map>    </property>    <property name="props">        <props> <!-- Properties 类型用 props 标签 -->            <prop key="username">root</prop>            <prop key="password">password</prop>        </props>    </property></bean>

测试运行结果:

public class MyTester {    private ApplicationContext ioc = new ClassPathXmlApplicationContext("beans.xml");    @Test    public void testBasicBean() {        Master bean = ioc.getBean(Master.class);        System.out.println(bean);        /*            Master{name='字符串用value', maps={key01=com.heihu577.beans.Obey@1a3869f4}, sets=[com.heihu577.beans.Obey@1a3869f4, com.heihu577.beans.Obey@a38d7a3], lists=[com.heihu577.beans.Obey@1a3869f4, com.heihu577.beans.Obey@396e2f39], obeys=[com.heihu577.beans.Obey@1a3869f4, com.heihu577.beans.Obey@a74868d], props={password=password, username=root}}        */    }}
使用 utils 标签生成特殊对象

上面的list, set, map等标签都在property标签内声明的, 我们可以在外面声明, 然后通过ref进行引入, 例如:

<utils:list id="myList">    <ref bean="obey01"/> <!-- lists.get(0) -->    <bean class="com.heihu577.beans.Obey"/> <!-- lists.get(1) --></utils:list><utils:set id="mySet">    <ref bean="obey01"/> <!-- lists.get(0) -->    <bean class="com.heihu577.beans.Obey"/> <!-- lists.get(1) --></utils:set><utils:map id="myMap">    <entry> <!-- map 类型包含 n 个 entry -->        <key><value>key01</value></key> <!-- entry 中的 key 指明了是 string 类型, 所以用 value 标签 -->        <ref bean="obey01"/> <!-- map 中的 value 部分 -->    </entry></utils:map><utils:properties id="myProperties">    <prop key="username">root</prop>    <prop key="password">password</prop></utils:properties><bean id="obey01" class="com.heihu577.beans.Obey"/> <!-- 准备一个 JavaBean --><bean id="master01" class="com.heihu577.beans.Master">    <property name="name">        <value>字符串用value</value>    </property>    <property name="lists" ref="myList"/>    <property name="obeys" ref="myList"/>    <property name="sets" ref="mySet"/>    <property name="maps" ref="myMap"/>    <property name="props" ref="myProperties"/></bean>

工厂类

静态工厂

准备如下工厂类:

public class MyStaticFactory {    private static final Map<String, Monster> monsterMap = new ConcurrentHashMap<>();    static { // 放入所对应的数据        monsterMap.put("monster01", new Monster(1, "monster01"));        monsterMap.put("monster02", new Monster(1, "monster02"));    }    public static Monster getMonster(String key) { // 提供工厂方法        return monsterMap.get(key);    }}

对应的xml配置如下:

<bean id="monster01" class="com.heihu577.factory.MyStaticFactory" factory-method="getMonster">    <!-- factory-method="静态方法名" -->    <constructor-arg index="0" value="monster01"/> <!-- 此时则作为静态方法的参数, 传入 monster01 的 key --></bean>

运行结果:

public class MyTester {    private ApplicationContext ioc = new ClassPathXmlApplicationContext("beans.xml");    @Test    public void testBasicBean() {        Monster monster01 = ioc.getBean("monster01", Monster.class);        System.out.println(monster01);        /*            Monster{id=1, name='monster01'} -> hashCode = 511473681        */    }}
成员工厂

准备如下工厂类:

public class MyFactory {    private final Map<String, Monster> monsterMap = new ConcurrentHashMap<>();    {        monsterMap.put("monster01", new Monster(1, "monster01"));        monsterMap.put("monster02", new Monster(1, "monster02"));    }    public Monster getMonster(String key) {        return monsterMap.get(key);    }}

准备如下xml:

<bean id="myFactory" class="com.heihu577.factory.MyFactory"/><bean id="monster02" factory-bean="myFactory" factory-method="getMonster">    <constructor-arg index="0" value="monster02"/></bean>

运行结果:

public class MyTester {    private ApplicationContext ioc = new ClassPathXmlApplicationContext("beans.xml");    @Test    public void testBasicBean() {        Monster monster02 = ioc.getBean("monster02", Monster.class);        System.out.println(monster02);        /*            Monster{id=1, name='monster02'} -> hashCode = 171497379        */    }}
FactoryBean 工厂

FactoryBeanSpring提供的工厂类的编写模式, 准备如下代码:

public class MyFactoryBean implements FactoryBean<Monster> {    private final Map<String, Monster> monsterMap = new ConcurrentHashMap<>();    private String key;    public MyFactoryBean() {        monsterMap.put("monster01", new Monster(1, "monster01"));        monsterMap.put("monster02", new Monster(1, "monster02"));    }    public void setKey(String key) { // 提供 key 的 setter        this.key = key;    }    @Override    public Monster getObject() throws Exception {        return monsterMap.get(this.key); // 在这里返回具体的对象    }    @Override    public Class<?> getObjectType() {        return Monster.class; // 与指定的泛型一致    }    @Override    public boolean isSingleton() {        return FactoryBean.super.isSingleton();    }}

对应xml:

<bean id="monster01" class="com.heihu577.factory.MyFactoryBean">    <!-- 当spring读取到MyFactoryBean实现了FactoryBean时,则会走工厂方案,在这里我们只需要使用property配置属性即可 -->    <property name="key" value="monster01"/></bean>

运行结果:

public class MyTester {    private ApplicationContext ioc = new ClassPathXmlApplicationContext("beans.xml");    @Test    public void testBasicBean() {        Monster monster01 = ioc.getBean("monster01", Monster.class);        System.out.println(monster01);        // Monster{id=1, name='monster01'} -> hashCode = 640363654    }}

bean 继承属性

<bean id="monster01" class="com.heihu577.beans.Monster">    <property name="id" value="1"/>    <property name="name" value="monster01"/></bean><bean id="monster02" parent="monster01"/>

然后获取monster02即可得到属性值与monster01一致的对象.

bean 对象单例和多例

默认bean对象都是单例, 若需要多例, 我们需指定scope属性, 如下:

<bean id="monster01" scope="prototype" class="com.heihu577.beans.Monster">    <property name="id" value="1"/>    <property name="name" value="monster01"/></bean>

随后运行结果如下:

public class MyTester {    private ApplicationContext ioc = new ClassPathXmlApplicationContext("beans.xml");    @Test    public void testBasicBean() {        System.out.println(ioc.getBean("monster01", Monster.class));        System.out.println(ioc.getBean("monster01", Monster.class));        /*            运行结果:            Monster{id=1, name='monster01'} -> hashCode = 1177377518            Monster{id=1, name='monster01'} -> hashCode = 1773206895            可以发现两次的 hashCode 并不一样.        */    }}

bean 对象钩子函数

定义如下类:

public class Monster {    private Integer id;    private String name;    public void init() {        System.out.println("Monster init...");    }    public void destroy() {        System.out.println("Monster destroy...");    }    // ... 其他方法}

对应的xml如下:

<bean id="monster01" class="com.heihu577.beans.Monster" init-method="init" destroy-method="destroy">    <property name="id" value="1"/>    <property name="name" value="张三"/></bean>

运行结果:

public class MyTester {    private ApplicationContext ioc = new ClassPathXmlApplicationContext("beans.xml");    @Test    public void testBasicBean() throws Exception {        System.out.println(ioc.getBean("monster01", Monster.class));        System.out.println(ioc.getBean("monster01", Monster.class));        ((AbstractApplicationContext) ioc).close();        /*            运行结果:                Monster init...                Monster{id=1, name='张三'} -> hashCode = 926370398                Monster{id=1, name='张三'} -> hashCode = 926370398                Monster destroy...        */    }}

bean 后置处理器 (应用于所有 bean, 有 AOP 的感觉)

定义后置处理器:

public class MyBeanPostProcessor implements BeanPostProcessor {    public Object postProcessBeforeInitialization(Object bean, String beanName) {        if (bean instanceof Monster) {            System.out.println("Monster -> " + beanName + " postProcessBeforeInitialization");        }        return bean;    }    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {        if (bean instanceof Monster) {            System.out.println("Monster -> " + beanName + " postProcessAfterInitialization");        }        return bean;    }}

随后定义如下bean:

<bean id="monster01" class="com.heihu577.beans.Monster" init-method="init" destroy-method="destroy">    <property name="id" value="1"/>    <property name="name" value="monster01"/></bean><bean id="myBeanPostProcessor" class="com.heihu577.beans.MyBeanPostProcessor"/>

运行结果:

public class MyTester {    private ApplicationContext ioc = new ClassPathXmlApplicationContext("beans.xml");    @Test    public void testBasicBean() throws Exception {        System.out.println(ioc.getBean("monster01", Monster.class));        ((AbstractApplicationContext) ioc).close();        /*        运行结果:            Monster -> monster01 postProcessBeforeInitialization            Monster init...            Monster -> monster01 postProcessAfterInitialization            Monster{id=1, name='monster01'} -> hashCode = 422392391            Monster destroy...        */    }}

Spring 读取文件配置

读取文件变量, 配置到Bean中, 首先准备setting.properties, 如下:

<context:property-placeholder location="classpath:settings.properties"/><!--文件内容:    id=1    name=zhangsan-->

效果如图:开发基础 | Spring 基本使用总结 && 手动实现 Spring随后我们即可使用如下配置进行读取配置文件并配置bean, 如下:

<context:property-placeholder location="classpath:settings.properties"/><bean id="monster01" class="com.heihu577.beans.Monster">    <property name="id" value="${id}"/>    <property name="name" value="${name}"/></bean>

运行效果如图:开发基础 | Spring 基本使用总结 && 手动实现 Spring

自动依赖注入 (xml)

准备如下关系:

public class UserController {    private UserService userService;    public void sayHello() {        System.out.println("UserController sayHello");        userService.sayHello();    }    // getter && setter && 有参构造 && 无参构造 && toString 方法    // 一定要提供 setter, 否则无法自动装配}public class UserService {    private UserDAO userDAO;    public void sayHello() {        System.out.println("UserService sayHello");        userDAO.sayHello();    }    // getter && setter && 有参构造 && 无参构造 && toString 方法    // 一定要提供 setter, 否则无法自动装配}public class UserDAO {    public void sayHello() {        System.out.println("UserDAO sayHello");    }}

准备如下xml:

<bean id="userDAO" class="com.heihu577.beans.UserDAO"/><bean autowire="byName"  id="userService" class="com.heihu577.beans.UserService"/><bean autowire="byName"  id="userController" class="com.heihu577.beans.UserController"/>

运行结果:

public class MyTester {    private ApplicationContext ioc = new ClassPathXmlApplicationContext("beans.xml");    @Test    public void testBasicBean() throws Exception {        UserController userController = ioc.getBean("userController", UserController.class);        userController.sayHello();        /*            UserController sayHello            UserService sayHello            UserDAO sayHello        */    }}

通过 EL 表达式给 Bean 赋值

准备如下JavaBean:

public class Person {    private Integer id; // 调用 returnId 成员方法得到值    private String name;  // 调用 returnString 静态方法得到值    private String hobby; // 使用 EL 进行表达式计算    private Integer age; // 使用 EL 进行表达式计算    public Person(Integer id, String name) {        this.id = id;        this.name = name;    }    public Person() {    }    public Integer returnId() {        return 1;    }    public static String returnString() {        return "张三";    }    public Integer getId() {        return id;    }    public void setId(Integer id) {        this.id = id;    }    public String getName() {        return name;    }    public void setName(String name) {        this.name = name;    }    public String getHobby() {        return hobby;    }    public void setHobby(String hobby) {        this.hobby = hobby;    }    public Integer getAge() {        return age;    }    public void setAge(Integer age) {        this.age = age;    }    @Override    public String toString() {        return "Person{" +                "id=" + id +                ", name='" + name + ''' +                ", hobby='" + hobby + ''' +                ", age=" + age +                '}';    }}

那么我们对应的Bean配置如下:

<bean id="person01" class="com.heihu577.beans.Person">    <property name="id" value="#{person01.returnId()}"/>    <property name="name" value="#{T(com.heihu577.beans.Person).returnString()}"/>    <property name="age" value="#{1+17}"/>    <property name="hobby" value="#{'打篮球'}"/></bean>

运行结果:

public class MyTester {    private ApplicationContext ioc = new ClassPathXmlApplicationContext("beans.xml");    @Test    public void testBasicBean() throws Exception {        Person person01 = ioc.getBean("person01", Person.class);        System.out.println(person01);        /*            Person{id=1, name='张三', hobby='打篮球', age=18}        */    }}

通过注解放置Bean

创建com.heihu577.components包, 在该包下进行定义我们的组件, 如图:开发基础 | Spring 基本使用总结 && 手动实现 Spring其中所对应的代码如下:

// Controller 代码public class UserController {    private UserService userService;    public void sayHello() {        System.out.println("UserDAO sayHello...");        userService.sayHello();    }    public UserService getUserService() {        return userService;    }    public void setUserService(UserService userService) {        this.userService = userService;    }    @Override    public String toString() {        return "UserController{" +                "userService=" + userService +                '}';    }}// Service 代码public class UserService {    private UserDAO userDAO;    public void sayHello() {        System.out.println("UserService sayHello...");        userDAO.sayHello();    }    public UserDAO getUserDAO() {        return userDAO;    }    public void setUserDAO(UserDAO userDAO) {        this.userDAO = userDAO;    }    @Override    public String toString() {        return "UserService{" +                "userDAO=" + userDAO +                '}';    }}// DAO 代码public class UserDAO {    public void sayHello() {        System.out.println("UserDAO sayHello...");    }}

那么, 我们可以通过@Service,@Controller,@Repository进行修饰它们, 并且将其放入到IOC容器中.开发基础 | Spring 基本使用总结 && 手动实现 Spring当然, 放入完之后我们需要配置beans.xml文件, 进行扫描包, 加入如下配置项:

<context:component-scan base-package="com.heihu577.components">    <context:include-filter type="annotation" expression="org.springframework.stereotype.Service"/> <!-- 只扫描 Service 注解 -->    <context:exclude-filter type="annotation" expression="org.springframework.stereotype.Service"/> <!-- 除了 Service 注解不扫描, 其他都扫描 -->    <!-- 如果想使用这两种模式, 必须在 component-scan 标签中增加该属性 use-default-filters="false" --></context:component-scan><!--     会扫描包即可, 当然 component-scan 还有其他参数, 但使用的方式并不多.-->

测试代码:

public class MyTester {    private ApplicationContext ioc = new ClassPathXmlApplicationContext("beans.xml");    @Test    public void testBasicBean() throws Exception {        UserController userController = ioc.getBean("userController", UserController.class);        System.out.println(userController);        /*            UserController{userService=null}        */    }}

这里放入容器的规则是, 类名首字母小写, 其他不变.

自动装配依赖关系

在上面我们可以看到, 虽然我们成功将Bean对象放入到IOC容器中, 但是属性之间的依赖关系并没有解决, 在这里我们有两种自动装配的方式.

@Autowired + @Qualifier 装配

@Autowired是通过class 类型进行装配的, 如果IOC容器中存在该类型, 那么会进行自动装配, 例如:

@Controllerpublic class UserController {    @Autowired    private UserService userService;    // 其他原有方法... }public class UserService {    @Autowired    private UserDAO userDAO;    // 其他原有方法... }@Repositorypublic class UserDAO {    // 其他原有方法... }

运行案例:

public class MyTester {    private ApplicationContext ioc = new ClassPathXmlApplicationContext("beans.xml");    @Test    public void testBasicBean() throws Exception {        UserController userController = ioc.getBean("userController", UserController.class);        System.out.println(userController);        userController.sayHello();        /*        运行结果:            UserController{userService=UserService{userDAO=com.heihu577.components.UserDAO@6646153}}            UserDAO sayHello...            UserService sayHello...            UserDAO sayHello...        */    }}

介绍完@Autowired后我们看一下@Qualifier, @Qualifier注解用在@Autowired注解之后, 用于指定IOC容器中的key, 例如当前xml为如下情况:

<context:component-scan base-package="com.heihu577.components"/> <!-- 当前 IOC 容器已经扫描到了一个 UserService --><bean id="userService02" class="com.heihu577.components.UserService"/> <!-- 第二个 userService -->

那么我们可以通过@Qualifier("userService02")进行指定装配key为userService02的bean对象, 在这里就不掩饰了.

@Resource 装配

@Resource可以通过类型,bean名称进行匹配, 功能相当于@Autowired + @Qualifier. 参数使用: @Resource(type = UserService.class, name = "userService"), 按照类型匹配, 如果有多个同类型的bean, 匹配不上按照name匹配, 如果不指定参数则使用这套机制进行匹配(name 值则默认为成员属性的名称).

其中, 自动装配Bean对象的原理模拟, 放在这篇知乎文章中: https://zhuanlan.zhihu.com/p/682619049

泛型依赖注入

有如下Bean关系, 如图:开发基础 | Spring 基本使用总结 && 手动实现 Spring准备如下代码案例:开发基础 | Spring 基本使用总结 && 手动实现 Spring执行测试代码:

public class MyTester {    private ApplicationContext ioc = new ClassPathXmlApplicationContext("beans.xml");    @Test    public void testBasicBean() throws Exception {        BookService bookService = ioc.getBean("bookService", BookService.class);        System.out.println(bookService);        bookService.sayHello();        /*            com.heihu577.Service.BookService@5db45159            BookDAO sayHello...        */    }}

可以看到, 因为BaseService<T>中的BaseDAO<T>是泛型的, 但是因为BookService指明了BaseService<Book>, 那么容器就会寻找指明了Book类型的BaseDAO去使用@Autowired进行自动装配.

AOP 各 API

动态代理

有这么一个场景:

定义一个Vehicle (交通工具)接口, 定义Run方法.

  • 定义Car (汽车)类, 实现交通工具接口, 并且实现Run方法.
  • 定义Airplane (飞机)类, 实现交通工具接口, 并且实现Run方法.

我们需要在Car|Airplane类调用Run方法前后进行监控方法的运行前后, 例如:

交通工具开始运行了... Car Run方法正在调用... 交通工具运行完毕...

我们可以使用传统的方法进行解决, 例如:开发基础 | Spring 基本使用总结 && 手动实现 Spring虽然可以成功, 但是形成了代码冗余, 那么我们的解决方式则是动态代理. 其核心想法则是, 在调用该对象的某个方法前后进行监控.

那么接下来我们就看一下动态代理如何实现, 首先定义VehicleProxyProvider类, 例如:

package com.heihu577.proxy01;import java.lang.reflect.InvocationHandler;import java.lang.reflect.Method;import java.lang.reflect.Proxy;public class VehicleProxyProvider {    private Vehicle target_vehicle;    public VehicleProxyProvider(Vehicle target_vehicle) {        this.target_vehicle = target_vehicle;    }    public Vehicle getProxy() {        ClassLoader classLoader = target_vehicle.getClass().getClassLoader();        Class<?>[] interfaces = target_vehicle.getClass().getInterfaces();        /*        *  public static Object newProxyInstance(ClassLoader loader, 指定类的 classLoader                                          Class<?>[] interfaces, 指定类实现的接口数组                                          InvocationHandler h) 使用匿名类进行实现该方法        * */        Vehicle o = (Vehicle) Proxy.newProxyInstance(classLoader, interfaces, new InvocationHandler() {            @Override            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {                /* proxy -> 代理对象 method -> 方法名称 args -> 方法传递的参数 */                System.out.println("交通工具开始运行了...");                Object result = method.invoke(target_vehicle, args); // 方法的返回结果                System.out.println("交通工具运行完毕...");                return result; // 将方法返回            }        });        return o;    }}

当然, 原来的Airplane|Car中删除掉System.out.println("交通工具开始运行了..."); && System.out.println("交通工具运行完毕...");这两行代码, 下面我们测试一下该代理对象:

@Testpublic void testProxy01() throws Exception {    Vehicle vehicle = new Car();    VehicleProxyProvider vehicleProxyProvider = new VehicleProxyProvider(vehicle); // 传递给代理对象    Vehicle proxy = vehicleProxyProvider.getProxy(); // 调用 getProxy Vehicle 类型的代理对象    proxy.run();    /*        交通工具开始运行了... (代理对象调用的)        Car Run方法正在调用... (Vehicle.run方法的实现)        交通工具运行完毕... (代理对象调用的)    */}
动态代理 DEBUG 执行过程

开发基础 | Spring 基本使用总结 && 手动实现 Spring当然了, 浓缩成如下代码更容易理解 (没有多余的关系):

@Testpublic void testProxy01() throws Exception {    Vehicle vehicle = new Airplane(); // 首先准备一个真正的实例    Vehicle vehicleProxy = (Vehicle) Proxy.newProxyInstance(vehicle.getClass().getClassLoader(),            vehicle.getClass().getInterfaces(),            new InvocationHandler() {                @Override                public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {                    Object result = null; // 运行结果保存                    if (method.getName().equals("run")) { // 如果调用的方法名称为 run 方法的话                        System.out.println("进入到 Run 方法...");                        result = method.invoke(vehicle, args); // 真正的实例.run(args) 这样调用, 返回结果                        System.out.println("运行完毕 Run 方法...");                    }                    return result; // 返回方法运行完毕结果                }            });    vehicleProxy.run(); // 调用 run 方法}

其中, 将其引入到AOP中, 可以这样理解:

@Testpublic void testProxy01() throws Exception {    Vehicle vehicle = new Airplane();    Vehicle vehicleProxy = (Vehicle) Proxy.newProxyInstance(vehicle.getClass().getClassLoader(),            vehicle.getClass().getInterfaces(),            new InvocationHandler() {                @Override                public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {                    Object result = null;                    System.out.println("前置通知");                    try {                        Object invoke = method.invoke(vehicle, args);                    } catch (Exception e) {                        e.printStackTrace();                        System.out.println("异常通知");                    } finally {                        System.out.println("最终通知");                    }                    System.out.println("后置通知");                    return result;                }            });}

动态代理的缺点则是, 在实际开发中, 我们并不希望使用System.out.println放在method.invoke前, 而是希望一个真正的方法放在method.invoke前, 这该如何解决?

动态代理问题解决
使用土方法解决
public void before() { // 定义前置通知方法, 当然, 也可以定义参数, 从外部传递进来即可.    System.out.println("前置通知");}public void after() {    System.out.println("后置通知");}@Testpublic void testProxy01() throws Exception {    Vehicle vehicle = new Airplane();    Vehicle vehicleProxy = (Vehicle) Proxy.newProxyInstance(vehicle.getClass().getClassLoader(),            vehicle.getClass().getInterfaces(),            new InvocationHandler() {                @Override                public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {                    Object result = null;                    before(); // 调用前置方法                    try {                        Object invoke = method.invoke(vehicle, args);                    } catch (Exception e) {                        e.printStackTrace(); // 抛出异常                        System.out.println("异常通知");                    } finally {                        System.out.println("最终通知");                    }                    after(); // 调用后置方法                    return result;                }            });}

这样即可进行在前后插入方法. 但是这样使用耦合度较高, 我们对其进行解耦.

创建com.heihu577.proxy02.HeihuAOP类, 放入前置通知&&后置通知方法, 如下代码:

public class HeihuAOP {    public static void before() {        System.out.println("前置通知");    }    public static void after() {        System.out.println("后置通知");    }}

当然, 这样就比较类似于工具类. 然后在上面的案例中, 将before()换为HeihuAOP.before()即可.

AOP 引入

理解完动态代理后, 下面就引用一下Spring框架的切面编程, 具体概念图如下:开发基础 | Spring 基本使用总结 && 手动实现 Spring

使用 AspectJ 框架引入切面编程

pom.xml文件中增加如下依赖项:

<dependency>    <groupId>org.aspectj</groupId>    <artifactId>aspectjweaver</artifactId>    <version>1.9.6</version></dependency><!-- AOP Alliance --><dependency>    <groupId>aopalliance</groupId>    <artifactId>aopalliance</artifactId>    <version>1.0</version></dependency><!-- CGLIB --><dependency>    <groupId>cglib</groupId>    <artifactId>cglib</artifactId>    <version>2.2</version></dependency><!-- Spring Aspects --><dependency>    <groupId>org.springframework</groupId>    <artifactId>spring-aspects</artifactId>    <version>5.3.8</version></dependency><!-- Spring AOP --><dependency>    <groupId>org.springframework</groupId>    <artifactId>spring-aop</artifactId>    <version>5.3.8</version></dependency>

修改pom.xml为如下:

<context:component-scan base-package="com.heihu577.aspectj"/><aop:aspectj-autoproxy/>  <!-- 当加入这行代码后, 被@Aspect注解类所指明的类|接口 ioc.getBean()返回的则是代理对象 (只能使用接口类型进行接收), 如果没有指明则是普通对象, 或者增加该属性 proxy-target-class="true" -->

定义接口并实现:

public interface Vehicle {    public void run();    public String fly(String msg);}

实现代码:

@Component // 一定要加入扩展, 放入 IOC 容器中public class Airplane implements Vehicle {    @Override    public void run() {        System.out.println("Airplane Run方法正在调用...");    }    @Override    public String fly(String msg) {        String result = "Airplane fly方法正在调用..." + msg;        System.out.println(result);        return result;    }}

随后定义切面类, 并加入@Aspect注解, 如下:

@Aspect@Componentpublic class VehicleAspectJ {    // execution(访问修饰符 方法返回类型 接口|类名.方法名(参数类型))    @Before("execution(public String com.heihu577.aspectj.Vehicle.fly(String))") // 不需要携带形式参数    public void before(JoinPoint joinPoint) {        Signature signature = joinPoint.getSignature(); // 获取方法签名, joinPoint 可以获取调用的参数等信息        System.out.println("前置通知 - 方法签名 -> " + signature.getName() + " 参数 -> " + Arrays.toString(joinPoint.getArgs()));    }    @After("execution(public String com.heihu577.aspectj.Vehicle.fly(String))")    public void after() { // 这里就不用静态方法了, 使用成员方法        System.out.println("后置通知");    }}

最终运行结果:

@Testpublic void testAspectJ() {    com.heihu577.aspectj.Vehicle airplane = ioc.getBean("airplane", com.heihu577.aspectj.Vehicle.class); // 这里接收一定要用接口类型, 因为返回来的是代理对象.    airplane.fly("哈哈哈");    /*        前置通知 - 方法签名 -> fly 参数 -> [哈哈哈]        Airplane fly方法正在调用...哈哈哈        后置通知    */}

其中有五种通知, 如下:

前置通知: @Before 执行方法前 返回通知: @AfterReturning 返回结果后 异常通知: @AfterThrowing try-catch 捕获到异常 后置通知: @After 执行方法后 环绕通知: @Around

模糊配置

在前面我们使用如下代码进行匹配类中的方法:

@Before("execution(public String com.heihu577.aspectj.Vehicle.fly(String))") // 不需要携带形式参数

当然这种方式也支持模糊匹配, 例如:

@After("execution(* *.*(..))") // 所有包的所有类全部执行 (这里的含义是 任意返回类型 任意类.任意方法(任意参数))
切入点表达式

在前面的execution()则是我们的切入点表达式, 已经应用过了, 在这里记载一下用法.开发基础 | Spring 基本使用总结 && 手动实现 Spring有如下规律:

  • 若表达式指明的是已经实现某个普通类的某一个方法, 那么将对该普通类进行切入.
  • 若表达式指明的是接口类, 那么将对实现该接口的所有类进行切入.
  • 若表达式指明的是未实现接口的类的某一个方法, 那么使用ioc.getBean会返回一个CGlib对象, 具体可以参考https://www.cnblogs.com/threeAgePie/p/15832586.html
切入点表达式重用

当我们想对某个方法进行切面, 同时放置前置通知, 后置通知时, 我们需要写两遍相同的execution()表达式, 例如:开发基础 | Spring 基本使用总结 && 手动实现 Spring当然了, 我们具体的优化思路则是使用@PointCut()进行包装, 如图:开发基础 | Spring 基本使用总结 && 手动实现 Spring

JoinPoint

在这里记载一下JoinPoint的常用方法:

public void beforeMethod(JoinPoint joinPoint){    joinPoint.getSignature().getName(); // 获取目标方法名    joinPoint.getSignature().getDeclaringType().getSimpleName(); // 获取目标方法所属类的简单类名    joinPoint.getSignature().getDeclaringTypeName(); // 获取目标方法所属类的类名    joinPoint.getSignature().getModifiers(); // 获取目标方法声明类型(public、private、protected)    Object[] args = joinPoint.getArgs(); // 获取传入目标方法的参数,返回一个数组    joinPoint.getTarget(); // 获取被代理的对象, 比如说 ioc.getBean(Car.class) 返回的是 Car 的代理对象, 在这里可以得到 Car 对象自己    joinPoint.getThis(); // 获取代理对象自己, 得到代理对象}
AfterReturning 得到目标方法返回结果

@AfterReturning注解中, 可以获取方法的返回结果, 下面是@AfterReturning方法的定义:

public @interface AfterReturning {    String value() default "";    String pointcut() default "";    String returning() default ""; // 重点关注该注解属性    String argNames() default "";}

编写如下代码进行测试, JavaBean如下:

@Componentpublic class Hack {    public String getString() {        System.out.println("Hack getString...");        return "Hack String..."; // 返回结果    }}

在切面类中定义如下方法进行切面:

@AfterReturning(value = "execution(String com.heihu577.components.Hack.getString())", returning = "res")public void hackGetString(JoinPoint joinPoint, Object res) { // 这里的 res 与 returning 指定的 res 相同    System.out.println("方法返回结果: res = " + res); // 在这里可以得到返回值}

运行结果:

@Testpublic void t2() {    Hack hack = ioc.getBean("hack", Hack.class);    hack.getString();    /*        Hack getString...        方法返回结果: res = Hack String...    */}
AfterThrowing 捕获异常

@AfterThrowing注解的定义如下:

public @interface AfterThrowing {    String value() default "";    String pointcut() default "";    String throwing() default ""; // 重点关注该方法    String argNames() default "";}

随后我们定义如下方法:

@Componentpublic class Hack {    public Integer testThrowing() {        int i;        try {            i = 1 / 0;        } catch (Exception e) {            throw new RuntimeException("除0错误");        }        return i;    }}

增加如下切面:

@AfterThrowing(value = "execution(Integer com.heihu577.components.Hack.testThrowing())", throwing = "throwMsg")public void hackTestThrowing(JoinPoint joinPoint, RuntimeException throwMsg) {    System.out.println(throwMsg.getClass());    System.out.println(throwMsg.toString());}

最终运行结果:

@Testpublic void t3() {    Hack hack = ioc.getBean("hack", Hack.class);    hack.testThrowing();    /*        class java.lang.RuntimeException        java.lang.RuntimeException: 除0错误 // 输出信息        java.lang.RuntimeException: 除0错误 // 报错信息            at com.heihu577.components.Hack.testThrowing(Hack.java:21)            at com.heihu577.components.Hack$$FastClassBySpringCGLIB$$1e6e314.invoke(<generated>)    */}
环绕通知 [ 了解 ]

环绕通知则是四种通知的组合体, 定义类以及方法如下:

@Componentpublic class Hack {    public String sayHi() {        return "Hi";    }}

定义切面类中的方法对其进行切面:

@Around(value = "execution(String com.heihu577.components.Hack.sayHi())")public void AroundTest(ProceedingJoinPoint proceedingJoinPoint) {    try {        Object[] args = proceedingJoinPoint.getArgs();        System.out.println("前置通知, 执行参数: " + Arrays.asList(args));        Object result = proceedingJoinPoint.proceed(); // 执行该函数        System.out.println("返回通知, 函数运行结果 => " + result);    } catch (Throwable e) {        System.out.println("异常通知");        throw new RuntimeException(e);    } finally {        System.out.println("最终通知");    }}

可以看到, 其中环绕通知的逻辑是我们程序员进行指明的, 最大的区别在于, 函数在哪里执行我们可以进行指定. 编写如下测试代码, 进行测试结果:

@Testpublic void t4() {    Hack hack = (Hack) ioc.getBean("hack");    String data = hack.sayHi();    /*        前置通知, 执行参数: []        后置通知, 函数运行结果 => Hi        最终通知    */}
AOP 优先级问题

如果我们用多个切面类进行切面一个对象的方法, 那么我们的执行优先级顺序可以使用@Order(n)进行控制, n的值越小, 优先级越高.

这里 @Order 如果不传递值, 默认是最低级的.

使用如下代码进行测试:

@Aspect@Componentpublic class AspectJUtils {    @Pointcut("execution(void com.heihu577.beans.Hack.sayHello())")    public void myPointCut() {}    @Before("myPointCut()")    public void beforeSayHello() {        System.out.println("SayHello1 Before...");    }    @After("myPointCut()")    public void afterSayHello() {        System.out.println("SayHello1 After...");    }}// 以及 AspectJUtils2@Aspect@Componentpublic class AspectJUtils2 {    @Pointcut("execution(void com.heihu577.beans.Hack.sayHello())")    public void myPointCut(){}    @Before("myPointCut()")    public void beforeSayHello() {        System.out.println("SayHello2 Before...");    }    @After("myPointCut()")    public void afterSayHello() {        System.out.println("SayHello2 After...");    }}

被切面的类定义如下:

@Componentpublic class Hack {    public void sayHello() {        System.out.println("Hello World");    }}

执行结果:

public class MyTester {    private ApplicationContext ioc = new ClassPathXmlApplicationContext("beans.xml");    @Test    public void before() {        Hack hack = ioc.getBean("hack", Hack.class);        hack.sayHello();        /*            SayHello1 Before...            SayHello2 Before...            Hello World            SayHello2 After...            SayHello1 After...        */    }}

下面我们使用@Order进行控制优先级, 让SayHello2先进行执行:

@Order(2)@Aspect@Componentpublic class AspectJUtils {    @Pointcut("execution(void com.heihu577.beans.Hack.sayHello())")    public void myPointCut(){}    @Before("myPointCut()")    public void beforeSayHello() {        System.out.println("SayHello1 Before...");    }    @After("myPointCut()")    public void afterSayHello() {        System.out.println("SayHello1 After...");    }}// 以及 AspectJUtils2@Order(1)@Aspect@Componentpublic class AspectJUtils2 {    @Pointcut("execution(void com.heihu577.beans.Hack.sayHello())")    public void myPointCut(){}    @Before("myPointCut()")    public void beforeSayHello() {        System.out.println("SayHello2 Before...");    }    @After("myPointCut()")    public void afterSayHello() {        System.out.println("SayHello2 After...");    }}

最终运行结果:

public class MyTester {    private ApplicationContext ioc = new ClassPathXmlApplicationContext("beans.xml");    @Test    public void before() {        Hack hack = ioc.getBean("hack", Hack.class);        hack.sayHello();        /*            SayHello2 Before...            SayHello1 Before...            Hello World            SayHello1 After...            SayHello2 After...        */    }}

其中前置通知, 返回通知等的调用顺序如下:开发基础 | Spring 基本使用总结 && 手动实现 Spring

AOP 基于 XML 配置

准备如下接口:

package com.heihu577.xml;public interface UsbInterface {    public String start();}

实现该接口的JavaBean:

package com.heihu577.xml;public class Phone implements UsbInterface {    @Override    public String start() {        System.out.println("Phone Starting...");        return "OK";    }}

准备切面类:

public class AspectUtil {    public void beforeStart() {        System.out.println("AspectUtil beforeStart...");    }    public void afterStart() {        System.out.println("AspectUtil AfterStart...");    }    public void afterStartReturning(JoinPoint joinPoint, Object res) {        System.out.println("AspectUtil afterStartReturning...");        System.out.println(res);    }    public void afterStartThrowing(JoinPoint joinPoint, RuntimeException runtimeException) {        System.out.println("AspectUtil afterStartThrowing...");        System.out.println(runtimeException.toString());    }}

xml配置如下:

<bean id="phone" class="com.heihu577.xml.Phone"/> <!-- 配置普通的 bean 对象 --><bean id="aspectUtil" class="com.heihu577.xml.AspectUtil"/> <!-- 配置一个自认为的切面类 --><aop:config> <!-- 将自认为的切面类配置为真正的切面类 -->    <aop:pointcut id="myPointCut" expression="execution(String com.heihu577.xml.Phone.start())"/> <!-- 配置切面表达式 -->    <aop:aspect ref="aspectUtil"> <!-- 指定切面对象 -->        <aop:before method="beforeStart" pointcut-ref="myPointCut"/> <!-- 配置前置通知 -->        <aop:after method="afterStart" pointcut-ref="myPointCut"/> <!-- 配置后置通知 -->        <aop:after-returning method="afterStartReturning" pointcut-ref="myPointCut" returning="res"/> <!-- 配置最终通知 -->        <aop:after-throwing method="afterStartThrowing" pointcut-ref="myPointCut" throwing="runtimeException"/> <!-- 配置异常通知 --><!--            <aop:around method="" 配置环绕通知... -->    </aop:aspect></aop:config>

最终运行结果:

public class MyTester {    private ApplicationContext ioc = new ClassPathXmlApplicationContext("beans.xml");    @Test    public void before() {        UsbInterface phone = ioc.getBean("phone", UsbInterface.class);        phone.start();        /*            AspectUtil beforeStart...            Phone Starting...            AspectUtil AfterStart...            AspectUtil afterStartReturning...            OK        */    }}

手动实现 Spring 底层机制

准备工作

搭建 Maven 项目

使用IDEA创建Maven工程后, 修改pom.xml文件内容如下:

<dependencies>    <dependency> <!-- 加入 Spring 开发的基本包 -->        <groupId>org.springframework</groupId>        <artifactId>spring-context</artifactId>        <version>5.3.8</version>    </dependency>    <dependency> <!-- 加入 Spring 开发切面编程需要的包 -->        <groupId>org.springframework</groupId>        <artifactId>spring-aspects</artifactId>        <version>5.3.8</version>    </dependency>    <dependency> <!-- 引入 Junit 单元测试 -->        <groupId>junit</groupId>        <artifactId>junit</artifactId>        <version>4.7</version>        <scope>compile</scope>    </dependency></dependencies>

准备类与类之间的关系

准备beans.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"       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd">    <context:component-scan base-package="com.heihu577.component"/> <!-- 扫描包 --></beans>

并且在该包下创建如下类关系, 例如: UserAction:

package com.heihu577.component;import org.springframework.stereotype.Component;@Component // 也可以使用 @Controller 注解public class UserAction {}

UserDAO:

package com.heihu577.component;import org.springframework.stereotype.Component;@Component // 也可以使用 @Repository 注解public class UserDAO {    public void hi() {        System.out.println("UserDAO hi...");    }}

UserService:

package com.heihu577.component;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.stereotype.Component;@Component // 也可以使用 @Service 注解public class UserService {    @Autowired // 也可以使用 @Resource    private UserDAO userDAO;    public void m1() {        userDAO.hi();    }}

测试:

public class T {    private ApplicationContext ioc = new ClassPathXmlApplicationContext("beans.xml");    @Test    public void t1() {        UserAction userAction = ioc.getBean("userAction", UserAction.class);        System.out.println(userAction); // com.heihu577.component.UserAction@358c99f5        UserService userService = ioc.getBean("userService", UserService.class);        System.out.println(userService); // com.heihu577.component.UserService@3ee0fea4        UserDAO userDAO = ioc.getBean("userDAO", UserDAO.class);        System.out.println(userDAO); // com.heihu577.component.UserDAO@48524010    }}

单例多例问题

在这里我们默认使用@Component, @Service, @Controller, @Repository注解默认是单例的, 如果我们想配置多例, 那么可以使用@Scope注解, 例如:

package com.heihu577.component;import org.springframework.context.annotation.Scope;import org.springframework.stereotype.Component;@Component // 也可以使用 @Controller 注解@Scope("prototype")public class UserAction {}

里面有很多想法需要我们实现, 例如: 为什么加入@Autowired, Spring 容器将自动实现依赖注入. 为什么加入@Scope, Spring 容器将获取多例.

BeanPostProcessor 问题

在Spring中, 有BeanPostProcessor接口, 如果实现了这个接口, 相当于在该对象初始化前后进行切面一个方法, 例如:

public class MyBeanPostProcessor implements BeanPostProcessor {    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {        System.out.println("MyBeanPostProcessor postProcessBeforeInitialization 被调用, beanName = " + beanName);        return bean;    }    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {        System.out.println("MyBeanPostProcessor postProcessAfterInitialization 被调用, beanName = " + beanName);        return bean;    }}

并且在xml文件中进行配置该类:

<context:component-scan base-package="com.heihu577.component"/><bean id="myBeanPostProcessor" class="com.heihu577.process.MyBeanPostProcessor"/>

当然了, 之前在学习该部分内容时, <bean>标签中有一个init-method属性, 如果使用扫描包, 使用@PostConstructor注解, 例如:

@Component // 也可以使用 @Controller 注解@Scope("prototype")public class UserAction {    @PostConstruct // 相当于 init-method 属性    public void init() {        System.out.println("UserAction init...");    }}

那么我们查看运行结果:

public class T {    private ApplicationContext ioc = new ClassPathXmlApplicationContext("beans.xml");    @Test    public void t1() {        UserAction userAction = ioc.getBean("userAction", UserAction.class);        System.out.println(userAction);        UserService userService = ioc.getBean("userService", UserService.class);        System.out.println(userService);        UserDAO userDAO = ioc.getBean("userDAO", UserDAO.class);        System.out.println(userDAO);        /*            MyBeanPostProcessor postProcessBeforeInitialization 被调用, beanName = userDAO            MyBeanPostProcessor postProcessAfterInitialization 被调用, beanName = userDAO            MyBeanPostProcessor postProcessBeforeInitialization 被调用, beanName = userService            MyBeanPostProcessor postProcessAfterInitialization 被调用, beanName = userService            MyBeanPostProcessor postProcessBeforeInitialization 被调用, beanName = userAction            UserAction init... (这里使用 @PostConstruct 指定了 init-method 方法)            MyBeanPostProcessor postProcessAfterInitialization 被调用, beanName = userAction            com.heihu577.component.UserAction@2b6856dd            com.heihu577.component.UserService@5db45159            com.heihu577.component.UserDAO@6107227e        */    }}

单例时, 在IOC容器启动时, 会自动调用BeanPostProcessor, 多例时, 当ioc.getBean时, 会调用BeanPostProcessor.

AOP 问题

定义如下接口:

package com.heihu577.aop;public interface UsbInterface {    public void start();}

定义类, 实现该接口:

package com.heihu577.aop;import org.springframework.stereotype.Component;@Componentpublic class Phone implements UsbInterface{    @Override    public void start() {        System.out.println("Phone Start...");    }}

定义切面类:

@Aspect@Componentpublic class UsbAspect {    @Pointcut("execution(void com.heihu577.aop.UsbInterface.start())")    public void myPointCut() {    }    @Before("myPointCut()")    public void startBefore(JoinPoint joinPoint) {        System.out.println("start Before...");    }    @After("myPointCut()")    public void startAfter(JoinPoint joinPoint) {        System.out.println("start After...");    }    @AfterThrowing("myPointCut()")    public void afterThrowing() {        System.out.println("start After Throwing...");    }    @AfterReturning("myPointCut()")    public void afterReturning() {        System.out.println("start After Returning...");    }}

定义xml文件, 并进行扫描:

<context:component-scan base-package="com.heihu577"/> <!-- 扫描 --><aop:aspectj-autoproxy/> <!-- 开启 AOP -->

运行结果:

public class T {    private ApplicationContext ioc = new ClassPathXmlApplicationContext("beans.xml");    @Test    public void t1() {        UsbInterface bean = ioc.getBean(UsbInterface.class);        bean.start();        /*            MyBeanPostProcessor postProcessBeforeInitialization 被调用, beanName = phone            MyBeanPostProcessor postProcessAfterInitialization 被调用, beanName = phone            MyBeanPostProcessor postProcessBeforeInitialization 被调用, beanName = usbAspect            MyBeanPostProcessor postProcessAfterInitialization 被调用, beanName = usbAspect            MyBeanPostProcessor postProcessBeforeInitialization 被调用, beanName = userDAO            MyBeanPostProcessor postProcessAfterInitialization 被调用, beanName = userDAO            MyBeanPostProcessor postProcessBeforeInitialization 被调用, beanName = userService            MyBeanPostProcessor postProcessAfterInitialization 被调用, beanName = userService            start Before...            Phone Start...            start After Returning...            start After...        */    }}

那么下来来研究一下BeanPostProcessorAOP切面之间的关系. 配置BeanPostProcessor如下:

package com.heihu577.process;import org.springframework.beans.BeansException;import org.springframework.beans.factory.config.BeanPostProcessor;import org.springframework.lang.Nullable;import org.springframework.stereotype.Component;@Componentpublic class MyBeanPostProcessor implements BeanPostProcessor {    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {        System.out.println("MyBeanPostProcessor postProcessBeforeInitialization 被调用, beanName = " + beanName + " " +                "class " + bean.getClass()); // 增加查看 class 的功能        return bean;    }    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {        System.out.println("MyBeanPostProcessor postProcessAfterInitialization 被调用, beanName = " + beanName + " class" +                " " + bean.getClass()); // 增加查看 class 的功能        return bean;    }}

运行结果如图:

MyBeanPostProcessor postProcessBeforeInitialization 被调用, beanName = phone class class com.heihu577.aop.Phone // 在进入 postProcessBeforeInitialization 之前还是 Phone 类型MyBeanPostProcessor postProcessAfterInitialization 被调用, beanName = phone class class com.sun.proxy.$Proxy18 // 在进入 postProcessAfterInitialization 之后变为了代理类型MyBeanPostProcessor postProcessBeforeInitialization 被调用, beanName = usbAspect class class com.heihu577.aop.UsbAspectMyBeanPostProcessor postProcessAfterInitialization 被调用, beanName = usbAspect class class com.heihu577.aop.UsbAspectMyBeanPostProcessor postProcessBeforeInitialization 被调用, beanName = userDAO class class com.heihu577.component.UserDAOMyBeanPostProcessor postProcessAfterInitialization 被调用, beanName = userDAO class class com.heihu577.component.UserDAOMyBeanPostProcessor postProcessBeforeInitialization 被调用, beanName = userService class class com.heihu577.component.UserServiceMyBeanPostProcessor postProcessAfterInitialization 被调用, beanName = userService class class com.heihu577.component.UserServicestart Before...Phone Start...start After Returning...start After...

我们可以看到, 被切面的类, 在postProcessBeforeInitialization之前是类本身的类型, 而在postProcessAfterInitialization后则变为了代理类型, 下面我们可以揭开它们的面纱.

AOP 的底层是基于 BeanPostProcessor 机制的. 在 Bean 创建好后, 根据是否需要 AOP 处理, 决定返回代理对象, 还是原生 Bean 在返回代理对象时, 就可以根据要代理的类和方法返回

最终目标: 不使用 Spring 框架, 模拟 Spring 底层实现, 也能完成相同的功能.

Spring 整体架构分析

开发基础 | Spring 基本使用总结 && 手动实现 Spring

classpath 路径

在执行java.exe时, 使用-classpath参数可以进行指明classLoader的路径, 例如:开发基础 | Spring 基本使用总结 && 手动实现 Spring所以我们在使用classloader获取类资源时, 可以找到target目录下的资源, 下面我们创建一个IDEA子模块, 来对我们当前的项目进行并行.开发基础 | Spring 基本使用总结 && 手动实现 Spring

然后根据我们之前所编写的项目, 进行分解. https://zhuanlan.zhihu.com/p/682619049

编写基础环境

阶段1: 实现扫描包

准备com.heihu577.annotations包, 并准备如下注解:

@Target(ElementType.TYPE)@Retention(RetentionPolicy.RUNTIME)public @interface Component {    String value() default "";}

以及:

@Target(ElementType.TYPE) // 该注解可以修饰类中的哪部分类型@Retention(RetentionPolicy.RUNTIME) // 指定 ComponentScan 注解保留范围, 当前运行时生效public @interface ComponentScan {    String value() default ""; // 可以传入 value 属性, 默认值为空字符串}

准备完毕注解后, 我们创建com.heihu577.components包, 并且准备一些测试Bean:

@Componentpublic class MonsterDAO {}
@Component(value = "monsterService") // 将 MonsterService 注入到容器中public class MonsterService {}
public class NoBean {}

并且在com.heihu577.ioc准备如下核心逻辑代码:

public class HeihuSpringApplicationContext {    private Class<?> springConfig;    public HeihuSpringApplicationContext(Class<?> springConfig) {        this.springConfig = springConfig;        initApplicationContext();    }    public void initApplicationContext() {        ComponentScan annotation = springConfig.getAnnotation(ComponentScan.class); // 得到该注解        String componentScanValue = annotation.value(); // 得到注解中的配置值        String classPath = componentScanValue.replace('.', '/'); // 得到真正的路径信息        System.out.println("要扫描的包: " + classPath);        URL classPathResource = springConfig.getClassLoader().getResource(classPath); // 得到 URL, 路径存在不返回 null        if (classPathResource != null) {            File file = new File(classPathResource.getPath()); // 得到 file 资源            if (file.isDirectory()) {                File[] files = file.listFiles(); // 得到所有编译好的文件信息                for (File f : files) { // 扫描 .class 的文件信息                    String fStr = f.toString(); // 字符串截取判断                    if (fStr.substring(fStr.indexOf(".")).equals(".class")) { // 判断最后是否是 .class 结尾                        String className = f.getName().replace(".class", "");                        String fullCLassPath = (classPath + "/" + className).replace("/", "."); // 再转换回来, 得到包名+类名                        try {                            Class<?> clazz = Class.forName(fullCLassPath);                            if (clazz.isAnnotationPresent(Component.class)) {                                System.out.println("是 Bean: " + clazz);                            } else {                                System.out.println("不是 Bean: " + clazz);                            }                        } catch (ClassNotFoundException e) {                            throw new RuntimeException(e);                        }                    }                }            }        } else {            System.out.println("扫描包出现了问题");        }    }}

以及我们所定义ComponentScan注解:

@ComponentScan("com.heihu577.components")public class HeihuSpringConfig {}

运行结果:

public class T {    public static void main(String[] args) {        HeihuSpringApplicationContext heihuSpringApplicationContext = new HeihuSpringApplicationContext(HeihuSpringConfig.class);        /*            要扫描的包: com/heihu577/components            是 Bean: class com.heihu577.components.MonsterDAO            是 Bean: class com.heihu577.components.MonsterService            不是 Bean: class com.heihu577.components.SB        */    }}

阶段2: 将 bean 信息封装到容器

当我们getBean时, 我们需要考虑两点.

  • getBean 时, 发现是单例 (singleton)的, 那么从另一个集合(单例集合)中直接获得到 Object
  • getBean 时, 发现是多例 (prototype)的, 那么现创建一个 Object

那么, 既然考虑到判断单例多例, 在原生Spring中, 可以定义@Scope, 所以在这里我们自己的框架中, 定义一个@Scope注解即可.

@Target(ElementType.TYPE) // 该注解可以修饰类中的哪部分类型@Retention(RetentionPolicy.RUNTIME) // 指定 ComponentScan 注解保留范围, 当前运行时生效public @interface Scope {    String value() default "singleton"; // 默认单例}

随后我们定义BeanDefinition, 用来保存每个类是单例|多例的.

package com.heihu577.ioc;public class BeanDefinition {    private String scope; // 当前 bean 作用范围    private Class<?> clazz; // 当前 bean 的 Class 对象    public BeanDefinition(String scope, Class<?> clazz) {        this.scope = scope;        this.clazz = clazz;    }    public BeanDefinition() {    }    public String getScope() {        return scope;    }    public void setScope(String scope) {        this.scope = scope;    }    public Class<?> getClazz() {        return clazz;    }    public void setClazz(Class<?> clazz) {        this.clazz = clazz;    }    @Override    public String toString() {        return "BeanDefinition{" +                "scope='" + scope + ''' +                ", clazz=" + clazz +                '}';    }}

随后我们将其定义为成员属性, 放到HeihuSpringApplicationContext中, 并定义成为一个Map集合, 例如:

private Map<String, BeanDefinition> beanDefinitionMap = new ConcurrentHashMap<>(); // 用于保存类原型

随后我们需要定义一个单例池, 用于保存所有单例.

private Map<String, Object> singletonObject = new ConcurrentHashMap<>(); // 用于保存单例对象

随后initApplicationContext方法的定义如下:

public void initApplicationContext() {    ComponentScan annotation = springConfig.getAnnotation(ComponentScan.class); // 得到该注解    String componentScanValue = annotation.value(); // 得到注解中的配置值    String classPath = componentScanValue.replace('.', '/'); // 得到真正的路径信息    System.out.println("要扫描的包: " + classPath);    URL classPathResource = springConfig.getClassLoader().getResource(classPath); // 得到 URL, 路径存在不返回 null    if (classPathResource != null) {        File file = new File(classPathResource.getPath()); // 得到 file 资源        if (file.isDirectory()) {            File[] files = file.listFiles(); // 得到所有编译好的文件信息            for (File f : files) { // 扫描 .class 的文件信息                String fStr = f.toString(); // 字符串截取判断                if (fStr.substring(fStr.indexOf(".")).equals(".class")) { // 判断最后是否是 .class 结尾                    String className = f.getName().replace(".class", "");                    String fullCLassPath = (classPath + "/" + className).replace("/", "."); // 再转换回来, 得到包名+类名                    try {                        Class<?> clazz = Class.forName(fullCLassPath);                        if (clazz.isAnnotationPresent(Component.class)) {                            String beanName = clazz.getDeclaredAnnotation(Component.class).value();                            if (beanName.equals("")) { // 如果是没配置的情况, 那么定义为首字母小写                                beanName = className.substring(0, 1).toLowerCase() + className.substring(1);                            }                            BeanDefinition beanDefinition = new BeanDefinition();                            beanDefinition.setClazz(clazz); // 保存当前 class 类型                            if (clazz.isAnnotationPresent(Scope.class)) {                                // 前面有 Scope 注解定义                                String scopeValue = clazz.getAnnotation(Scope.class).value();                                beanDefinition.setScope(scopeValue);                            } else {                                beanDefinition.setScope("singleton"); // 默认 singleton                            }                            beanDefinitionMap.put(beanName, beanDefinition); // 保存容器                            System.out.println("是 Bean: " + clazz);                        } else {                            System.out.println("不是 Bean: " + clazz);                        }                    } catch (ClassNotFoundException e) {                        throw new RuntimeException(e);                    }                }            }        }    } else {        System.out.println("扫描包出现了问题");    }}

我们重点关注对BeanDefinition集合类的一个操作. 最终效果如下:开发基础 | Spring 基本使用总结 && 手动实现 Spring

阶段3: 初始化单例池

那么我们创建一个基于BeanDefinition返回对象的方法.

private Object createBean(BeanDefinition beanDefinition) {    Class<?> clazz = beanDefinition.getClazz();    try {        Object o = clazz.getDeclaredConstructor().newInstance();        return o;    } catch (InstantiationException e) {        throw new RuntimeException(e);    } catch (IllegalAccessException e) {        throw new RuntimeException(e);    } catch (InvocationTargetException e) {        throw new RuntimeException(e);    } catch (NoSuchMethodException e) {        throw new RuntimeException(e);    }}

随后我们在initApplicationContext方法之后, 进行遍历BeanDefinition, 判断Scope是单例还是多例, 如果是单例, 那么放入到我们的单例池.

public HeihuSpringApplicationContext(Class<?> springConfig) {    this.springConfig = springConfig;    initApplicationContext(); // 扫描包的代码    judgeBeanDefinition(); // 单例池初始化代码}public void judgeBeanDefinition() {    Set<Map.Entry<String, BeanDefinition>> entries = beanDefinitionMap.entrySet();    // 得到所有 entry    for (Map.Entry<String, BeanDefinition> entry : entries) { // 遍历 entry 信息        BeanDefinition beanDefinition = entry.getValue(); // 得到 beanDefinition 类        String scope = beanDefinition.getScope(); // 得到单例还是多例        if ("singleton".equalsIgnoreCase(scope)) { // 如果是单例            singletonObject.put(entry.getKey(), createBean(beanDefinition)); // 放入到单例容器中        }    }}

下面我们就可以根据beanDefinitionMap, singletonObject来定义一套自己的getBean的逻辑.

  • 如果该 Bean 不存在 (不在 beanDefinitionMap 中), 那么直接抛出异常.
  • 如果该 Bean 存在, 那么得到 BeanDefinition, 进行判断 Scope 类型.
  • 如果类型是单例, 那么从单例池中寻找
  • 如果类型是多例, 直接调用createBean方法, 返回一个对象.

具体实现方法如下:

public Object getBean(String name) {    if (beanDefinitionMap.containsKey(name)) { // 如果 bean 存在        BeanDefinition beanDefinition = beanDefinitionMap.get(name); // 得到该 bean 的 class        String scope = beanDefinition.getScope(); // 拿到 单例|多例        if ("singleton".equalsIgnoreCase(scope)) { // 如果是 单例模式            return singletonObject.get(name); // 直接从单例容器中取        } else {            return createBean(beanDefinition); // 多例模式, 直接调用 createBean 进行创建        }    } else {        throw new NullPointerException("该 bean 不存在!");    }}

运行结果:

public class T {    public static void main(String[] args) {        HeihuSpringApplicationContext heihuSpringApplicationContext = new HeihuSpringApplicationContext(HeihuSpringConfig.class);        // monsterDAO 是单例, monsterService 是多例        Object bean1 = heihuSpringApplicationContext.getBean("monsterDAO");        Object bean2 = heihuSpringApplicationContext.getBean("monsterDAO");        System.out.println(bean1); // com.heihu577.components.MonsterDAO@5e481248        System.out.println(bean2); // com.heihu577.components.MonsterDAO@5e481248        Object bean3 = heihuSpringApplicationContext.getBean("monsterService");        Object bean4 = heihuSpringApplicationContext.getBean("monsterService");        System.out.println(bean3); // com.heihu577.components.MonsterService@66d3c617        System.out.println(bean4); // com.heihu577.components.MonsterService@63947c6b        Object bean = heihuSpringApplicationContext.getBean("ABC");        /*            Exception in thread "main" java.lang.NullPointerException: 该 bean 不存在!            at com.heihu577.ioc.HeihuSpringApplicationContext.getBean(HeihuSpringApplicationContext.java:110)            at T.main(T.java:17)        */    }}

实现依赖注入

在前面的createBean方法中, 我们已经得到了bean的class, 所以在这里我们依赖注入的位置放在createBean中更加合适, 例如:

private Object createBean(BeanDefinition beanDefinition) {    Class<?> clazz = beanDefinition.getClazz();    try {        Object o = clazz.getDeclaredConstructor().newInstance();        // ... 依赖注入核心代码        return o;    } catch (InstantiationException e) {        throw new RuntimeException(e);    } catch (IllegalAccessException e) {        throw new RuntimeException(e);    } catch (InvocationTargetException e) {        throw new RuntimeException(e);    } catch (NoSuchMethodException e) {        throw new RuntimeException(e);    }}

创建一个@Autowired注解, 如下:

package com.heihu577.annotations;import java.lang.annotation.*;@Target(ElementType.FIELD) // 作用于成员属性上@Retention(RetentionPolicy.RUNTIME)public @interface Autowired {    boolean required() default true;}

创建后, 我们在MonsterDAO中增加一个方法, 用于验证我们增加注解后是否成功, 例如:

@Componentpublic class MonsterDAO {    public void sayHello() {        System.out.println("MonsterDAO Hello World");    }}

并且, 创建MonsterServiceMonsterDAO之间的关系:

@Scope("prototype")@Component(value = "monsterService") // 将 MonsterService 注入到容器中public class MonsterService {    @Autowired    private MonsterDAO monsterDAO;    public void sayHello() {        monsterDAO.sayHello();    }}

在这里直接调用即可, 下面我们准备核心代码:

private Object createBean(BeanDefinition beanDefinition) {    Class<?> clazz = beanDefinition.getClazz();    try {        Object o = clazz.getDeclaredConstructor().newInstance();        Field[] declaredFields = clazz.getDeclaredFields();        for (Field declaredField : declaredFields) {            if (declaredField.isAnnotationPresent(Autowired.class)) { // 如果该字段被 Autowired 所修饰                String fieldName = declaredField.getName(); // 得到字段名称                Object bean = getBean(fieldName); // 通过 getBean 得到该对象 (根据字段名称查找)                declaredField.setAccessible(true);                declaredField.set(o, bean); // 将该字段的值设置为该 bean 对象            }        }        return o;    } catch (InstantiationException e) {        throw new RuntimeException(e);    } catch (IllegalAccessException e) {        throw new RuntimeException(e);    } catch (InvocationTargetException e) {        throw new RuntimeException(e);    } catch (NoSuchMethodException e) {        throw new RuntimeException(e);    }}public Object getBean(String name) {    if (beanDefinitionMap.containsKey(name)) {        BeanDefinition beanDefinition = beanDefinitionMap.get(name);        String scope = beanDefinition.getScope();        if ("singleton".equalsIgnoreCase(scope)) {            return singletonObject.get(name);        } else {            return createBean(beanDefinition);        }    } else {        throw new NullPointerException("该 bean 不存在!");    }}

最终测试结果:

public class T {    public static void main(String[] args) {        HeihuSpringApplicationContext heihuSpringApplicationContext = new HeihuSpringApplicationContext(HeihuSpringConfig.class);        MonsterService monsterService = (MonsterService) heihuSpringApplicationContext.getBean("monsterService");        monsterService.sayHello();        /*            要扫描的包: com/heihu577/components            是 Bean: class com.heihu577.components.MonsterDAO            是 Bean: class com.heihu577.components.MonsterService            不是 Bean: class com.heihu577.components.NoBean            MonsterDAO Hello World        */    }}

实现后置处理器功能

实现初始化方法

定义如下接口:

package com.heihu577.processor;public interface InitializingBean {    // 根据原生 spring, 定义了该接口, 该接口有一个方法    void afterPropertiesSet() throws Exception; // 在 Bean 的 setter 方法后执行}

并且让Bean进行实现:

@Componentpublic class MonsterDAO implements InitializingBean {    public void sayHello() {        System.out.println("MonsterDAO Hello World");    }    @Override    public void afterPropertiesSet() throws Exception {        System.out.println("afterPropertiesSet MonsterDAO Hi");    }}

在接口编程中, 我们可以根据该类是否实现了某个接口, 来进行判断是否要执行某个业务逻辑. 我们在HeihuSpringApplicationContext类中的createBean方法中, 我们可以这样定义:

private Object createBean(BeanDefinition beanDefinition) {    Class<?> clazz = beanDefinition.getClazz();    try {        Object o = clazz.getDeclaredConstructor().newInstance();        Field[] declaredFields = clazz.getDeclaredFields();        for (Field declaredField : declaredFields) {            if (declaredField.isAnnotationPresent(Autowired.class)) { // 如果该字段被 Autowired 所修饰                String fieldName = declaredField.getName(); // 得到字段名称                Object bean = getBean(fieldName); // 通过 getBean 得到该对象 (根据字段名称查找)                declaredField.setAccessible(true);                declaredField.set(o, bean); // 将该字段的值设置为该 bean 对象            }        }        if (o instanceof InitializingBean) {            // 如果实现 InitializingBean 接口, 那么就直接调用 afterPropertiesSet            ((InitializingBean) o).afterPropertiesSet();        }        return o;    } catch (InstantiationException e) {        throw new RuntimeException(e);    } catch (IllegalAccessException e) {        throw new RuntimeException(e);    } catch (InvocationTargetException e) {        throw new RuntimeException(e);    } catch (NoSuchMethodException e) {        throw new RuntimeException(e);    } catch (Exception e) {        throw new RuntimeException(e);    }}

最终测试结果:

public class T2 {    public static void main(String[] args) {        HeihuSpringApplicationContext heihuSpringApplicationContext = new HeihuSpringApplicationContext(HeihuSpringConfig.class);        MonsterDAO monsterDAO = (MonsterDAO) heihuSpringApplicationContext.getBean("monsterDAO");        monsterDAO.sayHello();        /*            要扫描的包: com/heihu577/components            是 Bean: class com.heihu577.components.MonsterDAO            是 Bean: class com.heihu577.components.MonsterService            不是 Bean: class com.heihu577.components.NoBean            afterPropertiesSet MonsterDAO Hi            MonsterDAO Hello World        */    }}

实现 BeanPostProcessor 功能

我们可以根据原生Spring中, 来定义一个BeanPostProcessor功能.

@Componentpublic interface BeanPostProcessor {    @Nullable    default Object postProcessBeforeInitialization(Object bean, String beanName) {        return bean;    }    @Nullable    default Object postProcessAfterInitialization(Object bean, String beanName)  {        return bean;    }}

随后我们可以定义一个容器, 用于实现它.

@Componentpublic class HeihuBeanPostProcessor implements BeanPostProcessor {    public Object postProcessBeforeInitialization(Object bean, String beanName) {        System.out.println("HeihuBeanPostProcessor postProcessBeforeInitialization... " + beanName);        return bean;    }    public Object postProcessAfterInitialization(Object bean, String beanName)  {        System.out.println("HeihuBeanPostProcessor postProcessAfterInitialization... " + beanName);        return bean;    }}

因为可能会有多个BeanPostProcessor对象, 所以我们在我们的HeihuSpringApplicationContext中, 可以定义一个ArrayList, 并且类型是BeanPOstProcessor池.

private List<BeanPostProcessor> beanPostProcessorList = new ArrayList<>();

并且我们在扫描包的时候增加如下代码:

String className = f.getName().replace(".class", "");String fullCLassPath = (classPath + "/" + className).replace("/", "."); // 再转换回来, 得到包名+类名try {    Class<?> clazz = Class.forName(fullCLassPath);    if (clazz.isAnnotationPresent(Component.class)) {        if (BeanPostProcessor.class.isAssignableFrom(clazz)) {            // 这个类实现了 BeanPostProcessor 接口            BeanPostProcessor beanPostProcessor = (BeanPostProcessor) clazz.getDeclaredConstructor().newInstance();            beanPostProcessorList.add(beanPostProcessor); // 加入到我们的 beanPostProcessorList 容器中            continue; // 直接进入下一跳. 单独放到ArrayList中即可        }

当然了, 我们的执行前后也是在初始化方法前后进行执行, 我们可以在createBean方法中的((InitializingBean) o).afterPropertiesSet()前后加入如下代码:

for (BeanPostProcessor beanPostProcessor : beanPostProcessorList) {    o = beanPostProcessor.postProcessBeforeInitialization(o, "..."); // 前置}if (o instanceof InitializingBean) {    ((InitializingBean) o).afterPropertiesSet(); // 初始化}for (BeanPostProcessor beanPostProcessor : beanPostProcessorList) {    o = beanPostProcessor.postProcessAfterInitialization(o, "..."); // 后置}return o;

运行结果:

public class T2 {    public static void main(String[] args) {        HeihuSpringApplicationContext heihuSpringApplicationContext = new HeihuSpringApplicationContext(HeihuSpringConfig.class);        MonsterDAO monsterDAO = (MonsterDAO) heihuSpringApplicationContext.getBean("monsterDAO");        monsterDAO.sayHello();        /*HeihuBeanPostProcessor postProcessBeforeInitialization... com.heihu577.components.MonsterDAO@66d3c617afterPropertiesSet MonsterDAO HiHeihuBeanPostProcessor postProcessAfterInitialization... com.heihu577.components.MonsterDAO@66d3c617MonsterDAO Hello World        */    }}
根据 BeanDefinition 得到 beanName

当然了, 我们之前所定义的public Object postProcessBeforeInitialization(Object bean, String beanName)中, beanName 我们不知道传什么, 在这里我们只需要提供一个根据BeanDefinition得到beanName的一个功能即可:

public String getKeyBybeanDefinitionMap(BeanDefinition beanDefinition) {    Set<Map.Entry<String, BeanDefinition>> entries = beanDefinitionMap.entrySet();    for (Map.Entry<String, BeanDefinition> entry : entries) {        BeanDefinition dstBeanDefinition = entry.getValue();        if (dstBeanDefinition == beanDefinition) { // 比较是否是同一对象            return entry.getKey();        }    }    return null;}

那么我们BeanPostProcessor调用的思路则是如下:

String beanName = getKeyBybeanDefinitionMap(beanDefinition);for (BeanPostProcessor beanPostProcessor : beanPostProcessorList) {    o = beanPostProcessor.postProcessBeforeInitialization(o, beanName);}if (o instanceof InitializingBean) {    // 如果实现 InitializingBean 接口, 那么就直接调用 afterPropertiesSet    ((InitializingBean) o).afterPropertiesSet();}for (BeanPostProcessor beanPostProcessor : beanPostProcessorList) {    o = beanPostProcessor.postProcessAfterInitialization(o, beanName);}

当然了, 如果自定义的beanPostProcessor返回NULL, 会直接影响到o变量, 此时再对o进行操作会爆出空指针异常, 在这里我们可以使用如下代码逻辑:

for (BeanPostProcessor beanPostProcessor : beanPostProcessorList) {    Object o1 = beanPostProcessor.postProcessBeforeInitialization(o, beanName);    if (o1 == null) {        continue;    } else {        o = o1;    }}if (o instanceof InitializingBean) {    // 如果实现 InitializingBean 接口, 那么就直接调用 afterPropertiesSet    ((InitializingBean) o).afterPropertiesSet();}for (BeanPostProcessor beanPostProcessor : beanPostProcessorList) {    Object o1 = beanPostProcessor.postProcessAfterInitialization(o, beanName);    if (o1 == null) {        continue;    } else {        o = o1;    }}return o;

这样的逻辑处理后就不会爆出异常.

实现 AOP 功能

在Spring框架中, 被@Aspect注解所修饰的类, 在getBean时, 返回的是$Proxy代理类, 所以在这里我们需要使用动态代理技术. 在Spring框架中, BeanPostProcessor可以对方法进行切面, 所以在这里我们需要使用BeanPostProcessor技术.

下面我们准备一个IUsb接口, 以及实现它的方法:

public interface IUsb {    public void start();    public void pringMsg(String msg);    public String getName();}

并且定义一个实现它的类:

@Componentpublic class MyUsb implements IUsb{    @Override    public void start() {        System.out.println("MyUsb start...");    }    @Override    public void pringMsg(String msg) {        System.out.println(msg);    }    @Override    public String getName() {        return "手机";    }}

当然了, 我们也需要制作一个切面类, 因为现在是设计初期, 我们先设计一个死的, 后面再设计成活的. 先死后活.

public class UsbAspect {    public static void beforeTest() { // 前置通知        System.out.println("UsbAspect before...");    }    public static void afterTest() { // 后置通知        System.out.println("UsbAspect after...");    }}

那么接下来, 我们对我们之前的BeanPostProcessor进行动手.

@Componentpublic class HeihuBeanPostProcessor implements BeanPostProcessor {    public Object postProcessBeforeInitialization(Object bean, String beanName) {//        if (bean instanceof MonsterService) {//            System.out.println("这是 Monster Service");//        }        System.out.println("HeihuBeanPostProcessor postProcessBeforeInitialization... " + bean + " - " + beanName);        return bean;    }    public Object postProcessAfterInitialization(Object bean, String beanName) {        System.out.println("HeihuBeanPostProcessor postProcessAfterInitialization... " + bean + " - " + beanName);        // 先死后活        if ("myUsb".equals(beanName)) { // 假设被切面的对象是 myUsb            Object proxyInstance = Proxy.newProxyInstance(HeihuBeanPostProcessor.class.getClassLoader(),                    bean.getClass().getInterfaces(),                    new InvocationHandler() {                        @Override                        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {                            Object result = null;                            if ("start".equals(method.getName())) { // 假设要切面 start 方法                                UsbAspect.beforeTest();                                method.invoke(bean, args);                                UsbAspect.afterTest();                            } else {                                result = method.invoke(bean, args);                            }                            return result;                        }                    });            return proxyInstance;        }        return bean;    }}

注意我们这里主要是针对于postProcessAfterInitialization方法进行操作. 最终运行结果:

public class T2 {    public static void main(String[] args) {        HeihuSpringApplicationContext heihuSpringApplicationContext = new HeihuSpringApplicationContext(HeihuSpringConfig.class);        IUsb myUsb = (IUsb) heihuSpringApplicationContext.getBean("myUsb");        myUsb.start();        /*            UsbAspect before...            MyUsb start...            UsbAspect after...        */    }}

到现在已经有了一个基本的实现, 就没必要继续往下深究了, 理解到AOP的机制即可.

JDBCTemplate

在这里我们的手撕Spring已经完毕了, 接下来还是Spring中的内容.

在之前, 我们使用的是JDBCByDruid类, 是我们自己开发的, 而Spring提供了一个JDBCTemplate, 也是用于操作数据库的. 准备如下pom.xml:

<dependencies>    <!-- c3p0 -->    <dependency>        <groupId>c3p0</groupId>        <artifactId>c3p0</artifactId>        <version>0.9.1.2</version>    </dependency>    <!-- MySQL Connector/J -->    <dependency>        <groupId>mysql</groupId>        <artifactId>mysql-connector-java</artifactId>        <version>8.0.25</version>    </dependency>    <!-- Spring Core -->    <dependency>        <groupId>org.springframework</groupId>        <artifactId>spring-core</artifactId>        <version>5.3.8</version>    </dependency>    <!-- Spring Expression Language (SpEL) -->    <dependency>        <groupId>org.springframework</groupId>        <artifactId>spring-expression</artifactId>        <version>5.3.8</version>    </dependency>    <!-- Spring JDBC -->    <dependency>        <groupId>org.springframework</groupId>        <artifactId>spring-jdbc</artifactId>        <version>5.3.8</version>    </dependency>    <!-- Spring ORM -->    <dependency>        <groupId>org.springframework</groupId>        <artifactId>spring-orm</artifactId>        <version>5.3.8</version>    </dependency>    <!-- Spring Transaction Management -->    <dependency>        <groupId>org.springframework</groupId>        <artifactId>spring-tx</artifactId>        <version>5.3.8</version>    </dependency>    <dependency> <!-- 加入 Spring 开发的基本包 -->        <groupId>org.springframework</groupId>        <artifactId>spring-context</artifactId>        <version>5.3.8</version>    </dependency>    <dependency> <!-- 加入 Spring 开发切面编程需要的包 -->        <groupId>org.springframework</groupId>        <artifactId>spring-aspects</artifactId>        <version>5.3.8</version>    </dependency>    <dependency> <!-- 引入 Junit 单元测试 -->        <groupId>junit</groupId>        <artifactId>junit</artifactId>        <version>4.7</version>        <scope>compile</scope>    </dependency></dependencies>

那么我们准备如下表:

CREATE DATABASE `spring`;use `spring`;CREATE TABLE `monster`(    id int primary key,    `name` varchar(64) not null default '',    skill varchar(64) not null default '')charset=utf8;INSERT INTO `monster` VALUES(100, '黄牛怪', '吐火');INSERT INTO `monster` VALUES(200, '黄袍怪', '吐烟');INSERT INTO `monster` VALUES(300, '蜘蛛怪', '吐丝');

随后我们配置/jdbc.properties文件:

jdbc.userName=rootjdbc.password=rootjdbc.driverClass=com.mysql.cj.jdbc.Driverjdbc.url=jdbc:mysql://localhost:3306/spring

随后准备bean.xml, 内容如下:

<context:property-placeholder location="classpath:jdbc.properties"/> <!-- 引入 JDBC 配置文件 --><bean class="com.mchange.v2.c3p0.ComboPooledDataSource" id="dataSource">    <!-- 配置 c3p0 的数据源, 是属于 c3p0 开发的配置 -->    <property name="user" value="${jdbc.userName}"/>    <property name="password" value="${jdbc.password}"/>    <property name="driverClass" value="${jdbc.driverClass}"/>    <property name="jdbcUrl" value="${jdbc.url}"/></bean><bean class="org.springframework.jdbc.core.JdbcTemplate" id="jdbcTemplate">    <!-- 配置 jdbcTemplate, 是属于 spring 开发的配置对象, 给 jdbcTemplate 配置数据池 -->    <property name="dataSource" ref="dataSource"/></bean>

运行结果:

public class T {    private ApplicationContext ioc2 = new ClassPathXmlApplicationContext("jdbcTemplateIoc.xml");    @Test    public void t2() throws SQLException {        DataSource dataSource = (DataSource) ioc2.getBean("dataSource");        System.out.println(dataSource); // 链接成功信息        Connection connection = dataSource.getConnection();        System.out.println("连接信息: " + connection); //  com.mchange.v2.c3p0.impl.NewProxyConnection@3b2cf7ab    }}

添加数据 && 修改数据

public void t3() throws SQLException {    JdbcTemplate jdbcTemplate = (JdbcTemplate) ioc2.getBean("jdbcTemplate");    // System.out.println(jdbcTemplate); // org.springframework.jdbc.core.JdbcTemplate@143640d5    // 添加方式1: 无预处理    jdbcTemplate.execute("INSERT INTO `spring`.`monster` VALUES(400, '张三', '吃饭')"); // 无返回值    // 添加方式2: 预处理    int affected = jdbcTemplate.update("INSERT INTO `spring`.`monster` VALUES(?,?,?)", 500, "李四", "吃饭"); // 返回受影响的行数    System.out.println(affected); // 1}

批量处理

@Testpublic void t4() {    JdbcTemplate jdbcTemplate = ioc2.getBean(JdbcTemplate.class);    String sql = "INSERT INTO `monster` VALUES(?,?,?)"; // 准备 SQL 语句    List<Object[]> param_list = new ArrayList<>(); // 准备好预处理数组    param_list.add(new Object[]{600, "王五", "打架"}); // 第一组数据    param_list.add(new Object[]{700, "赵六", "挨打"}); // 第二组数据    int[] result = jdbcTemplate.batchUpdate(sql, param_list); // 批量执行    System.out.println(Arrays.toString(result)); // [1, 1] 执行成功}

数据查询

@Testpublic void t5() {    JdbcTemplate bean = ioc2.getBean(JdbcTemplate.class);    String sql = "SELECT * FROM `monster` WHERE id > ?";    BeanPropertyRowMapper<Monster> monsterBeanPropertyRowMapper = new BeanPropertyRowMapper<>(Monster.class);    // 准备好 Mapper 才可以进行绑定数据到 Bean 上    List<Monster> result = bean.query(sql, monsterBeanPropertyRowMapper, 200);    // SQL语句, Mapper, 绑定的参数    System.out.println(result);    /*        [Monster{id=300, name='蜘蛛怪', skill='吐丝'}, Monster{id=400, name='张三', skill='吃饭'}, Monster{id=500, name='李四', skill='吃饭'}, Monster{id=600, name='王五', skill='打架'}, Monster{id=700, name='赵六', skill='挨打'}]    */}

查询单行单列值

@Testpublic void t6() {    JdbcTemplate bean = ioc2.getBean(JdbcTemplate.class);    String sql = "SELECT name FROM `monster` WHERE id = ?";    String name = bean.queryForObject(sql, String.class, 100);    System.out.println(name); // 黄牛怪}

使用 MAP 传递具体参数完成操作

在这里我们需要在beans.xml文件中配置namedParameterJdbcTemplate, 例如:

<bean id="namedParameterJdbcTemplate" class="org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate">    <!-- 配置 namedParameterJdbcTemplate, 设置为 c3p0 的连接池 -->    <constructor-arg name="dataSource" ref="dataSource"/></bean>

配置好之后, 我们可以使用该JdbcTemplate进行插入数据.

@Testpublic void t7() {    NamedParameterJdbcTemplate namedParameterJdbcTemplate = ioc2.getBean(NamedParameterJdbcTemplate.class);    // 这次不用 JdbcTemplate, 而是 NamedParameterJdbcTemplate 需要注意    String sql = "INSERT INTO `monster` VALUES(:my_id, :name, :skill)";    HashMap<String, Object> map_parameter = new HashMap<>();    map_parameter.put("my_id", 1000); // 放置的KEY与占位符名称相同    map_parameter.put("name", "黑客"); // 放置的KEY与占位符名称相同    map_parameter.put("skill", "进局子"); // 放置的KEY与占位符名称相同    int result = namedParameterJdbcTemplate.update(sql, map_parameter);    System.out.println(result); // 1}
通过已有对象来操作数据库
@Testpublic void t8() {    NamedParameterJdbcTemplate namedParameterJdbcTemplate = ioc2.getBean(NamedParameterJdbcTemplate.class);    Monster monster = new Monster(900, "牛逼怪物", "大相机");    int update = namedParameterJdbcTemplate.update("INSERT INTO `monster` VALUES(:id,:name,:skill)",            // 这里要与monster类中的字段对应   new BeanPropertySqlParameterSource(monster)); // 传入我们准备的对象    System.out.println(update);}

DAO 中使用 JdbcTemplate

准备如下DAO:

@Repository // DAO 使用 Repositorypublic class MonsterDAO {    @Autowired // JdbcTemplate 使用 Autowired 自动装配    private JdbcTemplate jdbcTemplate;    public boolean save(Monster monster) {        String sql = "INSERT INTO `monster` VALUES(?,?,?)";        return jdbcTemplate.update(sql, monster.getId(), monster.getName(), monster.getSkill()) > 0;    }    public List<Monster> showMonsters() {        String sql = "SELECT * FROM `monster`";        List<Monster> monsters = jdbcTemplate.query(sql, new BeanPropertyRowMapper<>(Monster.class));        return monsters;    }}

准备如下xml文件:

<context:component-scan base-package="com.heihu577.JdbcTemplate"/> <!-- 扫描到我们的 monsterDAO --><context:property-placeholder location="classpath:jdbc.properties"/> <!-- 引入 JDBC 配置文件 --><bean class="com.mchange.v2.c3p0.ComboPooledDataSource" id="dataSource">    <!-- 配置 c3p0 的数据源, 是属于 c3p0 开发的配置 -->    <property name="user" value="${jdbc.userName}"/>    <property name="password" value="${jdbc.password}"/>    <property name="driverClass" value="${jdbc.driverClass}"/>    <property name="jdbcUrl" value="${jdbc.url}"/></bean><bean class="org.springframework.jdbc.core.JdbcTemplate" id="jdbcTemplate">    <!-- 配置 jdbcTemplate, 是属于 spring 开发的配置对象, 给 jdbcTemplate 配置数据池 -->    <property name="dataSource" ref="dataSource"/></bean><bean id="namedParameterJdbcTemplate" class="org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate">    <!-- 配置 namedParameterJdbcTemplate, 设置为 c3p0 的连接池 -->    <constructor-arg name="dataSource" ref="dataSource"/></bean>

测试结果:

@Testpublic void t9() {    MonsterDAO monsterDAO = ioc2.getBean(MonsterDAO.class);    List<Monster> monsters = monsterDAO.showMonsters();    System.out.println(monsters);    /*        [Monster{id=100, name='黄牛怪', skill='吐火'}, Monster{id=200, name='黄袍怪', skill='吐烟'}, Monster{id=300, name='蜘蛛怪', skill='吐丝'}, Monster{id=400, name='张三', skill='吃饭'}, Monster{id=500, name='李四', skill='吃饭'}, Monster{id=700, name='赵六', skill='挨打'}, Monster{id=600, name='王五', skill='打架'}, Monster{id=1000, name='黑客', skill='进局子'}, Monster{id=900, name='牛逼怪物', skill='大相机'}]    */}

Spring 声明式事务处理

基本使用

创建如下表:

CREATE TABLE `user_account`(    user_id INT UNSIGNED PRIMARY KEY AUTO_INCREMENT,    user_name VARCHAR(32) NOT NULL DEFAULT '',    money DOUBLE NOT NULL DEFAULT 0.0)CHARSET=utf8;INSERT INTO `user_account` VALUES(NULL,'张三', 1000);INSERT INTO `user_account` VALUES(NULL,'李四', 2000);CREATE TABLE `goods`(    goods_id INT UNSIGNED PRIMARY KEY AUTO_INCREMENT,    goods_name VARCHAR(32) NOT NULL DEFAULT '',    price DOUBLE NOT NULL DEFAULT 0.0)CHARSET=utf8 ;INSERT INTO `goods` VALUES(NULL,'小风扇', 10.00);INSERT INTO `goods` VALUES(NULL,'小台灯', 12.00);INSERT INTO `goods` VALUES(NULL,'可口可乐', 3.00);CREATE TABLE `goods_amount`(    goods_id INT UNSIGNED PRIMARY KEY AUTO_INCREMENT,    goods_num INT UNSIGNED DEFAULT 0)CHARSET=utf8 ;INSERT INTO `goods_amount` VALUES(1,200);INSERT INTO `goods_amount` VALUES(2,20);INSERT INTO `goods_amount` VALUES(3,15);

并且准备如下DAO, 专门针对于Goods表进行处理:

@Repositorypublic class GoodsDAO {    @Autowired    private JdbcTemplate jdbcTemplate;    public Float queryPriceById(Integer id) { // 根据ID查询出商品价格        String sql = "SELECT price From goods Where goods_id=?";        Float price = jdbcTemplate.queryForObject(sql, Float.class, id);        return price;    }    public void updateBalance(Integer user_id, Float money) { // 减少用户余额        String sql = "UPDATE user_account SET money=money-? Where user_id=?";        jdbcTemplate.update(sql, money, user_id);    }    public void updateAmount(Integer goods_id, int amount) { // 修改商品库存        String sql = "UPDATE goods_amount SET goods_num=goods_num-? Where goods_id=?";        jdbcTemplate.update(sql, amount, goods_id);    }}

并且准备如下配置:

<context:property-placeholder location="classpath:jdbc.properties"/> <!-- 加载配置文件, 得用户名, 密码等信息 --><context:component-scan base-package="com.heihu577.JdbcTemplate02"/> <!-- 将 GoodsDAO 等 Spring Bean 扫描到 --><bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource"> <!-- 配置数据库连接池 -->    <property name="user" value="${jdbc.userName}"/>    <property name="password" value="${jdbc.password}"/>    <property name="driverClass" value="${jdbc.driverClass}"/>    <property name="jdbcUrl" value="${jdbc.url}"/></bean><bean class="org.springframework.jdbc.core.JdbcTemplate" id="jdbcTemplate">    <!-- 配置 jdbcTemplate, 是属于 spring 开发的配置对象, 给 jdbcTemplate 配置数据池 -->    <property name="dataSource" ref="dataSource"/></bean>

测试数据:

public class TestGoodsDAO {    private ClassPathXmlApplicationContext ioc = new ClassPathXmlApplicationContext("JdbcTemplateIoc02.xml");    private GoodsDAO goodsDAO = ioc.getBean(GoodsDAO.class);    @Test    public void testQueryPriceById() { // 测试查询根据商品ID得到价格信息        /*        *   +----------+--------------+-------+            | goods_id | goods_name   | price |            +----------+--------------+-------+            |        1 | 小风扇       |    10 |            |        2 | 小台灯       |    12 |            |        3 | 可口可乐     |     3 |            +----------+--------------+-------+        * */        Float price = goodsDAO.queryPriceById(1);        System.out.println("ID 为 1 的商品价格: " + price); // ID 为 1 的商品价格: 10.0    }    @Test    public void testUpdateAmount() { // 测试减少商品数量        /*        * 原本:        *   +----------+-----------+            | goods_id | goods_num |            +----------+-----------+            |        1 |       200 |            |        2 |        20 |            |        3 |        15 |            +----------+-----------+        * */        goodsDAO.updateAmount(1, 10); // goods_id 为 1 的商品数量减少10个        /*        * 执行结果后        *   +----------+-----------+            | goods_id | goods_num |            +----------+-----------+            |        1 |       190 |            |        2 |        20 |            |        3 |        15 |            +----------+-----------+        * */    }    @Test    public void testUpdateBalance() {        /* 原本:        *   +---------+-----------+-------+            | user_id | user_name | money |            +---------+-----------+-------+            |       1 | 张三      |  1000 |            |       2 | 李四      |  2000 |            +---------+-----------+-------+        * */        goodsDAO.updateBalance(1, 10.0f); // user_id 为 1 的 money 字段减少 10        /* 现在:        *   +---------+-----------+-------+            | user_id | user_name | money |            +---------+-----------+-------+            |       1 | 张三      |   990 |            |       2 | 李四      |  2000 |            +---------+-----------+-------+        * */    }}

创建Service:

@Servicepublic class GoodsService {    @Resource    private GoodsDAO goodsDAO;    public void buyGood(int userId, int goods_id, int amount) { // 用户ID 商品ID 购买数量        System.out.println("用户ID号为 " + userId + " 购买了商品ID为 " + goods_id + " 数量为 " + amount);        Float price = goodsDAO.queryPriceById(userId); // 得到当前用户余额        goodsDAO.updateBalance(userId, price * amount); // 用户的余额 - 商品单价 * 购买数量        goodsDAO.updateAmount(goods_id, amount); // 商品减少库存量        System.out.println("用户购买成功~");    }}

测试结果:

public class TestGoodsDAO {    private ClassPathXmlApplicationContext ioc = new ClassPathXmlApplicationContext("JdbcTemplateIoc02.xml");    private GoodsDAO goodsDAO = ioc.getBean(GoodsDAO.class);    private GoodsService goodsService = ioc.getBean(GoodsService.class);    @Test    public void testBuyGoods() {        /*        * mysql> SELECT * FROM goods;                +----------+--------------+-------+                | goods_id | goods_name   | price |                +----------+--------------+-------+                |        1 | 小风扇       |    10 |                |        2 | 小台灯       |    12 |                |        3 | 可口可乐     |     3 |                +----------+--------------+-------+                3 rows in set (0.00 sec)                mysql> SELECT * FROM goods_amount;                +----------+-----------+                | goods_id | goods_num |                +----------+-----------+                |        1 |       190 |                |        2 |        20 |                |        3 |        15 |                +----------+-----------+                3 rows in set (0.00 sec)                mysql> SELECT * FROM user_account;                +---------+-----------+-------+                | user_id | user_name | money |                +---------+-----------+-------+                |       1 | 张三      |   990 |                |       2 | 李四      |  2000 |                +---------+-----------+-------+                2 rows in set (0.00 sec)        * */        goodsService.buyGood(2, 2, 2); // 用户ID号为 2 购买了商品ID为 2 数量为 2        /*        * mysql> SELECT * FROM goods;                +----------+--------------+-------+                | goods_id | goods_name   | price |                +----------+--------------+-------+                |        1 | 小风扇       |    10 |                |        2 | 小台灯       |    12 |                |        3 | 可口可乐     |     3 |                +----------+--------------+-------+                3 rows in set (0.00 sec)                mysql> SELECT * FROM goods_amount;                +----------+-----------+                | goods_id | goods_num |                +----------+-----------+                |        1 |       190 |                |        2 |        18 |                |        3 |        15 |                +----------+-----------+                3 rows in set (0.00 sec)                mysql> SELECT * FROM user_account;                +---------+-----------+-------+                | user_id | user_name | money |                +---------+-----------+-------+                |       1 | 张三      |   990 |                |       2 | 李四      |  1976 |                +---------+-----------+-------+                2 rows in set (0.00 sec)        * */    }}

但是当前的情况并没有增加事务控制, 也就是提交|回滚, 当其中存在一条SQL语句出错时, 那么数据将错乱, 下面我们看一下Spring声明式事务处理的应用.

声明式事务应用 [Mysql - Innodb]

定义Service中操作DAO的方法如下:

@Servicepublic class GoodsService {    @Resource    private GoodsDAO goodsDAO;    @Transactional // 加入想要一并执行的注解    public void buyGoodByTx(int userId, int goods_id, int amount) { // 用户ID 商品ID 购买数量        System.out.println("用户ID号为 " + userId + " 购买了商品ID为 " + goods_id + " 数量为 " + amount);        Float price = goodsDAO.queryPriceById(userId); // 得到当前用户余额        goodsDAO.updateBalance(userId, price * amount); // 用户的余额 - 商品单价 * 购买数量        goodsDAO.updateAmount(goods_id, amount); // 商品减少库存量        System.out.println("用户购买成功~");    }}

但是此时@Transactional注解还没有生效, 定义如下xml文件, 让其生效:

<bean class="org.springframework.jdbc.datasource.DataSourceTransactionManager" id="dataSourceTransactionManager">    <!-- 必须配置该bean, 否则事务不生效 -->    <property name="dataSource" ref="dataSource"/></bean><tx:annotation-driven transaction-manager="dataSourceTransactionManager"/> <!-- 并且配置启用标签, 类似于AOP中的aop标签 -->

DAO中的方法如下:

@Repositorypublic class GoodsDAO {    @Autowired    private JdbcTemplate jdbcTemplate;    public Float queryPriceById(Integer id) { // 根据ID查询出商品价格        String sql = "SELECT price From goods Where goods_id=?";        Float price = jdbcTemplate.queryForObject(sql, Float.class, id);        return price;    }    public void updateBalance(Integer user_id, Float money) { // 减少用户余额        String sql = "UPDATE user_account SET money=money-? Where user_id=?";        jdbcTemplate.update(sql, money, user_id);    }    public void updateAmount(Integer goods_id, int amount) { // 修改商品库存        String sql = "UPDATEX goods_amount SET goods_num=goods_num-? Where goods_id=?"; // 这里故意搞错        jdbcTemplate.update(sql, amount, goods_id);    }}

执行完毕后, 数据不会被影响.

@Transaction, 底层用的是AOP, 是在执行我们Service中定义的方法前增加了前置通知, 其中调用了DataSourceTransactionManager类中的doBegin方法, 如图:开发基础 | Spring 基本使用总结 && 手动实现 Spring当代码没出错, 那么我们调用doCommit方法, 如图:开发基础 | Spring 基本使用总结 && 手动实现 Spring反之, 则调用doRollback方法:开发基础 | Spring 基本使用总结 && 手动实现 Spring

事务传播机制

REQUIRED (默认)

开发基础 | Spring 基本使用总结 && 手动实现 Spring当然, 我们也可以使用@Transactional注解, 显式声明事务:

@Transactional(propagation=Propagation.REQUIRED)

这也是Spring默认使用的传播机制, 我们可以创建如下测试环境: DAO重新复制一份方法, 如:

@Repositorypublic class GoodsDAO {    @Autowired    private JdbcTemplate jdbcTemplate;    public Float queryPriceById(Integer id) { // 根据ID查询出商品价格        String sql = "SELECT price From goods Where goods_id=?";        Float price = jdbcTemplate.queryForObject(sql, Float.class, id);        return price;    }    public void updateBalance(Integer user_id, Float money) { // 减少用户余额        String sql = "UPDATE user_account SET money=money-? Where user_id=?";        jdbcTemplate.update(sql, money, user_id);    }    public void updateAmount(Integer goods_id, int amount) { // 修改商品库存        String sql = "UPDATE goods_amount SET goods_num=goods_num-? Where goods_id=?";        jdbcTemplate.update(sql, amount, goods_id);    }    public Float queryPriceById2(Integer id) { // 根据ID查询出商品价格        String sql = "SELECT price From goods Where goods_id=?";        Float price = jdbcTemplate.queryForObject(sql, Float.class, id);        return price;    }    public void updateBalance2(Integer user_id, Float money) { // 减少用户余额        String sql = "UPDATE user_account SET money=money-? Where user_id=?";        jdbcTemplate.update(sql, money, user_id);    }    public void updateAmount2(Integer goods_id, int amount) { // 修改商品库存        String sql = "UPDATEX goods_amount SET goods_num=goods_num-? Where goods_id=?"; // 这里放置一个错误的SQL语句        jdbcTemplate.update(sql, amount, goods_id);    }}

在原有updateBalance|updateBalance|queryPriceById方法上, 复制粘贴出updateBalance2|updateBalance2|queryPriceById2, 其中updateAmount2方法中我们故意放置一个错误的SQL语句, 随后我们准备GoodsService, 如下:

@Servicepublic class GoodsService {    @Resource    private GoodsDAO goodsDAO;    public void buyGood(int userId, int goods_id, int amount) { // 用户ID 商品ID 购买数量        System.out.println("用户ID号为 " + userId + " 购买了商品ID为 " + goods_id + " 数量为 " + amount);        Float price = goodsDAO.queryPriceById(userId); // 得到当前用户余额        goodsDAO.updateBalance(userId, price * amount); // 用户的余额 - 商品单价 * 购买数量        goodsDAO.updateAmount(goods_id, amount); // 商品减少库存量        System.out.println("用户购买成功~");    }    @Transactional    public void buyGoodByTx(int userId, int goods_id, int amount) { // 用户ID 商品ID 购买数量        System.out.println("用户ID号为 " + userId + " 购买了商品ID为 " + goods_id + " 数量为 " + amount);        Float price = goodsDAO.queryPriceById(userId); // 得到当前用户余额        goodsDAO.updateBalance(userId, price * amount); // 用户的余额 - 商品单价 * 购买数量        goodsDAO.updateAmount(goods_id, amount); // 商品减少库存量        System.out.println("用户购买成功~");    }    @Transactional    public void buyGoodByTx2(int userId, int goods_id, int amount) {        System.out.println("用户ID号为 " + userId + " 购买了商品ID为 " + goods_id + " 数量为 " + amount);        Float price = goodsDAO.queryPriceById2(userId); // 得到当前用户余额        goodsDAO.updateBalance2(userId, price * amount); // 用户的余额 - 商品单价 * 购买数量        goodsDAO.updateAmount2(goods_id, amount); // 商品减少库存量        System.out.println("用户购买成功~");    }}

其中包含了buyGoodByTx以及buyGoodByTx2方法, 通过代码我们可以知道的是, buyGoodByTx是肯定不会报错的, SQL语句等都是正确的, 而buyGoodByTx2会报错, 其中包含了肯定会报错的updateAmount2方法. 那么我们准备一个类, 来并行执行GoodsService::buyGoodByTx以及GoodsService::buyGoodByTx2, 并且使用@Transactional注解进行修饰, 如下:

@Componentpublic class MultiplyService {    @Autowired    private GoodsService goodsService;    @Transactional    public void multiBuyGoodsTx() {        goodsService.buyGoodByTx(1, 1, 1); // 正常运行        goodsService.buyGoodByTx2(2, 1, 2); // 报错, 运行不了    }}

测试类:

@Testpublic void t10() {    MultiplyService multiplyService = ioc2.getBean(MultiplyService.class);    multiplyService.multiBuyGoodsTx(); // JAVA控制台显示出错, 但 Mysql 的数据并没被 buyGoodByTx 影响, 也就是, multiBuyGoodsTx 若出现错误, 那么该方法中的所有方法统一进行回滚.}

REQUIRED_NEW

开发基础 | Spring 基本使用总结 && 手动实现 Spring在上面的案例我们可以看出来, required模式是一个巨大的事务, 将其中所有的事务统一为一个事务, 要成功都commit, 要失败都rollback. 那么接下来我们要观察的是REQUIRED_NEW, 我们对GoodsService中已经被@Transacitonal所修饰的注解进行修改为如下:

@Transactional(propagation = Propagation.REQUIRES_NEW)public void buyGoodByTx(int userId, int goods_id, int amount) { // 用户ID 商品ID 购买数量    System.out.println("用户ID号为 " + userId + " 购买了商品ID为 " + goods_id + " 数量为 " + amount);    Float price = goodsDAO.queryPriceById(userId); // 得到当前用户余额    goodsDAO.updateBalance(userId, price * amount); // 用户的余额 - 商品单价 * 购买数量    goodsDAO.updateAmount(goods_id, amount); // 商品减少库存量    System.out.println("用户购买成功~");}@Transactional(propagation = Propagation.REQUIRES_NEW)public void buyGoodByTx2(int userId, int goods_id, int amount) {    System.out.println("用户ID号为 " + userId + " 购买了商品ID为 " + goods_id + " 数量为 " + amount);    Float price = goodsDAO.queryPriceById2(userId); // 得到当前用户余额    goodsDAO.updateBalance2(userId, price * amount); // 用户的余额 - 商品单价 * 购买数量    goodsDAO.updateAmount2(goods_id, amount); // 商品减少库存量    System.out.println("用户购买成功~");}

增加了传播机制的设置后, 我们再进行测试, 会buyGoodByTx执行成功, 而buyGoodByTx2会执行失败. 当然了, 其他的还有事务的隔离级别, 超时回滚, 在这里就不做过多演示了.

原文始发于微信公众号(Heihu Share):开发基础 | Spring 基本使用总结 && 手动实现 Spring

免责声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由读者承担全部法律及连带责任,本站不承担任何法律及连带责任;如有问题可邮件联系(建议使用企业邮箱或有效邮箱,避免邮件被拦截,联系方式见首页),望知悉。
  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2024年7月15日15:05:07
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   开发基础 | Spring 基本使用总结 && 手动实现 Springhttps://cn-sec.com/archives/2923414.html
                  免责声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由读者承担全部法律及连带责任,本站不承担任何法律及连带责任;如有问题可邮件联系(建议使用企业邮箱或有效邮箱,避免邮件被拦截,联系方式见首页),望知悉.

发表评论

匿名网友 填写信息