当前位置: 首页 > article >正文

后端:Spring(IOC、AOP)

文章目录

  • 1. Spring
  • 2. IOC 控制反转
    • 2-1. 通过配置文件定义Bean
      • 2-1-1. 通过set方法来注入Bean
      • 2-1-2. 通过构造方法来注入Bean
      • 2-1-3. 自动装配
      • 2-1-4. 集合注入
      • 2-1-5. 数据源对象管理(第三方Bean)
      • 2-1-6. 在xml配置文件中加载properties文件的数据(context命名空间)
      • 2-1-7. 加载容器的其他方式
      • 2-1-8. p命名空间
      • 2-1-9. c命名空间
      • 2-1-10. util命名空间
    • 2-2. 通过注解加载Bean
      • 2-2-1. 选择性实例化Bean
      • 2-2-2. 纯注解加载Bean
      • 2-2-3. 自动装配
      • 2-2-4. 简单类型的注入
      • 2-2-5. 管理第三方Bean
    • 2-3. Bean的实例化方式
      • 2-3-1. 通过构造方法来实例化Bean
      • 2-3-2. 通过简单工厂模式实例化
      • 2-3-3. 通过工厂方法模式来实例化
      • 2-3-4. 通过FactoryBean接口实例化
      • 2-3-5. BeanFactory 和 FactoryBean 的区别
      • 2-3-6. FactoryBean 的应用(以自定义Date类型举例)
    • 2-4. Bean的生命周期(5步)
      • 2-4-1. Bean 的生命周期之7步
      • 2-4-2. Bean 的生命周期之10步
      • 2-4-3. Bean的作用域
      • 2-4-4. 自己实例化的对象让spring容器去管理
    • 2-5. Bean 循环依赖问题
      • 2-5-1. set注入 + 单例模式之循环依赖
      • 2-5-2. 构造器注入 + 单例模式之循环依赖
      • 2-5-3. Spring 解决循环依赖的机理
    • 2-6. 自定义 spring 框架
  • 3. Spring之JdbcTemplate
  • 4. Spring 代理模式
    • 4-1. jdk 之动态代理
    • 4-2. cglib 之动态代理
    • 4-3. jdk 与 cglib 动态代理的区别
  • 5. 面向切面编程 AOP
  • 6. Spring 事务
    • 6-1. 事务的传播特性
    • 6-2. 事务的隔离级别
    • 6-3. 事务的超时时间
    • 6-4. 设置事务只读(readOnly)

1. Spring

主要包括两种功能,分别为IOC(Inverse Of Control,意为着控制反转,用于反转创建Bean的控制权。通过使用ioc,可以降低代码的耦合度,耦合度指的是类与类之间的依赖关系,如果耦合度高表明类与类之间的依赖关系越紧密,此时如果修改其中的一些代码,可能会造成其他类出错的情况,对于后期的维护及其不便。使用ioc容器管理的Bean,推荐使用实现接口的实现类)、AOP(Aspect Oriented Programming,意为面向切面编程)。

2. IOC 控制反转

IOC意为控制反转,也就是反转创建Bean的控制权。在这之前我们需要调用一个类下的某个方法时,通常做法是首先对这个类进行实例化,然后再调用其实例化对象的方法。通过IOC,把这个创建类交给一个容器去管理,我们需要用到时,只需要从容器中去拿即可。当然前提是我们需要定义配置文件,当然,随着版本的迭代,后期发展到我们只需要添加一些注解即可。

2-1. 通过配置文件定义Bean

这个前提是需要有spring-context的依赖(我的这个是SpringBoot项目哈),导入这个依赖之后,在resources这个目录下鼠标右键,找到新建xml配置文件就有对应的配置文件格式了。
在这里插入图片描述
具体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="userDao" class="com.lz.demo_spring_24_1.dao.UserDao"/>

</beans>

我在这个配置文件里边定义了一个Bean,并且这个Bean的名字为userDao,此时我们使用java代码就可以从这个容器中去获取这个Bean了。当然首先需要先加载到这个配置文件,这里使用 ClassPathXmlApplicationContext去加载,加载完之后可以得到一个ioc容器对象,此时,只需要通过这个ioc容器对象通过getBean即可获取对应的Bean对象。

package com.lz.demo_spring_24_1;

import com.lz.demo_spring_24_1.dao.UserDao;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.context.support.ClassPathXmlApplicationContext;

@SpringBootTest(classes = Test2.class)
public class Test2 {

    @Test
    public void test1(){
        ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("spring-config.xml");

        UserDao userDao = (UserDao) ctx.getBean("userDao");

        System.out.println(userDao);
    }
}

2-1-1. 通过set方法来注入Bean

在一个类下如果需要引入另外一个类的方法,前提是需要对这个类实例化。如果使用ioc,添加set方法即可(还有其他)。

package com.lz.demo_spring_24_1.service;

import com.lz.demo_spring_24_1.dao.UserDao;
public class UserService {
    private UserDao ud;
    public void setUd(UserDao ud) {
        this.ud = ud;
    }
    public void print(){
        System.out.println(ud);
    }
}

对应的配置文件中需要做的配置如下:

<?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="userDao" class="com.lz.demo_spring_24_1.dao.UserDao"/>

    <bean id="userService" class="com.lz.demo_spring_24_1.service.UserService">
        <property name="ud" ref="userDao"/>
    </bean>
</beans>

测试代码如下:

package com.lz.demo_spring_24_1;


import com.lz.demo_spring_24_1.service.UserService;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.context.support.ClassPathXmlApplicationContext;

@SpringBootTest(classes = Test2.class)
public class Test2 {

    @Test
    public void test1(){
        ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("spring-config.xml");
        UserService us = (UserService) ctx.getBean("userService");
        System.out.println(us);
        us.print();
    }
}

注意:需要提醒一下是,set注入的配置文件Bean的property属性name值是根据对应类的set方法名来的,而不是根据对应Bean的变量名。
在这里插入图片描述
在这里插入图片描述
上述是错误写法,其中name属性值应该修改为abcSdi才对。
另外,关于xml配置文件中的其他一些属性。
name属性用于给这个Bean起别名,多个别名之间用逗号隔开。
在这里插入图片描述
scope属性用于设置对应的Bean是单例,还是原型,默认情况下是单例的
在这里插入图片描述
可以修改为原型的。对于一个简单类型,想在配置文件中注入值,只需要设置其value属性即可。
在这里插入图片描述
如果想了解哪些类型是简单类型,可以去BeanUtils类下找到isSimpleValueType方法,查看对应的源码就可以知道。
在这里插入图片描述
常见的简单类型有八种基本数据类型、字符串、枚举类型、Class类型、日期类型(不过日期类型在配置文件需要写入特定的格式才支持,因此通常情况下会把日期类型当作是简单类型来注入)。

2-1-2. 通过构造方法来注入Bean

需要添加对应的构造方法即可,然后在配置文件中添加对应的构造参数即可。

package com.lz.demo_spring_24_1.service;


import com.lz.demo_spring_24_1.dao.UserDao;

public class UserService {

    private UserDao ud;
    private Integer val;

    public void setVal(Integer val) {
        this.val = val;
    }
    public void setUd(UserDao ud) {
        this.ud = ud;
    }

    public UserService(){}
    public UserService(UserDao ud){
        this.ud = ud;
    }
    public UserService(UserDao ud,Integer val){
        this.ud = ud;
        this.val = val;
    }

    public void print(){

        System.out.println(ud+" "+val);
    }
}
<?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="userDao" class="com.lz.demo_spring_24_1.dao.UserDao"/>

    <bean id="userService" class="com.lz.demo_spring_24_1.service.UserService">
        <constructor-arg name="ud" ref="userDao"/>
        <constructor-arg name="val" value="1000"/>
    </bean>

</beans>

上述这样配置存在一个问题,那就是耦合度比较高(因为这是通过构造方法的变量名来进行注入的)比如如果我在类文件里边修改ud为ud1,那么此时就需要在配置文件中做对应的修改。此时可以通过设置类型type属性从而解决这个耦合度问题,如下:
在这里插入图片描述
但是如果构造器方法中存在很多相同类型,上述解决办法就不行了,此时可以通过设置构造器方法中的参数位置index属性来解决。
在这里插入图片描述

2-1-3. 自动装配

在xml配置文件中的配置autowire属性即可,本质依旧是set注入,因此在类文件下依旧需要添加对应的set方法,如下:

<?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="userDao" class="com.lz.demo_spring_24_1.dao.UserDao"/>
    <bean id="userService" class="com.lz.demo_spring_24_1.service.UserService" autowire="byType"/>
</beans>

属性autowire的值也可以修改为byName,此时根据的是通过Bean的名字进行注入,因此在配置文件中要注入的Bean的id值不能随便取,这里需要额外注意一下。上述是根据Bean的类型进行注入的,只不过在xml配置文件中要注入的Bean只能为一个,否则会报错,因为它不知道到底需要注入哪一个。
对应的java类如下:

package com.lz.demo_spring_24_1.service;


import com.lz.demo_spring_24_1.dao.UserDao;
public class UserService {
    private UserDao ud;
    public void setUd(UserDao ud) {
        this.ud = ud;
    }
    public void print(){
        System.out.println(ud+" ");
    }
}

既然是自动装配,那么装配的那个Bean肯定是需要在配置文件中进行定义的。
在这里插入图片描述

2-1-4. 集合注入

对应java类参考代码如下:

package com.lz.demo_spring_24_1.other;


import java.util.*;

public class Datas {

    private int[] arr1;
    private List<String> list1;
    private Set<String> set;
    private Map<String,Object> map;
    private Properties properties;

    public void setArr1(int[] arr1) {
        this.arr1 = arr1;
    }

    public void setList1(List<String> list1) {
        this.list1 = list1;
    }

    public void setSet(Set<String> set) {
        this.set = set;
    }

    public void setMap(Map<String, Object> map) {
        this.map = map;
    }

    public void setProperties(Properties properties) {
        this.properties = properties;
    }

    @Override
    public String toString() {
        return "Datas{" +
                "arr1=" + Arrays.toString(arr1) +
                ", list1=" + list1 +
                ", set=" + set +
                ", map=" + map +
                ", properties=" + properties +
                '}';
    }
}

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="datas" class="com.lz.demo_spring_24_1.other.Datas">
        <property name="arr1">
            <array>
                <value>123</value>
                <value>456</value>
            </array>
        </property>
        <property name="list1">
            <list>
                <value>123</value>
                <value>234</value>
            </list>
        </property>
        <property name="set">
            <set>
                <value>123</value>
                <value>234</value>
            </set>
        </property>
        <property name="map">
            <map>
                <entry key="country" value="china"/>
                <entry key="age" value="100"/>
            </map>
        </property>
        <property name="properties">
            <props>
                <prop key="country">china</prop>
                <prop key="age">100</prop>
            </props>
        </property>
    </bean>
</beans>

如果在数组、List、Set中注入非简单类型,只需要把value标签修改为ref标签,且在ref标签的bean属性中写入对应的Bean的名称即可。
在这里插入图片描述
对于map数据类型,如果key、value值是非简单类型,直接使用key-ref、value-ref即可。
在这里插入图片描述

2-1-5. 数据源对象管理(第三方Bean)

这里以druid数据源为例,需要导入druid的依赖。

<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>druid</artifactId>
    <version>1.1.16</version>
</dependency>

在对应的xml配置文件中定义的Bean如下(这里使用的是set注入,由于其并没有对应设置对应配置的构造方法,所以使用set注入,而不使用构造器注入):

<?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="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
        <property name="driverClassName" value="com.mysql.jdbc.Driver"/>
        <property name="url" value="jdbc:mysql://localhost:3306/mytest1"/>
        <property name="username" value="root"/>
        <property name="password" value="root"/>
    </bean>

</beans>

运行结果:
在这里插入图片描述

2-1-6. 在xml配置文件中加载properties文件的数据(context命名空间)

前提是需要在xml中开启context命名空间,如下:

<?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
                http://www.springframework.org/schema/context/spring-context.xsd
            ">
</beans>

之后需要在xml配置文件中加载properties文件,最后修改其中Bean的一些配置即可。propertis文件配置如下:

spring.application.name=demo_spring_24_1

jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/mytest1
jdbc.username=root
jdbc.password=root

此时在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
                http://www.springframework.org/schema/context/spring-context.xsd
            ">

    <context:property-placeholder location="application.properties"/>
<!--    使用context空间加载properties文件-->
    <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
        <property name="driverClassName" value="${jdbc.driver}"/>
        <property name="url" value="${jdbc.url}"/>
        <property name="username" value="${jdbc.username}"/>
        <property name="password" value="${jdbc.password}"/>
    </bean>
</beans>

如果想要加载多个properties文件,可以在location属性中用逗号隔开,写上其他properties文件即可。

<context:property-placeholder location="application.properties,application2.properties"/>

上述这种写法并不怎么规范,规范写法应该是这样,如下:

<context:property-placeholder location="classpath:*.properties"/>

如果想不加载系统属性,可以在上面context的属性system-properties-mode设置为NEVER即可。

<context:property-placeholder location="application.properties" system-properties-mode="NEVER"/>

2-1-7. 加载容器的其他方式

上述方式都是通过加载类路径下的配置文件来进行的,其实还可以通过加载绝对路径来进行。
在这里插入图片描述

2-1-8. p命名空间

这种方式本质上是set注入,因此依旧需要在对应的类文件中添加set方法,但是在配置文件中可以简化一些操作而已。(是set注入的一种简化而已)首先,需要对配置文件中添加一点配置,参考如下:

<?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:p="http://www.springframework.org/schema/p"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">

</beans>

在这里插入图片描述

package com.lz.demo_spring_24_1.entity;


import java.util.Date;

public class User {

    private String name;
    private Date birthDay;

    public void setName(String name) {
        this.name = name;
    }

    public void setBirthDay(Date birthDay) {
        this.birthDay = birthDay;
    }

    @Override
    public String toString() {
        return "User{" +
                "name='" + name + '\'' +
                ", birthDay=" + birthDay +
                '}';
    }
}

在这里插入图片描述
只需要在配置文件以p:变量名后添加对应的值即可,因为变量birthDay是日期类型,可以使用简单类型,也可以使用非简单类型。
在这里插入图片描述
运行结果:
在这里插入图片描述

2-1-9. c命名空间

本质上是构造方法注入,因此需要添加对应的构造方法。(只是构造器注入的一种简化而已)。和p命名空间一样,都需要对xml配置文件添加一些配置,用以开启c命名空间。

<?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:c="http://www.springframework.org/schema/c"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">

    <bean id="date" class="java.util.Date"/>
    <bean id="user" class="com.lz.demo_spring_24_1.entity.User" c:_0="张三" c:birthDay-ref="date"/>

</beans>

有两种方式可以进行注入,一种是通过参数的位置,另外一种是通过参数名,参考如上,运行结果和p命名空间一样。

2-1-10. util命名空间

和上面两种命名空间一样,需要在xml配置文件中添加util命名空间的配置。配置如下:

<?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:util="http://www.springframework.org/schema/util"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
                           http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util.xsd
        ">
</beans>

现在假设我想要连接数据库,但是可以使用连接池可以用druid、c3p0,它们都需要添加一些连接数据库的配置。
在这里插入图片描述
在这里插入图片描述

并且连接的配置信息都相同,此时可以采用util命名空间(实现配置复用而已),如下:

<?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:util="http://www.springframework.org/schema/util"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
                           http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util.xsd
        ">


    <util:properties id="pro">
        <prop key="driver">com.mysql.cj.jdbc.driver</prop>
        <prop key="url">jdbc:mysql://localhost:3306/mytest1</prop>
        <prop key="username">root</prop>
        <prop key="password">root</prop>
    </util:properties>

    <bean id="ds1" class="com.lz.demo_spring_24_1.entity.MyDataSource1">
        <property name="properties" ref="pro"/>
    </bean>

    <bean id="ds2" class="com.lz.demo_spring_24_1.entity.MyDataSource2">
        <property name="properties" ref="pro"/>
    </bean>
</beans>

运行结果如下:
在这里插入图片描述

2-2. 通过注解加载Bean

这种方式最初的版本依旧需要写xml配置文件,同时需要在对应的类上加上注解@Component,如下:

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


    <context:component-scan base-package="com.lz.demo_spring_24_1.dao"/>

</beans>

只要是在com.lz.demo_spring_24_1.dao这个目录下所有的添加了@Component注解的类都会被容器进行管理。如果需要添加其他包下Bean被spring容器进行管理,可以在上述配置文件的包后用逗号隔开,之后再添加其他包路径即可。当然也可以写两个包的父包即可。

package com.lz.demo_spring_24_1.dao;


import org.springframework.stereotype.Component;

@Component
public class UserDao {
}

从@Component又衍生出其他三种注解,分别为@Repository(数据层)、@Service(业务层)、@Controller(表现层)。它们的功能都相同,只是为了便于分辨而已。

2-2-1. 选择性实例化Bean

对于一个包下所定义的Bean(添加了对应注解的),如何在xml配置文件中选择性去选择哪些Bean可以被实例化。比如现在我定义了两个Bean,其中一个为a,另一个为b,a上添加了注解@Service,b上添加了注解@Controller,现在使用context命名空间扫描这两个bean的父包。然后进行过滤,只把有注解@Service Bean a添加到spring容器中进行管理,参考代码如下:

package com.lz.demo_spring_24_1.beans;

import org.springframework.stereotype.Controller;
import org.springframework.stereotype.Service;


@Service
public class A {
    public A(){
        System.out.println("A........");
    }
}

@Controller
class B {
    public B(){
        System.out.println("B........");
    }
}

xml配置文件写法1

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

    <context:component-scan base-package="com.lz.demo_spring_24_1.beans" use-default-filters="true">
        <context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
    </context:component-scan>



</beans>

xml配置文件写法2

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

    <context:component-scan base-package="com.lz.demo_spring_24_1.beans" use-default-filters="false">
        <context:include-filter type="annotation" expression="org.springframework.stereotype.Service"/>
    </context:component-scan>



</beans>

运行结果如下:
在这里插入图片描述

2-2-2. 纯注解加载Bean

这种模式不需要编写xml配置文件,在上述代码不变的基础上,新建一个配置类,当然需要添加@Configuration注解。写这个配置类相当于是代替上面那个xml配置文件。

package com.lz.demo_spring_24_1.config;


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

@Configuration
@ComponentScan(value = "com.lz.demo_spring_24_1.dao")
public class SpringConfig {
}

之前是加载那个配置文件xml,此时如果想要运行成功,需要加载这个配置类。

package com.lz.demo_spring_24_1;


import com.lz.demo_spring_24_1.config.SpringConfig;
import com.lz.demo_spring_24_1.dao.UserDao;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

import java.sql.SQLException;

@SpringBootTest(classes = Test2.class)
public class Test2 {

    @Test
    public void test1() {


        ApplicationContext ctx = new AnnotationConfigApplicationContext(SpringConfig.class);
        UserDao userDao = (UserDao) ctx.getBean("userDao");
        System.out.println(userDao);
    }
}

2-2-3. 自动装配

现在我在数据访问层定义了一个类UserDao,还在业务层定义了一个类UserService,其中需要在UserService中引用UserDao类下的某个方法。为此,需要在UserService中new UserDao,在前面知识中了解到,可以通过set、构造器、自动装配这三种方式注入UserDao类对象,但是,上述讲述的是xml配置文件来进行注入的,现在如何使用配置类来实现上述那种自动装配的效果呢?参考代码如下:

package com.lz.demo_spring_24_1.dao;

import org.springframework.stereotype.Repository;

@Repository
public class UserDao {
}

package com.lz.demo_spring_24_1.service;


import com.lz.demo_spring_24_1.dao.UserDao;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

@Service
public class UserService {
    @Autowired
    private UserDao ud;

    public void print(){

        System.out.println(ud);
    }

}

我们只需要在UserService类中使用注解@AutoWired这种注入方式即可,当然也可以使用注解@Resource。它们两者的区别是前者首先是根据类型去对应包下去查找是否存在UserDao这个Bean,如果没有,再通过Bean名去查找,如果两者都没有找到或者出现歧义,最终会报错;而后者正好相反。@Resource注解可以通过name属性指定Bean名,如果name值没有指定,那么会把变量名当作Bean名来使用。。(也就是说如果此时变量名和对应的Bean名不一致,此时再根据类型来进行装配,如果没有找到,会报错。。。)

如果所注入的Bean有多个(比如有多个类都实现了某个接口,而且注入的Bean类型使用了泛型),此时可以在注解@AutoWired下添加注解@Qualifier,并在@Qualifier内写上对应的Bean名,当然这个Bean名需要在对应的类上写上才行。如下,有一个接口UserDao,它有一个方法printUserInfo,它有两个实现类UserDaoImpl1、UserDaoImpl2,这个类上都添加了注解@Repository。然后有一个类UserService,添加了注解@Service,在这个类下需要UserDao的依赖,这里直接使用泛型。如下:

package com.lz.demo_spring_24_1.beans;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Service;

@Service
public class UserService {

    @Autowired
    private UserDao ud;

    public void printUserInfo(){
        ud.printUserInfo();
    }
}

此时运行结果如下:
在这里插入图片描述
因为UserDao有两个实现类,此时不知道使用哪个,因此报错,此时就可以添加注解@Qualifier添加对应的Bean名(不设置对应Bean名,Bean名默认为类名首字母小写。。),如下:
在这里插入图片描述
当然也可以直接使用@Resource注解,写上对应的Bean名。
在这里插入图片描述
另外,注解@AutoWired也可以放在set方法上及构造方法上,如果所注入的Bean只有一个,@AutoWired可以省略,但是需要添加对应的构造方法,需要注意的是如果存在默认无参构造方法,这样是不行的,如下:
在这里插入图片描述

2-2-4. 简单类型的注入

通过使用注解@Value可以注入简单类型,如下:

package com.lz.demo_spring_24_1.dao;


import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Repository;

@Repository
public class UserDao {

    @Value("lize")
    private String name;

    @Override
    public String toString() {
        return "UserDao{" +
                "name='" + name + '\'' +
                '}';
    }
}

不过,上述这样写基本上没有任何意义,直接在name后面添加等于号并写上对应的值不也是一样吗?@Value真正意义在于可以注入配置文件properties中的变量,如下:
首先和上面xml配置一样,首先需要在配置类上指明所对应的配置文件,需要用到注解@PropertySource。
在这里插入图片描述
使用的话只需要在@Value注解内用${}指明引用哪个变量的值。
在这里插入图片描述
运行结果如下:
在这里插入图片描述
另外@Value还可以使用在set方法上,以及构造方法上的对应参数上。。

2-2-5. 管理第三方Bean

首先需要定义一个配置类,在这个配置类下定义对应Bean的方法。参考代码如下:

package com.lz.demo_spring_24_1.config;


import com.alibaba.druid.pool.DruidDataSource;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import javax.sql.DataSource;

@Configuration
public class SpringConfig2 {


    @Bean
    public DataSource dataSource(){

        DruidDataSource ds = new DruidDataSource();
        ds.setDriverClassName("com.mysql.jdbc.driver");
        ds.setUrl("jdbc:mysql://localhost:3306/mytest1");
        ds.setUsername("root");
        ds.setPassword("root");
        return ds;
    }
}

但是上述方式存在一个问题,就是如果我想修改上述一些配置信息,此时还需要找到这个类,然后再进行修改,为此,我们可以把上述信息放到配置文件properties中去,需要用到时只需要通过@Value注解注入即可。

2-3. Bean的实例化方式

这部分有一些内容和前面有重复,但是这里相当于是总结了吧!

2-3-1. 通过构造方法来实例化Bean

在xml配置文件中定义对应的Bean,然后直接通过容器.getBean方法来获取对应的Bean。默认情况下,是调用Bean的无参构造方法。。

2-3-2. 通过简单工厂模式实例化

本质上依旧是通过构造方法来实例化Bean,参考代码如下:
工厂类

 package com.lz.demo_spring_24_1.entity.factory;


public class Factory1 {

    public static User getUser(){
        return new User();
    }

}

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="user" class="com.lz.demo_spring_24_1.entity.factory.Factory1" factory-method="getUser"/>

</beans>

运行结果:
在这里插入图片描述

2-3-3. 通过工厂方法模式来实例化

本质上依旧是通过构造方法来实例化Bean,参考代码如下:
工厂类

package com.lz.demo_spring_24_1.entity.factory;


public class UserFactory {

    public User get(){
        return new User();
    }
}

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="user2Factory" class="com.lz.demo_spring_24_1.entity.factory.UserFactory"/>
    <bean id="user2" factory-bean="user2Factory" factory-method="get"/>
</beans>

运行结果:
在这里插入图片描述

2-3-4. 通过FactoryBean接口实例化

可以说是第三种方式的一种简化。参考代码如下:
实现了FactoryBean接口的类

package com.lz.demo_spring_24_1.entity.factory;

import org.springframework.beans.factory.FactoryBean;

public class UserFactoryBean implements FactoryBean<User> {
    @Override
    public boolean isSingleton() {
//        return FactoryBean.super.isSingleton();
        return true;
    }
    // 默认是单例的

    @Override
    public User getObject() throws Exception {
        return new User();
    }

    @Override
    public Class<?> getObjectType() {
        return null;
    }
}

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="user3" class="com.lz.demo_spring_24_1.entity.factory.UserFactoryBean"/>
</beans>

运行结果:
在这里插入图片描述

2-3-5. BeanFactory 和 FactoryBean 的区别

BeanFactory 是spring ioc容器的最顶层对象,意为 “Bean工厂”,负责创建Bean对象。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
FactoryBean 是一个Bean,是一个能够辅助 spring 实例化其他Bean对象的一个Bean。在spring中,Bean可以分为两类,一种为普通Bean,另外一种是工厂Bean。

2-3-6. FactoryBean 的应用(以自定义Date类型举例)

从前面可以知道,Date这种类型既可以当作是简单类型,也可以当作非简单类型。当作简单类型时,在xml配置文件中定义时,需要输入特定格式,否则会报错。而 FactoryBean 是Spring中一种用于辅助实例化其他Bean的Bean,为此,可以使用 FactoryBean 的形式,使在xml文件定义Date 类型的Bean支持自定义格式输入,参考代码如下:
继承了FactoryBean 接口的类

package com.lz.demo_spring_24_1.entity.factory;

import org.springframework.beans.factory.FactoryBean;

import java.text.SimpleDateFormat;
import java.util.Date;

public class DateFactoryBean implements FactoryBean<Date> {


    private String date_str;

    public DateFactoryBean(String date_str) {
        this.date_str = date_str;
    }

    @Override
    public boolean isSingleton() {
        return true;
    }

    @Override
    public Date getObject() throws Exception {

        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
        // 自定义输入的日期格式
        return sdf.parse(date_str);
    }

    @Override
    public Class<?> getObjectType() {
        return null;
    }
}

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="date" class="com.lz.demo_spring_24_1.entity.factory.DateFactoryBean">
        <constructor-arg name="date_str" value="2021-01-01"/>
    </bean>

</beans>

运行结果:
在这里插入图片描述

2-4. Bean的生命周期(5步)

就是Bean从被创建到销毁的过程。Bean的生命周期可以被划分为5个过程,分别是实例化Bean、Bean属性赋值、初始化Bean、使用Bean、销毁Bean

  • 实例化Bean,调用无参数构造方法;
  • Bean属性赋值,调用set方法;
  • 初始化Bean,调用 Bean 的 init方法,需要自己编写代码,并进行配置;
  • 使用Bean;
  • 销毁Bean,调用 Bean 的destory方法,需要自己编写代码,并进行配置;

Bean的声明周期代码演示如下:

package com.lz.demo_spring_24_1.entity;

// Bean 的生命周期
public class User3 {
    private String name;

    public User3() {
        System.out.println("1. 实例化Bean。。。");
    }

    public void setName(String name) {
        this.name = name;
        System.out.println("2. Bean 参数赋值...");
    }

    // 初始化Bean
    public void init(){
        System.out.println("3. 初始化Bean。。。");
    }
    // 销毁 Bean
    public void destory(){
        System.out.println("5. 销毁Bean。。。");
    }
}
<?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="user" class="com.lz.demo_spring_24_1.entity.User3" init-method="init" destroy-method="destory">
        <property name="name" value="张三"/>
    </bean>

</beans>
package com.lz.demo_spring_24_1;

import com.lz.demo_spring_24_1.entity.User3;
import org.junit.jupiter.api.Test;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class Test2025_1 {

    @Test
    public void test3(){

        ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("spring-2025-3.xml");
        User3 user3 = ctx.getBean("user", User3.class);
        System.out.println("4. 使用Bean"+user3);
        // 使用Bean
        ctx.close();
        // 关闭容器,只能applicationContext实现类才有close方法。。。
    }
}

运行结果:
在这里插入图片描述

2-4-1. Bean 的生命周期之7步

在上面说到Bean的生命周期只有5步,7步的说法是在前面5步的基础上添加了2步,就是在5步的第4步 初始化Bean 前面加上 执行”Bean后处理器“的before方法,在后面加上 执行”Bean后处理器“的fater方法。。。
需要在前面基本上添加一个类,这个类需要实现BeanPostProcessor接口。。。,并重写其下面的2个方法。

package com.lz.demo_spring_24_1.entity.interfaces;

import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;

public class LogBeanPostProcessor implements BeanPostProcessor {
    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        System.out.println("执行 Bean后处理器 的before方法");
        return BeanPostProcessor.super.postProcessBeforeInitialization(bean, beanName);
    }

    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        System.out.println("执行 Bean后处理器 的after方法");
        return BeanPostProcessor.super.postProcessAfterInitialization(bean, beanName);
    }
}

另外需要在xml配置文件中配置这个Bean。
在这里插入图片描述
之后就可以看到运行结果了。。。
在这里插入图片描述
需要注意的是,上述添加这个Bean对所有的Bean都会生效。。。也就是说 当前容器对象 getBean 之后获取到Bean都会执行上述两个函数。。。

2-4-2. Bean 的生命周期之10步

在前面7步的基础之上,再额外添加3步,添加的位置分别为:

  • 在 执行”Bean后处理器“的before方法 前面添加 检查 Bean 是否实现了Aware的相关接口,并设置相关依赖;
  • 在 执行”Bean后处理器“的before方法 后面添加 检查 Bean 是否实现了InitializingBean接口,并调用接口方法;
  • 在使用Bean 之后添加了 检查 Bean 是否实现了DispossableBean接口,并调用接口方法。

其中Aware的相关接口有:BeanNameAware、BeanClassLoaderAware、BeanFactoryAware。

  • 当Bean实现了BeanNameAware,spring会将Bean的名字传递给Bean;
  • 当Bean实现了BeanClassLoaderAware,spring会将加载该Bean的类加载器传递给Bean;
  • 当Bean实现了BeanFactoryAware,spring会将Bean工厂对象传递给Bean。

总结而言:如果测试生命周期10步,需要让对应的类实现5个接口,分别为BeanNameAware、BeanClassLoaderAware、BeanFactoryAware、InitializingBean、DispossableBean。

演示代码如下:

package com.lz.demo_spring_24_1.entity;


import org.springframework.beans.BeansException;
import org.springframework.beans.factory.*;

// Bean 的生命周期
public class User3 implements BeanNameAware, BeanClassLoaderAware, BeanFactoryAware, InitializingBean, DisposableBean {
    private String name;

    public User3() {
        System.out.println("1. 实例化Bean。。。");
    }

    public void setName(String name) {
        this.name = name;
        System.out.println("2. Bean 参数赋值...");
    }

    // 初始化Bean
    public void init(){
        System.out.println("4. 初始化Bean。。。");
    }
    // 销毁 Bean
    public void destory1(){
        System.out.println("7. 销毁Bean。。。");
    }

    @Override
    public void setBeanClassLoader(ClassLoader classLoader) {
        System.out.println("Bean的类加载器:"+classLoader);
    }

    @Override
    public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
        System.out.println("Bean的 工厂对象是:"+beanFactory);
    }

    @Override
    public void setBeanName(String s) {
        System.out.println("Bean的名字是:"+s);
    }

    @Override
    public void destroy() throws Exception {
        System.out.println("实现了DisposableBean接口。。。");
    }

    @Override
    public void afterPropertiesSet() throws Exception {
        System.out.println("实现了InitializingBean接口。。。");
    }
}

<?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 class="com.lz.demo_spring_24_1.entity.interfaces.LogBeanPostProcessor"/>

    <bean id="user" class="com.lz.demo_spring_24_1.entity.User3" init-method="init" destroy-method="destory1">
        <property name="name" value="张三"/>
    </bean>

</beans>

运行结果:
在这里插入图片描述

2-4-3. Bean的作用域

spring 容器只对 单例的 Bean进行完整的生命周期管理。如果是原型的Bean,spring容器只负责将Bean初始化完毕,等客户端一旦获取到该Bean之后,spring容器就不在管理该对象的生命周期了。如果需要测试的话,只需要在xml配置文件的Bean添加属性scope,并设置值为原型,运行结果如下:
在这里插入图片描述

2-4-4. 自己实例化的对象让spring容器去管理

直接复制类代码,导包让软件去导入。。。

package com.lz.demo_spring_24_1;

import com.lz.demo_spring_24_1.entity.User3;
import com.lz.demo_spring_24_1.entity.User4;
import com.lz.demo_spring_24_1.entity.factory.User;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import java.util.Date;

public class Test2025_1 {

    @Test
    public void test4(){

        User4 user4 = new User4();
        System.out.println(user4);
        
        DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
        beanFactory.registerSingleton("user",user4);

        User4 user1 = beanFactory.getBean(User4.class);
        System.out.println(user1);

    }
}

运行结果:
在这里插入图片描述

2-5. Bean 循环依赖问题

其实就是在一个Bean a中需要Bean b的依赖,而在Bean b中又需要Bean a的依赖。

2-5-1. set注入 + 单例模式之循环依赖

比如如下代码:

package com.lz.demo_spring_24_1.entity.xunhuan;

public class UserA {
    private String name;
    private UserB userB;

    public void setName(String name) {
        this.name = name;
    }

    public void setUserB(UserB userB) {
        this.userB = userB;
    }

    @Override
    public String toString() {
        return "UserA{" +
                "name='" + name + '\'' +
                ", userB=" + userB.getName() +
                '}';
    }

    public String getName() {
        return name;
    }
}

package com.lz.demo_spring_24_1.entity.xunhuan;

public class UserB {
    private String name;
    private UserA userA;

    public void setName(String name) {
        this.name = name;
    }

    public void setUserA(UserA userA) {
        this.userA = userA;
    }

    @Override
    public String toString() {
        return "UserB{" +
                "name='" + name + '\'' +
                ", userA=" + userA.getName() +
                '}';
    }

    public String getName() {
        return name;
    }
}

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="usera" class="com.lz.demo_spring_24_1.entity.xunhuan.UserA">
        <property name="name" value="张三"/>
        <property name="userB" ref="userb"/>
    </bean>

    <bean id="userb" class="com.lz.demo_spring_24_1.entity.xunhuan.UserB">
        <property name="name" value="李四"/>
        <property name="userA" ref="usera"/>
    </bean>
</beans>

运行结果:
在这里插入图片描述
如果上述代码UserA的toString方法中参数直接是UserB,并且在UserB的toString方法中参数直接是UserA。此时的结果会报错,因为输出UserA这个对象时,实际上调用的是重写toString方法,而在toString方法中又会输出UserB,而在UserB的toString方法下又有UserA,此时会陷入si循环。。最终导致内存溢出从而导致报错。
上面是 单例模式 + set注入(原型模式下不可以) 的模式下的运行结果,Spring容器在加载的时候,进行实例化,只要任意一个Bean实例化后,马上进行“曝光”,不等属性赋值;Bean被“曝光”之后,再进行属性赋值。(在spring中为什么可以解决循环依赖的问题。。。)需要注意的是,在spring中只有当两个Bean都是原型下,才会出现异常,但是如果其中有一个是单例的,就不会出现异常。。

2-5-2. 构造器注入 + 单例模式之循环依赖

这种方式是存在问题,因为这是直接在Bean a构造方法里面给属性赋值,但是其中一个参数Bean b还没有进行实例化,而Bean b里边又有一个参数Bean a也没有进行实例化。参考代码如下:

package com.lz.demo_spring_24_1.entity.xunhuan;

public class UserA {
    private String name;
    private UserB userB;

    public UserA(String name, UserB userB) {
        this.name = name;
        this.userB = userB;
    }

    /*public void setName(String name) {
        this.name = name;
    }

    public void setUserB(UserB userB) {
        this.userB = userB;
    }*/

    @Override
    public String toString() {
        return "UserA{" +
                "name='" + name + '\'' +
                ", userB=" + userB.getName() +
                '}';
    }

    public String getName() {
        return name;
    }
}

package com.lz.demo_spring_24_1.entity.xunhuan;

public class UserB {
    private String name;
    private UserA userA;

    public UserB(String name, UserA userA) {
        this.name = name;
        this.userA = userA;
    }

    /*public void setName(String name) {
        this.name = name;
    }

    public void setUserA(UserA userA) {
        this.userA = userA;
    }*/

    @Override
    public String toString() {
        return "UserB{" +
                "name='" + name + '\'' +
                ", userA=" + userA.getName() +
                '}';
    }

    public String getName() {
        return name;
    }
}

在这里插入图片描述
也就是说 构造器注入 + 单例模式 这种方式下spring是无法解决循环依赖问题的。

2-5-3. Spring 解决循环依赖的机理

set注入+单例模式下为什么能解决循环依赖问题?
根本原因在于:这种方式可以将 实例化Bean 和 给Bean属性赋值 这两个动作分开去完成。实例化Bean的时候,调用无参构造方法来完成,**此刻可以先不给属性赋值,可以提前将该Bean对象“曝光”给外界。**给Bean属性赋值的时候,调用setter方法来完成。两个过程是完全分开去完成的,并且两个过程不要求在同一个时间点上完成。

2-6. 自定义 spring 框架

这里的spring框架只有基本ioc功能,且还是通过配置文件的形式。。参考代码如下:
myspring核心代码

package com.lz.demo_spring_24_1.myspring.utils;


import org.dom4j.Document;
import org.dom4j.Element;
import org.dom4j.Node;
import org.dom4j.io.SAXReader;
import java.io.InputStream;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class MyBeanFactory {

    private Map<String,Object> beanMap = new HashMap<>();
    // 用来存储bean的哈希表
    // 在构造方法这里的读取xml配置文件的数据,
    // 然后通过反射机制进行实例化对象,之后把实例化后的对象存储到哈希表中进行存储
    public MyBeanFactory(String configPath) {
        try{
            SAXReader reader = new SAXReader();
            InputStream stream = ClassLoader.getSystemClassLoader().getResourceAsStream(configPath);
            Document document = reader.read(stream);
            List<Node> nodes = document.selectNodes("//bean");
            // 获取所有的bean标签
            for (Node node : nodes) {
                Element ele = (Element) node;
                // 转换成Element类型
                String id = ele.attributeValue("id");
                String clazz = ele.attributeValue("class");
                // bean 的名称,class 字符串
                Class<?> aClass = Class.forName(clazz);
                Constructor<?> constructor = aClass.getDeclaredConstructor();
                Object o = constructor.newInstance();
                beanMap.put(id,o);
                // 通过反射对bean进行无参实例化
            }
            setBeanField(nodes,beanMap);
        }catch (Exception e){
            e.printStackTrace();
        }
    }

    /**
     * 获取bean的方法
     * beanName : Bean的名称
     * */
    public Object getBean(String beanName){


        return beanMap.get(beanName);
    }

    // 给对象属性赋值
    // 相当于set注入
    private void setBeanField(List<Node> nodes,Map<String,Object> beanMap){

        for (Node node : nodes) {
            try{
                Element ele = (Element) node;
                String id = ele.attributeValue("id");
                String clazz = ele.attributeValue("class");
                // bean 的名称,class 字符串
                Class<?> aClass = Class.forName(clazz);

                List<Element> properties = ele.elements();
                // 所有的属性标签
                properties.forEach(property->{
                    try{
                        String name = property.attributeValue("name");

                        String value = property.attributeValue("value");
                        String ref = property.attributeValue("ref");

                        String setName = "set" + name.substring(0,1).toUpperCase() + name.substring(1);
                        Field field = aClass.getDeclaredField(name);
//                        field.setAccessible(true);
                        Class<?> type1 = field.getType();
                        Method setMethod = aClass.getDeclaredMethod(setName, type1);

                        Object v = value;
                        if(value != null){
                            // 这个变量是私有的
                            // 简单类型
                            String typeSimpleName = type1.getSimpleName();
                            // 属性类型名
                            switch (typeSimpleName){
                                case "byte":
                                    v = Byte.parseByte(value);
                                    break;
                                case "short":
                                    v = Short.parseShort(value);
                                    break;
                                case "int":
                                    v = Integer.parseInt(value);
                                    break;
                                case "long":
                                    v = Long.parseLong(value);
                                    break;
                                case "boolean":
                                    v = Boolean.parseBoolean(value);
                                    break;
                                case "float":
                                    v = Float.parseFloat(value);
                                    break;
                                case "double":
                                    v = Double.parseDouble(value);
                                    break;
                                case "char":
                                    v = value.charAt(0);
                                    break;
                                case "Byte":
                                    v = Byte.valueOf(value);
                                    break;
                                case "Short":
                                    v = Short.valueOf(value);
                                    break;
                                case "Integer":
                                    v = Integer.valueOf(value);
                                    break;
                                case "Long":
                                    v = Long.valueOf(value);
                                    break;
                                case "Boolean":
                                    v = Boolean.valueOf(value);
                                    break;
                                case "Float":
                                    v = Float.valueOf(value);
                                    break;
                                case "Double":
                                    v = Double.valueOf(value);
                                    break;
                                case "Character":
                                    v = Character.valueOf(value.charAt(0));
                                    break;
                            }
                            setMethod.invoke(beanMap.get(id),v);
                        }

                        if(ref != null){
                            // 非简单类型
                            setMethod.invoke(beanMap.get(id),beanMap.get(ref));
                        }


                    }catch (Exception e){
                        e.printStackTrace();
                    }
                });
            }catch (Exception e){
                e.printStackTrace();
            }
        }
    }
}

测试类

package com.lz.demo_spring_24_1.myspring;

public class User {

    private String name;
    private Integer age;
    private User2 user2;

    public void setAge(Integer age) {
        this.age = age;
    }

    public void setName(String name) {
        this.name = name;
    }

    public void setUser2(User2 user2) {
        this.user2 = user2;
    }


    @Override
    public String toString() {
        return "User{" +
                "name='" + name + '\'' +
                ", age=" + age +
                ", user2=" + user2 +
                '}';
    }
}

package com.lz.demo_spring_24_1.myspring;

public class User2 {
}

配置文件

<?xml version="1.0" encoding="UTF-8"?>
<beans>
    <bean id="user" class="com.lz.demo_spring_24_1.myspring.User">
        <property name="name" value="张三"/>
        <property name="age" value="20"/>
        <property name="user2" ref="user2"/>
    </bean>

    <bean id="user2" class="com.lz.demo_spring_24_1.myspring.User2"/>

</beans>

运行结果:
在这里插入图片描述
需要注意的是,因为需要解析xml文件数据,需要导入对应依赖,如下:

<!--        用于解析xml文件的包-->
        <dependency>
            <groupId>org.dom4j</groupId>
            <artifactId>dom4j</artifactId>
            <version>2.1.3</version>
        </dependency>
        <dependency>
            <groupId>jaxen</groupId>
            <artifactId>jaxen</artifactId>
            <version>1.2.0</version>
        </dependency>

3. Spring之JdbcTemplate

JdbcTemplate是Spring提供的一个jdbc模板类,是对jdbc的封装。当然,现在大多数用的都是MyBatis等。首先需要导入的依赖为:

        <!-- MySQL JDBC 驱动 -->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.46</version>
        </dependency>
<!--        spring-jdbc-->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-jdbc</artifactId>
        </dependency>

另外还需要spring-context的依赖哈。因为我这边使用的mysql 数据库版本为5.xxx的版本,因此使用mysql的驱动为5.xxx,在对应的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="myDataSource" class="com.lz.demo_spring_24_1.jdbcTemplate.MyDataSource">
        <property name="driver" value="com.mysql.jdbc.Driver"/>
        <property name="url" value="jdbc:mysql://localhost:3306/mytest1?characterEncoding=utf8"/>
        <property name="username" value="root"/>
        <property name="password" value="root"/>
    </bean>


    <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
        <property name="dataSource" ref="myDataSource"/>
    </bean>


</beans>

往数据库中插入一条数据,如下:

package com.lz.demo_spring_24_1;

import com.lz.demo_spring_24_1.jdbcTemplate.entity.User;
import org.junit.jupiter.api.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.jdbc.core.JdbcTemplate;

import java.util.List;

public class MyJdbcTemplate {


    @Test
    public void test(){


        ApplicationContext ctx = new ClassPathXmlApplicationContext("spring-jdbcTemplate.xml");
        JdbcTemplate jdbcTemplate = ctx.getBean("jdbcTemplate",JdbcTemplate.class);
        String sql = "insert into user values(?,?)";
        jdbcTemplate.update(sql,2,"王五");

    }
}

插入是可以成功的。
如果想查询数据,并且查询出的数据字段都需要映射到对应实体类上对应变量上去,可以使用如下代码:

package com.lz.demo_spring_24_1;

import com.lz.demo_spring_24_1.jdbcTemplate.entity.User;
import org.junit.jupiter.api.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.jdbc.core.BeanPropertyRowMapper;
import org.springframework.jdbc.core.JdbcTemplate;

import java.util.List;

public class MyJdbcTemplate {


    @Test
    public void test(){


        ApplicationContext ctx = new ClassPathXmlApplicationContext("spring-jdbcTemplate.xml");
        JdbcTemplate jdbcTemplate = ctx.getBean("jdbcTemplate",JdbcTemplate.class);

        String sql = "select * from user";
        List<User> users = jdbcTemplate.query(sql,new BeanPropertyRowMapper<>(User.class));
        for (User user : users) {
            System.out.println(user);
        }
    }
}

如果想要更换成其他的DataSource,只需要在xml配置文件中修改对应DataSource的配置即可,class属性值修改为druid的,driver、url、username、password这四个属性名可能有所不同。。

4. Spring 代理模式

关于Spring aop的功能实现本质上就是动态代理,参考文章链接为:Spring AOP原理–动态代理。。。关于上述文章的静态代理,这里有更加详细的参考,代理类和被代理类都需要实现公共的接口,如下:
公共的接口类

package com.lz.demo_spring_24_1.proxy;
// 这是一个接口
public interface IUser {
    void play();
    // 方法 play
}

被代理的类

package com.lz.demo_spring_24_1.proxy.impl;

import com.lz.demo_spring_24_1.proxy.IUser;

public class IUserImpl implements IUser {
    @Override
    public void play() {
        System.out.println("学习编程技术。。。");
    }
}

代理类

package com.lz.demo_spring_24_1.proxy;


// 代理类
public class UserProxy implements IUser{


    // 这里应用泛型,可以降低代码的耦合度
    private IUser iUser = null;
    // 通过构造方法来把对应变量赋值
    public UserProxy(IUser iUser) {
        this.iUser = iUser;
    }

    @Override
    public void play() {
        System.out.println("这里可以做一些前置操作。。。");
        iUser.play();
        System.out.println("这里可以做一些后置操作。。。");
    }
}

测试运行

package com.lz.demo_spring_24_1;


import com.lz.demo_spring_24_1.proxy.IUser;
import com.lz.demo_spring_24_1.proxy.UserProxy;
import com.lz.demo_spring_24_1.proxy.impl.IUserImpl;
import org.junit.jupiter.api.Test;

public class ProxyTest {

    @Test
    public void test(){

        IUser iUser = new IUserImpl();
        IUser userProxy = new UserProxy(iUser);
        userProxy.play();
    }
}

在这里插入图片描述
但是上述代理存在一个很大的问题,那就是接口下面的方法如果很多的话,并且在代理类上上的每个方法都需要增强,那么被代理类就需要写很多可能较为重复的增强代码;而且每个被代理类的都需要编写对应的代理类。因此,有了动态代理。。。

4-1. jdk 之动态代理

使用动态代理,代理类可以不用编写了,但是接口必须要有。。在上述代码基础之上进行操作。。。接口类和被代理类和上面一样。。jdk动态代理不需要额外添加依赖。

Proxy.newProxyInstance(arg1,arg2,arg3)

通过上述代码实现一个代理对象,其中参数分别表示的意思为:

  • arg1:被代理类的类加载器;
  • arg2:被代理类实现的接口;
  • arg3:最为关键,实现InvocationHandler的对象a,且对象a传入参数为被代理的那个对象(这样才能实现增强代码);这个只需要写一个即可,就可以解决上述静态代理存在的那两个问题。

实现InvocationHandler接口的类

package com.lz.demo_spring_24_1.proxy.jdkProxy;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;


public class MyHandler implements InvocationHandler {

    private Object target;

    public MyHandler(Object target) {
        this.target = target;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        // method 为被代理类方法
        // args 方法参数
        // 反射。。
        
        System.out.println("这里做一些前置操作。。。");
        Object ans = method.invoke(target,args);
        System.out.println("这里做一些后置操作。。。");
        return ans;
    }
}

运行代码:

package com.lz.demo_spring_24_1;


import com.lz.demo_spring_24_1.proxy.IUser;
import com.lz.demo_spring_24_1.proxy.UserProxy;
import com.lz.demo_spring_24_1.proxy.impl.IUserImpl;
import com.lz.demo_spring_24_1.proxy.jdkProxy.MyHandler;
import org.junit.jupiter.api.Test;

import java.lang.reflect.Proxy;

public class ProxyTest {

    @Test
    public void test2(){
        IUser iUser = new IUserImpl();
        IUser iUserProxy = (IUser) Proxy.newProxyInstance(iUser.getClass().getClassLoader(),
                iUser.getClass().getInterfaces(),
                new MyHandler(iUser));
        iUserProxy.play();
    }
}

运行结果和上述一致。。。

4-2. cglib 之动态代理

如果是maven项目,需要额外导入cglib的依赖才行。参考代码如下:

package com.lz.demo_spring_24_1.proxy.cglibProxy;

import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;

public class MyCallback implements MethodInterceptor {

    @Override
    public Object intercept(Object target, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {

        System.out.println("这里可以做一些前置操作。。。");
        Object ans = methodProxy.invokeSuper(target,args);
        System.out.println("这里可以做一些后置操作。。。");
        return ans;
    }
}

package com.lz.demo_spring_24_1;

import com.lz.demo_spring_24_1.proxy.cglibProxy.MyCallback;
import com.lz.demo_spring_24_1.proxy.impl.IUserImpl;
import org.junit.jupiter.api.Test;
import org.springframework.cglib.proxy.Enhancer;

public class ProxyTest {
    @Test
    public void test3(){
        Enhancer enhancer = new Enhancer();
        // 设置被代理的类
        enhancer.setSuperclass(IUserImpl.class);
        enhancer.setCallback(new MyCallback());
        IUserImpl iUser = (IUserImpl) enhancer.create();
        // 代理的类
        iUser.play();
    }
}

运行结果和上面一致。。

4-3. jdk 与 cglib 动态代理的区别

在这里插入图片描述
参考链接在这:jdk 与 cglib 动态代理的区别

5. 面向切面编程 AOP

详细请看这篇博文:Aop 面向切面编程

Spring 的AOP底层实现本质是动态代理,jdk、cglib动态代理两者都有,Spring在这两种动态代理中可以根据实际应用场景实现切换,如果是代理接口,会默认使用jdk动态代理;如果要代理某个类,这个类没有实现接口,那么就会切换到cglib。当然,也可以通过配置强制让Spring来使用两者动态代理中的一种。

在这里插入图片描述
首先需要导入aspect的依赖

 <dependency>
   <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-aop</artifactId>
</dependency>

之后编写xml配置文件

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
                           http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
                           http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd
        ">


    <context:component-scan base-package="com.lz.demo_spring_24_1.proxy.aspect"/>
<!--    自动扫描-->
    <aop:aspectj-autoproxy/>
<!--    让 @Aspect 起作用-->
</beans>

编写Aspect的切面类

package com.lz.demo_spring_24_1.proxy.aspect;


import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component;


@Aspect
@Component
public class MyAspect {

    @Before("execution(* com.lz.demo_spring_24_1.proxy.aspect.UserService.*(..))")
    public void fun1(){
        System.out.println("前置通知。。。");
    }

}

测试代码

package com.lz.demo_spring_24_1;


import com.lz.demo_spring_24_1.proxy.aspect.UserService;
import org.junit.jupiter.api.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class MyAspectTest {

    @Test
    public void test(){

        ApplicationContext ctx = new ClassPathXmlApplicationContext("spring-2025-aspect.xml");

        UserService userService = ctx.getBean("userService",UserService.class);

        userService.selectAll();
    }
}

运行结果:
在这里插入图片描述
在这里可以打印一下UserService的Class值,可以发现它是属于cglib动态代理生成的。
在这里插入图片描述
因为UserService类并不是通过实现某某接口的。
如果想纯注解实现上述效果,只需要把上述xml配置文件用一个配置类来代替即可,配置类参考如下:

package com.lz.demo_spring_24_1.proxy.aspect;


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

@Configuration
@ComponentScan(value = "com.lz.demo_spring_24_1.proxy.aspect")
@EnableAspectJAutoProxy
public class Config {
}

当然测试代码的加载容器需要修改一下:
在这里插入图片描述

6. Spring 事务

具体详细请看这篇文章:Spring 事务。在一个业务中,可能涉及到多条sql数据的执行,这些sql数据要么全部执行成功,要么全部执行失败,为此,应用到了事务 Transaction。
事务的四个处理过程,包括开启事务、执行核心业务代码、提交事务、回滚事务。事务的四个特性为:原子性、一致性、隔离性、持久性,也就是常说的ACID,其中原子性表示事务不可以再分;一致性表示事务要么同时成功,要么同时失败;隔离性表示事务和事务之间互不干扰;持久性表示一旦事务提交,它对数据库的修改就是永久性的,即使系统发生故障,数据也不会丢失。

6-1. 事务的传播特性

关于事务的传播特性,总共有7种,下述只是给出常见的四种。
在这里插入图片描述
测试结果:如果外部事物存在,并且内部事务也存在,且两个事务的传播行为都为REQUIRED。此时内部事务有抛异常的代码,在外部事务里边进行了try/catch捕获,事务会进行回滚。
如果上述内部事务为REQUIRES_NEW,外部事务不变,此时外部事务会正常执行,内部事务会进行回滚。

6-2. 事务的隔离级别

事务的隔离级别包括读未提交、读已提交、可以重复读、串行化。在上面那篇文章里边只介绍了后3种,因为通过设置后面3种隔离级别,可以解决对应的问题,比如脏读、不可重复读、幻读。
实际测试:关于脏读,如果其中一个事务a执行查询操作,另外一个事务b执行插入操作。如果事务a设置的隔离级别为读未提交,b事务没有设置隔离级别(数据库是MYSQL,也就是隔离级别为读已提交)。事务b先执行,但是没有结束;事务a后执行并已结束,此时事务a读取到数据是脏数据,也就是脏读。如果事务a设置的隔离级别为读已提交,依旧按照上述执行流程来,此时事务a的运行结果会报错。

6-3. 事务的超时时间

如果事务设置了超时时间a,那么表示超过a秒如果该事务种所有的DML语句还没有执行完毕的话,最终结果会选择回滚事务的超时时间指的事务开始到最后一条DML语句执行完的时间(只要不超过这个时间,就不会进行回滚操作)。如果最后一条DML语句后面还有很多业务逻辑,这些业务逻辑执行的时间不计入超时时间。

6-4. 设置事务只读(readOnly)

之所以设置事务为只读,是为了提高select语句的执行效率(这里启动了Spring的优化策略)。在这种事务下,只能执行查询操作,执行插入、删除、修改操作都会报错。


http://www.kler.cn/a/502515.html

相关文章:

  • Hadoop•安装JDK
  • 嵌入式系统中的 OpenCV 与 OpenGLES 协同应用
  • 【JVM-2.3】深入解析JVisualVM:Java性能监控与调优利器
  • 25年无人机行业资讯 | 1.1 - 1.5
  • 忘记了PDF文件的密码,怎么办?
  • 腾讯云AI代码助手编程挑战赛 - 腾讯云AI代码助手小试
  • DHCP详解和部署
  • 电脑分辨率调到为多少最佳?电脑分辨率最佳设置
  • 17.C语言输入输出函数详解:从缓存原理到常用函数用法
  • 深入详解人工智能自然语言处理(NLP)之文本处理:分词、词性标注、命名实体识别
  • R语言的面向对象编程
  • MMDetection框架下的常见目标检测与分割模型综述与实践指南
  • 【数字化】华为-用变革的方法确保规划落地
  • 【Linux】Linux常见指令(下)
  • Flutter pubspec.yaml 使用方式
  • 重回C语言之老兵重装上阵(四)vscode配置C语言多文件编译运行
  • Cython全教程2 多种定义方式
  • 浏览器输入http形式网址后自动跳转https解决方法
  • 【Vue实战】Vuex 和 Axios 拦截器设置全局 Loading
  • 2024年11月架构设计师综合知识真题回顾,附参考答案、解析及所涉知识点(一)
  • iOS开发基础109-网络安全
  • Python脚本自动发送电子邮件
  • 【JAVA面试】java权限修饰符
  • STM32-Flash存储
  • 二叉树层序遍历 Leetcode102.二叉树的层序遍历
  • 论文笔记(六十一)Implicit Behavioral Cloning