Spring6

发布时间 2023-08-11 15:31:51作者: SRIGT

0x00 环境配置

环境:

  • IDEA >= 2022.1.4
  • JDK 17
  • Maven 3.8.6
  • Spring 6.0.0
  • JUnit 4.13.2
  • Log4j2
  • 新建模块 spring001 -> 高级设置 -> 组 ID

  • 在 /spring001/src/main/java 分别新建软件包

    • com.spring.dao:持久层
    • com.spring.service:业务层,调用持久层
    • com.spring.web:表示层,调用业务层
    • com.spring.client:测试
  • 层和层之间使用接口交互

    • dao

      • 新建接口 UserDao.java

        public interface UserDao {
            void deleteById();
        }
        
      • 新建实现包 impl

        • 新建实现类 UserDaoImplForMySQL.java

          public class UserDaoImplForMySQL implements UserDao {
              @Override
              public void deleteById() {
                  System.out.println("Deleting...");
              }
          }
          
    • service

      • 新建接口 UserService.java

        public interface UserService {
            void deleteUser();
        }
        
      • 新建实现包 impl

        • 新建实现类 UserServiceImpl.java

          public class UserServiceImpl implements UserService {
              private UserDao userDao = new UserDaoImplForMySQL();
              
              @Override
              public void deleteUser() {
                  userDao.deleteById();
              }
          }
          
    • web

      • 新建类 UserAction.java

        public class UserAction {
            private UserService userService = new UserServiceImpl();
        
            public void deleteRequest() {
                userService.deleteUser();
            }
        }
        
    • client

      • 新建类 Test.java

        public class Test {
            public static void main(String[] args) {
                UserAction userAction = new UserAction();
                userAction.deleteRequest();
            }
        }
        
  • 上述项目在需求升级后需要大面积替换原代码,很不方便且容易出错,故使用 Spring 进行调整

0x01 框架主要思想

  • OCP

    • OCP 是软件七大开发原则开闭原则
      • 对扩展开发,对修改关闭
    • OCP 是七大开发原则中最核心、最基本的原则,核心为:扩展功能时未修改原代码
  • DIP

    • DIP 是软件七大开发原则依赖倒置原则
      • 下层依赖上层时符合该原则
    • DIP 核心为面向接口编程
  • IoC

    • IoC 是控制反转,是一种编程思想,是新型设计模式
      • 在程序中不使用硬编码的方式来新建对象和维护对象关系
    • IoC 用于解决当前程序设计既违反了 OCP 又违反 DIP 的问题
  • Spring 框架

    • Spring 是实现 IoC 思想的容器
    • IoC 的实现方法很多,其中比较重要的是依赖注入(DI)
    • DI 常见的方式
      • set 注入
      • 构造方法注入

0x02 框架概述

(1)八大模块

  1. Spring AOP:面向切面编程
  2. Spring Web:支持集成常见的 Web 框架
  3. Spring Web MVC:Spring 自有的 MVC 框架
  4. Spring Webflux:Spring 自有的 响应式 Web 框架
  5. Spring DAO:单独的支持 JDBC 操作的 API
  6. Spring ORM:集成常见的 ORM 框架,如 MyBatis 等
  7. Spring Context:国际化消息、事件传播、验证支持、企业服务、Velocity 和 FreeMarket2 集成
  8. Spring Core:控制反转

(2)特点

  1. 轻量、非侵入式
  2. 控制反转
  3. 面向切面
  4. 容器
  5. 框架

0x03 入门程序

(1)Spring 下载与引用

  • Spring 官网

    graph LR A(Projects<br />Spring Framework)-->Github -->B(Access to Binaries<br />Spring Framework Artifacts) -->C(Spring Repositories<br />https://repo.spring.io) -->Artifacts -->D(plugins-release/<br />org/springframework/<br />spring/6.0.0-RC2) -->E(General/URL to file) -->spring-x.x.x-dist.zip
    字符 说明
    第一个数字 主版本,有可能进行大的架构调整,各大版本之间并不一定兼容
    第二个数字 次版本,在主版本架构不变的前提下,增加了一些新的特性或变化
    第三个数字 增量版本,bug修复,细节的完善
    M 里程碑版本,测试版本,发布版本的前兆
    RC 候选发布版本,稳定版本,并不一定会发布
    RELEASE/NULL 发布版本,稳定版本,在项目中真正可用的版本
  • Maven 引用 Spring

    <dependencies>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>6.2.2RC2</version>
        </dependency>
    </dependencies>
    

(2)创建项目

  • 修改 pom.xml 文件内容

    <!--    修改打包方式-->
        <packaging>jar</packaging>
    
    <!--    配置多个仓库-->
        <repositories>
            <repository>
                <id>repository.spring.milestone</id>
                <name>Spring Milestone Repository</name>
                <url>https://repo.spring.io/milestone</url>
            </repository>
        </repositories>
    
    <!--    依赖项-->
        <dependencies>
    <!--        Spring Context 依赖-->
            <dependency>
                <groupId>org.springframework</groupId>
                <artifactId>spring-context</artifactId>
                <version>6.0.4</version>
            </dependency>
    
    <!--        junit 单元测试依赖-->
            <dependency>
                <groupId>junit</groupId>
                <artifactId>junit</artifactId>
                <version>4.13.2</version>
                <scope>test</scope>
            </dependency>
        </dependencies>
    
  • 添加 Spring 配置

    • 在 src/main/resources 目录下新建 spring.xml 文件(名称可变)来配置 Spring

    • 在该文件中配置 bean,从而使 Spring 可以帮助我们管理对象(实现 IoC 的作用)

    • bean 有两个重要属性:idclass

      • id:bean 的唯一标识
      • class:必须填写类的全路径,如com.xxx.xxx.ClassName
        • 带包名的类名,双击需要引用的类名,右键选择“复制引用”
    • 如:<bean id="userBean" class="com.spring6.bean.User" />

    • 也可以引用 Java 自带的无参类,如:<bean id="date" class="java.util.Date" />

(3)编写程序

  • 获取对象

    • 在 src/text/java 目录下新建软件包

    • 在该软件包中新建 Java 类,并写入以下代码:

      public class FirstSpringTest {
          @Test
          public void testFirstSpringCode() {
              ApplicationContext appContext = new ClassPathXmlApplicationContext("spring.xml");
              Object userBean = appContext.getBean("userBean");
          }
      }
      
      • 第一步,获取 Spring 容器对象——ApplicationContext

        • ApplicationContext本质是接口,其中有一个实现类为ClassPathXmlApplicationContext,专门从类路径当中加载 Spring 配置文件的一个 Spring 上下文对象
        • BeanFactoryApplicationContext接口的超级父接口,是 IoC 容器的顶级接口,反映了 Spring 的 IoC 容器底层实际上使用了工厂模式
          • XML 解析、工厂模式、反射机制
      • 第二步,根据 bean 的 id 从 Spring 容器中获取这个对象,通过getBean()方法实现

  • 默认情况下,Spring 会通过反射机制,调用类的无参构造方法来实例化对象,其构造方法如下:

    Class example = Class.forName("com.example");
    Object obj = example.newInstance();
    
  • 获取多个 Spring 容器对象

    ApplicationContext appContext = new ClassPathXmlApplicationContext("spring_1.xml", "spring_2.xml", "spring_3.xml");
    
  • 如果需要访问子类特有属性和方法时

    • 方法一:强转

      import java.util.Date;
      
      public class FirstSpringTest {
          @Test
          public void testFirstSpringCode() {
              // <bean id="date" class="java.util.Date" />
              ApplicationContext appContext = new ClassPathXmlApplicationContext("spring.xml");
              Date date = (Date)appContext.getBean("date");
          }
      }
      
    • 方法二:

      import java.util.Date;
      
      public class FirstSpringTest {
          @Test
          public void testFirstSpringCode() {
              // <bean id="date" class="java.util.Date" />
              ApplicationContext appContext = new ClassPathXmlApplicationContext("spring.xml");
              Date date = appContext.getBean("date", Date.class);
          }
      }
      

(4)启用 Log4j2 日志框架

  • 环境配置

    • 引入依赖

      // pom.xml
      <dependency>
          <groupId>org.apache.logging.log4j</groupId>
          <artifactId>log4j-core</artifactId>
          <version>2.19.0</version>
      </dependency>
      
      <dependency>
          <groupId>org.apache.logging.log4j</groupId>
          <artifactId>log4j-slf4j2-impl</artifactId>
          <version>2.19.0</version>
      </dependency>
      
    • 在 src/main/resources 目录下新建文件 log4j2.xml,并写入以下内容

      <?xml version="1.0" encoding="UTF-8" ?>
      
      <configuration>
      
          <loggers>
      <!--        level 指定日志级别,以下是从低到高的排序-->
      <!--        ALL < TRACE < DEBUG < INFO < WARN < ERROR < FATAL < OFF-->
              <root level="DEBUG">
                  <appender-ref ref="spring6log" />
              </root>
          </loggers>
      
          <appenders>
      <!--        输出日志信息到控制台-->
              <console name="spring6log" target="SYSTEM_OUT">
      <!--            控制日志输出的格式-->
                  <PatternLayout pattern="%d{yyyy-MM-dd HH:mm:ss SSS} [%t] %-3level %logger{1024} - %msg%n" />
              </console>
          </appenders>
      
      </configuration>
      
  • 使用 log4j2 记录日志信息

    • 在 test 中的类 FirstSpringTest.java 里写入以下代码:

      import org.slf4j.Logger;
      import org.slf4j.LoggerFactory;
      
      public class FirstSpringTest {
          @Test
          public void testFirstSpringCode() {
              Logger logger = LoggerFactory.getLogger(FirstSpringTest.class);
      
              logger.info("Info Message");
              logger.debug("Debug Message");
              logger.trace("Trace Message");
          }
      }
      
      • 第一步,创建日志记录器对象
      • 第二步,记录日志并根据不同的级别来输出日志

0x04 IoC 的实现

(1)IoC 控制反转

  • 控制反转是一种思想
  • 控制反转的目的
    • 降低程序耦合度
    • 达到 OCP 原则
    • 达到 DIP 原则
  • 反转内容
    • 将对象的创建权力交出去,由第三方容器负责
    • 将对象和对象之间的关系维护权交出去,由第三方容器负责
  • 实现方法
    • 依赖注入 DI

(2)依赖注入

  • Spring 通过依赖注入完成对 Bean 对象的创建和其中属性赋值的管理

  • 依赖注入解释

    • 依赖:对象和对象之间的关联关系
    • 注入:一种数据传递行为,使对象之间产生关系
  • 依赖注入常见形式

    • set 注入

      • 基于 set 方法实现的,底层会通过反射机制调用属性对应的 set 方法而后给属性赋值,此方法要求属性必须对外提供 set 方法
      • 构造 set 方法时,方法名必须以 set 开始
      public void setUserDao(UserDao userDao) {
          this.userDao = userDao;
      }
      
      • Spring 调用对应 set 方法前,需要在 spring.xml 中配置相应 Bean 的属性 property

        • property 标签中包含的 name 属性以首字母小写的方式填写 set 方法名中 set 后的全部内容
        • property 标签中包含的 type 属性填写形参的 Bean 的 id
        <bean id="userDaoBean" class="com.spring6.dao.UserDao" />
        <bean id="userServiceBean" class="com.spring6.service.UserService">
            <property name="userDao" ref="userDaoBean" />
        </bean>
        
    • 构造注入

      • 通过构造方法为属性赋值
      public UserService(UserDao userDao) {
          this.userDao = userDao;
      }
      
      • 在 spring.xml 中配置相应 Bean 的属性 constructor-arg

        • constructor-arg 标签中包含的 index 属性指定构造方法的第 index+1 个参数
        • constructor-arg 标签中包含的 ref 属性指定对应的 Bean 的 id
        <bean id="userDaoBean" class="com.spring6.dao.UserDao" />
        <bean id="userServiceBean" class="com.spring6.service.UserService">
            <constructor-arg index="0" ref="userDaoBean" />
        </bean>
        
      • 或类似于 set 注入方法配置 spring.xml

        <bean id="userDaoBean" class="com.spring6.dao.UserDao" />
        <bean id="userServiceBean" class="com.spring6.service.UserService">
            <constructor-arg name="userDao" ref="userDaoBean" />
        </bean>
        
      • 或根据类型注入

        <bean id="userDaoBean" class="com.spring6.dao.UserDao" />
        <bean id="userServiceBean" class="com.spring6.service.UserService">
            <constructor-arg ref="userDaoBean" />
        </bean>
        

(3)set 注入详解

public void setUserDao(UserDao userDao) {
 this.userDao = userDao;
}

a. 注入外部 Bean

  • Bean 定义到外面,在 property 标签中使用 ref 属性进行注入
<bean id="userDaoBean" class="com.spring6.dao.UserDao" />
<bean id="userServiceBean" class="com.spring6.service.UserService">
    <property name="userDao" ref="userDaoBean" />
</bean>

b. 注入内部 Bean

  • 在 Bean 标签中嵌套着 Bean 标签
<bean id="userServiceBean" class="com.spring6.service.UserService">
    <property name="userDao">
        <bean class="com.spring6.dao.UserDao" />
    </property>
</bean>

c. 注入简单类型

  • 可以通过 set 注入的方式给属性赋值

    • 在 src/main/java 新建 com.spring6.bean.User 类

      package com.spring6.bean;
      
      public class User {
          private String username;
          private String password;
          private int age;
      
          public void setUsername(String username) {
              this.username = username;
          }
      
          public void setPassword(String password) {
              this.password = password;
          }
      
          public void setAge(int age) {
              this.age = age;
          }
      
          @Override
          public String toString() {
              return "User{" +
                      "username='" + username + '\'' +
                      ", password='" + password + '\'' +
                      ", age=" + age +
                      '}';
          }
      }
      
    • 在 src/main/resources/spring.xml 中配置如下,其中重点在于 property 标签的 value 属性用于传值

      <bean id="userBean" class="com.spring6.bean.User">
          <property name="username" value="张三" />
          <property name="password" value="123" />
          <property name="age" value="20" />
      </bean>
      
    • 测试该注入代码如下

      ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.xml");
      User user = applicationContext.getBean("userBean", User.class);
      System.out.println(user);
      
  • 查看简单类型

    • 在 IDEA 中双击 Shift 键,搜索 BeanUtils
    • 在 BeanUtils.class 中按 Ctrl + F12 搜索 isSimpleValueType 即可查看 Spring 中的全部简单类型

d. 级联属性赋值

  • User.java

    private String name;
    
    // 级联注入添加
    private Group group;
    
    public void setName(String name) {
        this.name = name;
    }
    
    // 级联注入添加
    // 对应 spring.xml 中的 group.name
    public Group getGroup() {
        return group;
    }
    
    // 级联注入添加
    public void setGroup(Group group) {
        this.group = group;
    }
    
    @Override
    public String toString() {
        return "User{" +
                "name='" + name + '\'' +
                ", group=" + group +
                '}';
    }
    
  • Group.java

    private String name;
    
    public void setName(String name) {
        this.name = name;
    }
    
    @Override
    public String toString() {
        return "Group{" +
                "name='" + name + '\'' +
                '}';
    }
    
  • Spring.xml

    <bean id="userBean" class="com.spring6.bean.User">
        <property name="name" value="张三" />
    	<property name="group" ref="groupBean" />
        <property name="group.name" value="第一组" />
    </bean>
    
    <bean id="groupBean" class="com.spring6.bean.Group" />
    
  • Test.java

    ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.xml");
    User user = applicationContext.getBean("userBean", User.class);
    System.out.println(user);
    
  • 级联注入重点

    • 需要 get 方法
    • 配置中先指定 ref 后再赋值 value

e. 注入数组

  • User.java

    private String[] hobbies;
    
    public void setHobbies(String[] hobbies) {
        this.hobbies = hobbies;
    }
    
    @Override
    public String toString() {
        return "User{" +
                "hobbies=" + Arrays.toString(hobbies) +
                '}';
    }
    
  • spring.xml

    <bean id="userBean" class="com.spring6.bean.User">
        <property name="hobbies">
            <array>
                <value>吃饭</value>
                <value>睡觉</value>
                <value>打豆豆</value>
            </array>
        </property>
    </bean>
    

f. 注入 List 集合

有序可重复

  • User.java

    private List<String> hobbies;
    
    public void setHobbies(List<String> hobbies) {
        this.hobbies = hobbies;
    }
    
    @Override
    public String toString() {
        return "User{" +
                "hobbies=" + hobbies +
                '}';
    }
    
  • spring.xml

    <bean id="userBean" class="com.spring6.bean.User">
        <property name="hobbies">
            <list>
                <value>吃饭</value>
                <value>睡觉</value>
                <value>打豆豆</value>
            </list>
        </property>
    </bean>
    

g. 注入 Set 集合

无序不可重复

  • User.java

    private Set<String> hobbies;
    
    public void setHobbies(Set<String> hobbies) {
        this.hobbies = hobbies;
    }
    
    @Override
    public String toString() {
        return "User{" +
                "hobbies=" + hobbies +
                '}';
    }
    
  • spring.xml

    <bean id="userBean" class="com.spring6.bean.User">
        <property name="hobbies">
            <set>
                <value>吃饭</value>
                <value>吃饭</value>
                <value>睡觉</value>
                <value>睡觉</value>
                <value>打豆豆</value>
            </set>
        </property>
    </bean>
    
    • 有重复元素不会报错,但是仅输出一次

h. 注入 Map 集合

  • User.java

    private Map<Integer, String> phones;
    
    public void setPhones(Map<Integer, String> phones) {
        this.phones = phones;
    }
    
    @Override
    public String toString() {
        return "User{" +
                "phones=" + phones +
                '}';
    }
    
  • spring.xml

    <bean id="userBean" class="com.spring6.bean.User">
        <property name="phones">
            <map>
                <entry key="1" value="110" />
                <entry key="2" value="119" />
                <entry key="3" value="120" />
            </map>
        </property>
    </bean>
    
    • 对应非简单类型可以使用key-refvalue-ref

i. 注入 Properties

  • User.java

    private Properties properties;
    
    public void setProperties(Properties properties) {
        this.properties = properties;
    }
    
    @Override
    public String toString() {
        return "User{" +
                "properties=" + properties +
                '}';
    }
    
  • spring.xml

    <bean id="userBean" class="com.spring6.bean.User">
        <property name="properties">
            <props>
                <prop key="name">张三</prop>
                <prop key="sex">男</prop>
            </props>
        </property>
    </bean>
    
    • prop 的 key 和 value 只能是 String 型

j. 注入 null 和空字符串

未设置时默认为 null

  • User.java

    private String string1;
    
    private String string2;
    
    public void setString1(String string1) {
        this.string1 = string1;
    }
    
    public void setString2(String string2) {
        this.string2 = string2;
    }
    
    @Override
    public String toString() {
        return "User{" +
                "string1='" + string1 + '\'' +
                ", string2='" + string2 + '\'' +
                '}';
    }
    
  • spring.xml

    <bean id="userBean" class="com.spring6.bean.User">
        <property name="string1">
            <null />
        </property>
    
        <property name="string2" value=""/>
    </bean>
    

k. 注入的值含有特殊字符

  • XML 中有 5 个特殊字符:<>'"&,这些字符会被当做 XML 语法的一部分进行解析

  • 解决方法:

    • 使用转义字符代替

      特殊字符 转义字符
      > &gt;
      < &lt;
      ' &apos;
      " &quot;
      & &amp;
      • 在 spring.xml 中配置值

        <bean id="userBean" class="com.spring6.bean.User">
            <property name="string1" value="2 &lt; 3" />
        </bean>
        
    • 将含有特殊字符的字符串放到<![CDATA[]]>

      • 在 spring.xml 中配置值

        <bean id="userBean" class="com.spring6.bean.User">
            <property name="string2">
                <value><![CDATA[2 < 3]]></value>
            </property>
        </bean>
        

(4)p 命名空间注入

  • 目的:简化 set 注入配置

  • 前提条件

    • 在 XML 头部信息中添加 p 命名空间的配置信息:xmlns:p="http://www.springframework.org/schema/p"
    • 需要对应的属性提供 setter 方法
  • User.java

    private String string;
    
    private Date date;
    
    public void setString(String string) {
        this.string = string;
    }
    
    public void setDate(Date date) {
        this.date = date;
    }
    
    @Override
    public String toString() {
        return "User{" +
                "string='" + string + '\'' +
                ", date=" + date +
                '}';
    }
    
  • spring.xml

    <beans xmlns="http://www.springframework.org/schema/beans"
           xmlns:p="http://www.springframework.org/schema/p"
           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="userBean" class="com.spring6.bean.User" p:string="字符串" p:date-ref="dateBean" />
    
        <bean id="dateBean" class="java.util.Date" />
    
    </beans>
    

(5)c 命名空间注入

  • 目的:简化构造方法注入配置

  • 前提条件

    • 在 XML 头部信息中添加 p 命名空间的配置信息:xmlns:c="http://www.springframework.org/schema/c"
    • 需要提供构造方法
  • User.java

    private String string;
        
    private int number;
        
    public User(String string, int number) {
        this.string = string;
        this.number = number;
    }
    
    @Override
    public String toString() {
        return "User{" +
                "string='" + string + '\'' +
                ", number=" + number +
                '}';
    }
    
  • spring.xml

    <beans xmlns="http://www.springframework.org/schema/beans"
           xmlns:c="http://www.springframework.org/schema/c"
           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="userBean" class="com.spring6.bean.User" c:_0="字符串" c:number="123" />
    
    </beans>
    

(6)util 命名空间

  • 目的:配置复用

  • 前提条件:在 XML 头部信息中添加配置信息

    xmlns:util="http://www.springframework.org/schema/util"
    
    xsi:schemaLocation="http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util.xsd"
    
  • User.java(User2.java)

    private Properties properties;
    
    public void setProperties(Properties properties) {
        this.properties = properties;
    }
    
    @Override
    public String toString() {
        return "User{" +
                "properties=" + properties +
                '}';
    }
    
  • spring.xml

    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="http://www.springframework.org/schema/beans"
           xmlns:util="http://www.springframework.org/schema/util"
           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
                               http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util.xsd">
    
        <util:properties id="prop">
            <prop key="key1">value1</prop>
            <prop key="key2">value2</prop>
            <prop key="key3">value3</prop>
        </util:properties>
    
        <bean id="userBean" class="com.spring6.bean.User">
            <property name="properties" ref="prop" />
        </bean>
    
        <bean id="userBean2" class="com.spring6.bean.User2">
            <property name="properties" ref="prop" />
        </bean>
    
    </beans>
    
  • Test.java

    ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.xml");
    User user = applicationContext.getBean("userBean", User.class);
    User2 user2 = applicationContext.getBean("userBean2", User2.class);
    System.out.println(user);
    System.out.println(user2);
    

(7)基于 XML 的自动装配

  • Spring 可以完成自动化的注入,又称自动装配,基于 set 方法

  • 方式:根据名称或类型装配

  • 根据名称

    • spring.xml

      <bean id="userService" class="com.spring6.service.UserService" autowire="byName" />
      <bean id="userDao" class="com.spring6.dao.UserDao" />
      
      • 其中,在第二行的 bean 里,属性 id 必须为 set 方法名中除set外其他内容首字母小写
  • 根据类型

    • spring.xml

      <bean id="userDao" class="com.spring6.dao.UserDao"></bean>
      <bean id="userDao2" class="com.spring6.dao.UserDao2"></bean>
      <bean id="userService" class="com.spring6.service.UserService" autowire="byType" />
      

(8)Spring 引入外部属性配置

  • 设置外部属性配置。在 src/main/resources 下新建 setting.properties

    username=root
    password=1234
    
  • 引入外部 properties 文件。在 spring.xml 中进行下述操作

    • 引入 context 命名空间

    • 指定属性配置文件的路径

    • 使用${key}方法从外部属性配置文件中取值

      • key 值首先加载当前操作系统默认的值,导致username对应的 value 为当前系统管理员名称,为避免上述问题并使 key 名称易于理解,可使用添加前缀的操作

        user.username=root
        user.password=1234
        
    <beans xmlns="http://www.springframework.org/schema/beans"
           xmlns:context="http://www.springframework.org/schema/context"
           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
                               http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
    
        <context:property-placeholder location="setting.properties" />
    
        <bean id="userBean" class="com.spring6.bean.User">
            <property name="username" value="${user.username}" />
            <property name="password" value="${user.password}" />
        </bean>
    
    </beans>
    

0x05 Bean 的作用域

(1)singleton

Singleton:单例

  • 默认情况下,Bean 是单例的,其在 Spring 上下文初始化的时候实例化,每一次调用getBean()方法时,都返回那个单例对象
  • 在配置 Bean 时可以通过添加 scope 属性值来设置 Bean 是单例或多例

(2)prototype

Prototype:原型 / 多例

  • 当 Bean 的 scope 属性设置为Prototype时,Spring 上下文初始化时不会初始化这些 Bean,每一次调用getBean()方法时,才实例化该对象

(3)其他 Scope

在 pom.xml 中添加依赖 SpringMVC,此时该 Spring 项目为 Web 项目

<dependency>
 <groupId>org.springframework</groupId>
 <artifactId>spring-webmvc</artifactId>
 <version>6.0.4</version>
</dependency>
  • request
    • 仅限于在 Web 应用中使用
    • 一次请求中只有一个 Bean
  • session
    • 仅限于在 Web 应用中使用
    • 一次会话中只有一个 Bean
  • global session
    • portlet 应用中专用的
    • 如果在 Servlet 的 Web 应用中使用时,其效果与 session 相同
  • application
    • 仅限于在 Web 应用中使用
    • 一个应用对应一个 Bean
  • websocket
    • 仅限于在 Web 应用中使用
    • 一个 websocket 生命周期对应一个 Bean
  • 自定义 Scope

(4)自定义 Scope

以自定义线程级 scope 为例:一个线程对应一个 Bean

  • 自定义 Scope,实现 Scope 接口

    • Spring 内置了线程范围的类:org.springframework.context.support.SimpleThreadScope
  • 将自定义的 Scope 注册到 Spring 容器中

    <!-- filename: spring-scope.xml -->
    
    <bean class="org.springframework.beans.factory.config.CustomScopeConfigurer">
        <property name="scopes">
            <map>
                <entry key="myThread">
                    <bean class="org.springframework.context.support.SimpleThreadScope" />
                </entry>
            </map>
        </property>
    </bean>
    
  • 使用 Scope

    <!-- filename: spring-scope.xml -->
    <bean id="userBean" class="com.spring6.bean.User" scope="myThread" />
    
  • 测试

    ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.xml");
    User user = applicationContext.getBean("userBean", User.class);
    System.out.println(user);
    
    new Thread(() -> {
        User user2 = applicationContext.getBean("userBean", User.class);
        System.out.println(user2);
    }).start();
    

0x06 GoF 之工厂模式

  • 设计模式定义:一种可以被重复利用的解决方案

  • GoF:Gang of Four,四人组,出自《设计模式》

    • 《设计模式》描述了 23 种设计模式,可分成三大类

      1. 创建型:解决对象创建问题

        单例模式、工厂方法模式、抽象工厂模式、建造者模式、原型模式

      2. 结构型:一些类或对象组合在一起的经典结构

        代理模式、装饰模式、适配器模式、组合模式、享元模式、外观模式、桥接模式

      3. 行为型:解决类或对象之间交互问题

        策略模式、模板方法模式、责任链模式、观察者模式、迭代子模式、命令模式、备忘录模式、状态模式、访问者模式、中介模式、解释器模式

(1)工厂模式的三种形态

  • 简单工厂模式(Simple Factory)
    • 又称静态工厂方法模式,不属于 23 种设计模式之一,是工厂方法模式的一种特殊实现
  • 工厂方法模式(Factory Method)
  • 抽象工厂模式(Abstract Factory)

(2)简单工厂模式

  • 抽象产品角色

    // filename: Weapon.java
    
    public abstract class Weapon {
        public abstract void attack();
    }
    
  • 具体产品角色

    // filename: Tank.java
    
    public class Tank extends Weapon {
        @Override
        public void attack() {
            System.out.println("Tank attack");
        }
    }
    
  • 工厂类角色

    // filename: WeaponFactory.java
    
    public class WeaponFactory {
        public static Weapon get(String weaponType) {
            if ("Tank".equals(weaponType)) {
                return new Tank();
            } else {
                throw new RuntimeException("Weapon type not supported");
            }
        }
    }
    
  • 测试

    // filename: Test.java
    
    public class Test {
        public static void main(String[] args) {
            Weapon tank = WeaponFactory.get("Tank");
            tank.attack();
        }
    }
    
  • 简单工厂模式优点

    • 客户端程序不需要关心对象的创建细节,需要时索要即可,初步实现了生产和消费的责任分离
  • 简单工厂模式缺点

    • 工厂类如果出现问题会导致整个系统瘫痪
    • 不符合 OCP 开闭原则,在系统扩展时需要修改工厂类
  • Spring 中的 BeanFactory 就使用了简单工厂模式

(3)工厂方法模式

  • 抽象产品角色

    // filename: Weapon.java
    
    public abstract class Weapon {
        public abstract void attack();
    }
    
  • 具体产品角色

    // filename: Tank.java
    
    public class Tank extends Weapon {
        @Override
        public void attack() {
            System.out.println("Tank attack");
        }
    }
    
  • 抽象工厂角色

    // filename: WeaponFactory.java
    
    public abstract class WeaponFactory {
        public abstract Weapon get();
    }
    
  • 具体工厂角色

    // filename: TankFactory.java
    
    public class TankFactory extends WeaponFactory {
        @Override
        public Weapon get() {
            return new Tank();
        }
    }
    
  • 测试

    // filename: Test.java
    
    public class Test {
        public static void main(String[] args) {
            WeaponFactory weaponFactory = new TankFactory();
            Weapon tank = weaponFactory.get();
            tank.attack();
        }
    }
    
  • 工厂方法模式优点

    • 创建对象时仅需要知到名称即可
    • 扩展性高
    • 屏蔽产品的具体实现
  • 工厂方法模式缺点

    • 每增加一个产品就需要对应增加一个工厂,使得系统中类的个数成倍上升,增加了系统复杂度

(4)抽象工厂模式

  • 对比

    工厂方法模式 抽象工厂模式
    针对目标 一个产品系列 多个产品系列
    实现效果 一个产品系列一个工厂类 多个产品系列一个工厂类
  • 特点:抽象工厂模式是指当有多个抽象角色时,使用的一种工厂模式,抽象工厂模式可以向客户端提供一个接口,使客户端在不必指定产品的具体情况下,创建多个产品族中的产品对象。多个抽象产品类中,每个抽象产品类可以派生出多个具体产品类;一个抽象工厂类可以创建出多个具体产品类的实例

  • 抽象工厂模式优点:

    • 当一个产品族中的多个对象被设计成一起工作时,它能保证客户端始终只使用同一个产品族中的对象
  • 抽象工厂模式缺点

    • 产品族扩展十分困难

0x07 Bean 的实例化方式

(1)通过构造方法实例化

  • 默认情况下会调用 Bean 的无参数构造方法

(2)通过简单工厂模式实例化

  • 创建产品类 Star.java

    public class Star {
        public Star() {
            System.out.println("Star 无参构造方法");
        }
    }
    
  • 创建工厂类 StarFactory.java

    • 静态方法
    public class StarFactory {
        public static Star get() {
            return new Star();
        }
    }
    
  • 在配置文件中实例化 Bean

    <bean id="starFactory" class="com.spring.bean.StarFactory" factory-method="get"/>
    
  • 在测试文件中测试

    @Test
    public void test() {
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.xml");
        Star star = applicationContext.getBean("starFactory", Star.class);
        System.out.println(star);
    }
    

(3)通过 factory-bean 实例化

  • 具体产品类:Tank.java

    public class Tank {
        public Tank() {
            System.out.println("Tank 无参构造");
        }
    }
    
  • 具体工厂类:TankFactory.java

    • 实例方法
    public class TankFactory {
        public Tank get() {
            return new Tank();
        }
    }
    
  • 在配置文件中实例化 Bean

    <bean id="tankFactory" class="com.spring.bean.TankFactory" />
    <bean id="tank" factory-bean="tankFactory" factory-method="get" />
    
  • 在测试文件中测试

    @Test
    public void test() {
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.xml");
        Tank tank = applicationContext.getBean("tank", Tank.class);
        System.out.println(tank);
    }
    

(4)通过 FactoryBean 接口实例化

  • 在 Spring 中,当编写了类直接实现 FactoryBean 接口后,factory-bean 会自动指向实现 FactoryBean 接口的类,factory-method 会自动指向getObject()方法

  • 创建产品类:Person.java

    public class Person {
        public Person() {
            System.out.println("person");
        }
    }
    
  • 创建工厂类:PersonFactory.java

    import org.springframework.beans.factory.FactoryBean;
    
    public class PersonFactory implements FactoryBean<Person> {
        @Override
        public Person getObject() throws Exception {
            return new Person();
        }
    
        @Override
        public Class<?> getObjectType() {
            return null;
        }
    
        @Override
        public boolean isSingleton() {
            return true;
        }
    }
    
  • 在 spring.xml 配置文件中配置

    <bean id="personFactory" class="com.spring.bean.PersonFactory" />
    
  • 测试

    @Test
    public void test() {
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.xml");
        Person person = applicationContext.getBean("personFactory", Person.class);
        System.out.println(person);
    }
    

(5)BeanFactory 与 FactoryBean 的区别

  • BeanFactory 是 Spring IoC 容器的顶级对象,负责创建 Bean 对象
  • FactoryBean 是一个 Bean,能够辅助 Spring 实例化其他 Bean 对象的一个 Bean
    • 在 Spring 中,Bean 可分为普通 Bean 和 工厂 Bean

(6)注入自定义 Date

  • Student.java

    private Date birth;
    
    public void setBirth(Date birth) {
        this.birth = birth;
    }
    
    @Override
    public String toString() {
        return "Student{" +
                "birth=" + birth +
                '}';
    }
    
  • StudentFactory.java

    public class StudentFactory implements FactoryBean<Date> {
        private String strDate;
    
        public StudentFactory(String strDate) {
            this.strDate = strDate;
        }
    
        @Override
        public Date getObject() throws Exception {
            SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
            Date date = sdf.parse(strDate);
            return date;
        }
    
        @Override
        public Class<?> getObjectType() {
            return null;
        }
    }
    
  • spring.xml

    <bean id="studentFactory" class="com.spring.bean.StudentFactory">
        <constructor-arg index="0" value="2000-01-01" />
    </bean>
    
    <bean id="student" class="com.spring.bean.Student">
        <property name="birth" ref="studentFactory" />
    </bean>
    
  • Test.java

    @Test
    public void test() {
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.xml");
        Student student = applicationContext.getBean("student", Student.class);
        System.out.println(student);
    }
    

0x08 Bean 的生命周期

生命周期的本质在于程序在某个时间点调用了某个类的某个方法

Bean 生命周期的管理可以参考 Spring 源码中 AbstractAutowireCapableBeanFactory 类的 doCreateBean()方法

(1)五步

  1. 实例化 Bean
  2. Bean 属性赋值
  3. 初始化 Bean
  4. 使用 Bean
  5. 销毁 Bean
  • User.java

    public class User {
        private String name;
    
        public void destroyBean() {
            System.out.println("Step 5");
        }
    
        public void initBean() {
            System.out.println("Step 3");
        }
    
        public void setName(String name) {
            System.out.println("Step 2");
            this.name = name;
        }
    
        public User() {
            System.out.println("Step 1");
        }
    }
    
  • spring.xml

    <bean id="user" class="com.spring.bean.User" init-method="initBean" destroy-method="destroyBean">
        <property name="name" value="字符串" />
    </bean>
    
  • Test.java

    @Test
    public void test() {
        ApplicationContext applicationContext = new sPathXmlApplicationContext("spring.xml");
        User user = applicationContext.getBean("user", User.class);
        System.out.println("Step 4  " + user);
    
        // 手动关闭 Spring 容器后 Spring 才会销毁 Bean
        ClassPathXmlApplicationContext context = (ClassPathXmlApplicationContext) applicationContext;
        context.close();
    }
    
  • 测试结果

    Step 1
    Step 2
    Step 3
    Step 4 com.spring.bean.User@1165b38
    Step 5
    

(2)七步

  1. 实例化 Bean
  2. Bean 属性赋值
  3. ?执行“Bean 后处理器”的 before 方法
  4. 初始化 Bean
  5. ?执行“Bean 后处理器”的 after 方法
  6. 使用 Bean
  7. 销毁 Bean
  • 编写一个类实现 BeanPostProcessor 类,重写 before 和 after 方法

    • 日志 Bean 后处理器:LogBeanPostProcessor.java

      public class LogBeanPostProcessor implements BeanPostProcessor {
          @Override
          public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
              // bean:创建的 Beam 对象
              // beanName:Bean 的名字
              System.out.println("Step 3 before 方法");
              return BeanPostProcessor.super.postProcessBeforeInitialization(bean, beanName);
          }
      
          @Override
          public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
              System.out.println("Step 5 after 方法");
              return BeanPostProcessor.super.postProcessAfterInitialization(bean, beanName);
          }
      }
      
  • 在 spring.xml 中配置“Bean 后处理器”

    • 该 Bean 后处理器将作用于整个配置文件中所有的 Bean

      <bean class="com.spring.bean.LogBeanPostProcessor" />
      
  • 测试结果

    Step 1
    Step 2
    Step 3 before 方法
    Step 4
    Step 5 after 方法
    Step 6 com.spring.bean.User@6a78afa0
    Step 7
    

(3)十步

  1. 实例化 Bean

  2. Bean 属性赋值

  3. ?检查 Bean 是否实现了 Aware 的相关接口,并设置相关依赖

    Aware 相关接口及说明

    • BeanNameAware:将 Bean 的名字传递给 Bean
    • BeanClassLoaderAware:将加载该 Bean 的类加载器传递给 Bean
    • BeanFactoryAware:将 Bean 工厂对象传递给 Bean
  4. 执行“Bean 后处理器”的 before 方法

  5. ?检查 Bean 是否实现了 InitializingBean 接口,并调用接口方法

  6. 初始化 Bean

  7. 执行“Bean 后处理器”的 after 方法

  8. 使用 Bean

  9. ?检查 Bean 是否实现了 DisposableBean 接口,并调用接口方法

  10. 销毁 Bean

  • 调用接口

    public class User implements BeanNameAware, BeanClassLoaderAware, BeanFactoryAware, InitializingBean, DisposableBean {
        
        // ...
    
        @Override
        public void setBeanClassLoader(ClassLoader classLoader) {
            System.out.println("Step 3 classloader: " + classLoader);
        }
    
        @Override
        public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
            System.out.println("Step 3 beanFactory: " + beanFactory);
        }
    
        @Override
        public void setBeanName(String name) {
            System.out.println("Step 3 beanName: " + name);
        }
    
        @Override
        public void afterPropertiesSet() throws Exception {
            System.out.println("Step 5");
        }
    
        @Override
        public void destroy() throws Exception {
            System.out.println("Step 9");
        }
    }
    
  • 测试结果

    Step 1
    Step 2
    Step 3 beanName: user
    Step 3 classloader: jdk.internal.loader.ClassLoaders$AppClassLoader@78308db1
    Step 3 beanFactory: org.springframework.beans.factory.support.DefaultListableBeanFactory@229d10bd: defining beans [com.spring.bean.LogBeanPostProcessor#0,user]; root of factory hierarchy
    Step 4 before 方法
    Step 5
    Step 6
    Step 7 after 方法
    Step 8 com.spring.bean.User@815b41f
    Step 9
    Step 10
    

(4)多例 Bean 的生命周期

  • Spring 根据 Bean 的作用域来选择管理方式
    • 对于单例情况下,Spring 能够精确管理 Bean 的创建、初始化和销毁
    • 对于多例情况下,Spring 仅能负责创建,不能跟踪其生命周期

(5)使用 Spring 管理手动创建的对象

  • Student.java

    public class Student {
    }
    
  • Test.java

    @Test
    public void test() {
        // 创建对象
        Student student = new Student();
        System.out.println(student);
    
        // 将 student 对象纳入 Spring 容器进行管理
        DefaultListableBeanFactory factory = new DefaultListableBeanFactory();
        factory.registerSingleton("studentBean", student);
    
        // 从 Spring 容器中获取
        Object studentBean = factory.getBean("studentBean");
        System.out.println(studentBean);
    }
    

0x09 Bean 的循环依赖

A 对象中有属性 B,B 对象中有属性 A,此时出现循环依赖

(1)单例和 set 注入时产生的循环依赖

任意一个 Bean 实例化后就会被立即曝光

  • Husband.java

    private String name;
    
    private Wife wife;
    
    public void setName(String name) {
        this.name = name;
    }
    
    public void setWife(Wife wife) {
        this.wife = wife;
    }
    
    public String getName() {
        return name;
    }
    
    @Override
    public String toString() {
        return "Husband{" +
                "name='" + name + '\'' +
                ", wife=" + wife.getName() +
                '}';
    }
    
  • Wife.java

    private String name;
    
    private Husband husband;
    
    public void setName(String name) {
        this.name = name;
    }
    
    public void setHusband(Husband husband) {
        this.husband = husband;
    }
    
    public String getName() {
        return name;
    }
    
    @Override
    public String toString() {
        return "Wife{" +
                "name='" + name + '\'' +
                ", husband=" + husband.getName() +
                '}';
    }
    
  • spring.xml

    <bean id="husband" class="com.spring.bean.Husband" scope="singleton">
        <property name="name" value="Husband" />
        <property name="wife" ref="wife" />
    </bean>
    
    <bean id="wife" class="com.spring.bean.Wife" scope="singleton">
        <property name="name" value="Wife" />
        <property name="husband" ref="husband" />
    </bean>
    
  • Test.java

    @Test
    public void test() {
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.xml");
        Husband husband = applicationContext.getBean("husband", Husband.class);
        Wife wife = applicationContext.getBean("wife", Wife.class);
        System.out.println(husband);
        System.out.println(wife);
    }
    

(2)多例和 set 注入时产生的循环依赖

  • 多例时任意一个 Bean 实例化后 会被立即曝光
  • 一个多例,一个单例时不会报错

(3)构造方法时产生的循环依赖

  • 无法解决

0x0A 回顾反射机制

(1)分析方法四要素

  1. 确定需要调用的对象
  2. 确定需要调用的方法
  3. 确定需要传递的参数
  4. 获取方法执行的结果

(2)获取 Method

(3)反射调用 Method

  • SomeService.java

    public void function() {
        System.out.println("function 1");
    }
    
    public void function(String s) {
        System.out.println("function 2");
    }
    
    public void function(String s, int i) {
        System.out.println("function 3");
    }
    
  • Test.java

    // 获取类
    Class<?> clazz = Class.forName("com.spring.bean.SomeService");
    
    // 获取方法
    Method functionMethods = clazz.getDeclaredMethod("function", String.class, int.class);
    
    // 调用方法
    Constructor<?> constructor = clazz.getDeclaredConstructor();
    Object obj = constructor.newInstance();
    Object value = functionMethods.invoke(obj, "字符串", 123);
    System.out.println(value);
    

0x0B IoC 注解式开发

(1)回顾注解

  • 注解的存在主要是为了简化 XML 的配置,Spring6 倡导全注解开发

  • 自定义注解:Component.java

    package com.java.annotation;
    
    import java.lang.annotation.ElementType;
    import java.lang.annotation.Retention;
    import java.lang.annotation.RetentionPolicy;
    import java.lang.annotation.Target;
    
    @Target(value = {ElementType.TYPE})
    @Retention(value = RetentionPolicy.RUNTIME)
    public @interface Component {
        String value();
    }
    
    • 以上自定义了一个注解:Component
    • 该注解上方修饰的注解包括:Target、Retention,称为元注解
      • Target:设置 Component 可以出现的位置,表示该注解只能用在类和接口上
      • Retention:设置 Component 保持性策略,表示该注解可以被反射机制读取
      • String value():属性类型为 String,属性名为 value
  • 使用自定义注解:User.java

    // @Component(value = "user")
    @Component("user")
    public class User {
    }
    
    • 当且仅当属性名仅有 value 时,使用注解可以写成@Component("user")
  • 通过反射机制读取注解:Test.java

    package com.java.annotation;
    
    public class Test {
        public static void main(String[] args) throws Exception {
            // 获取类
            Class<?> aClass = Class.forName("com.java.annotation.User");
    
            // 判断类是否有注解
            if(aClass.isAnnotationPresent(Component.class)) {
                // 获取类的注解
                Component annotation = aClass.getAnnotation(Component.class);
    
                // 访问注解属性
                System.out.println(annotation.value());
            }
        }
    }
    
  • 组件扫描原理:ComponentScan.java

    • 路径不可以有空格
    package com.mypackage.client;
    
    import com.mypackage.annotation.Component;
    
    import java.io.*;
    import java.net.URL;
    import java.util.*;
    
    public class ComponentScan {
        public static void main(String[] args) {
            Map<String, Object> beanMap = new HashMap<>();
            String packageName = "com.mypackage.bean";
            String packagePath = packageName.replaceAll("\\.", "/");
            URL url = ClassLoader.getSystemClassLoader().getResource(packagePath);
            File file = new File(url.getPath());
            File[] files = file.listFiles();
            Arrays.stream(files).forEach(f -> {
                String className = packageName + "." + f.getName().split("\\.")[0];
                try {
                    Class<?> clazz = Class.forName(className);
                    if (clazz.isAnnotationPresent(Component.class)) {
                        Component component = clazz.getAnnotation(Component.class);
                        String beanId = component.value();
                        Object bean = clazz.newInstance();
                        beanMap.put(beanId, bean);
                    }
                } catch (Exception e) {
                    e.printStackTrace();
                }
            });
            System.out.println(beanMap);
        }
    }
    

(2)声明 Bean 的注解

  • 负责声明 Bean 的常见注解

    • @Component

      @Target(value = {ElementType.TYPE})
      @Retention(value = RetentionPolicy.RUNTIME)
      @Documented
      @Indexed
      public @interface Component {
          String value() default "";
      }
      
    • @Controller:表示层

      @Target({ElementType.TYPE})
      @Retention(RetentionPolicy.RUNTIME)
      @Documented
      @Component
      public @interface Controller {
          @AliasFor( annotation = Component.class )
          String value() default "";
      }
      
    • @Service:业务层

      @Target({ElementType.TYPE})
      @Retention(RetentionPolicy.RUNTIME)
      @Documented
      @Component
      public @interface Service {
          @AliasFor( annotation = Component.class )
          String value() default "";
      }
      
    • @Repository:持久层

      @Target({ElementType.TYPE})
      @Retention(RetentionPolicy.RUNTIME)
      @Documented
      @Component
      public @interface Repository {
          @AliasFor( annotation = Component.class )
          String value() default "";
      }
      

(3)Spring 注解的使用

  1. 加入 aop 依赖

    当在 Maven 中加入 spring-context 依赖后,会自动关联 aop 依赖

  2. 在配置文件中添加 context 命名空间

    <beans
    xmlns:context="http://www.springframework.org/schema/context"
    xsi:schemaLocation="http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
    
  3. 在配置文件中指定扫描的包

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

    如果需要扫描多个包,则可以使用以下方法:

    • <context:component-scan base-package="com.spring.bean, com.spring.bean2" />
      
    • <context:component-scan base-package="com.spring" />
      
  4. 在 Bean 类上使用注解

    package com.spring.bean;
    
    import org.springframework.stereotype.Component;
    
    @Component(value = "userBean")
    public class User {
    }
    

(4)选择性实例化 Bean

举例:对使用 Controller 注解的 Bean 实例化

  • 方法一

    <context:component-scan base-package="com.spring.bean" use-default-filters="false">
        <context:include-filter type="annotation" expression="org.springframework.stereotype.Controller" />
    </context:component-scan>
    
    • 设置 use-default-filters="false"
      • false 表示指定的包中所有声明 Bean 的注解失效
    • context:include-filter 中指定允许开启 Bean 实例化的注解
  • 方法二

    <context:component-scan base-package="com.spring.bean" use-default-filters="true">
        <context:exclude-filter type="annotation" expression="org.springframework.stereotype.Component" />
        <context:exclude-filter type="annotation" expression="org.springframework.stereotype.Service" />
        <context:exclude-filter type="annotation" expression="org.springframework.stereotype.Repository" />
        <context:exclude-filter type="annotation" expression="org.springframework.stereotype.Indexed" />
    </context:component-scan>
    
    • 设置 use-default-filters="true"
      • true 表示指定的包中所有声明 Bean 的注解有效
    • context:exclude-filter 中指定不允许开启 Bean 实例化的注解

(5)负责注入的注解

a. @Value

  • 当属性的类型是简单类型时使用

  • User.java

    @Value(value = "张三")
    private String name;
    @Value(value = "20")
    private int age;
    
    @Override
    public String toString() {
        return "User{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
    

b. @Autowired & @Qualifier

  • @Autowired 可以注入非简单类型

    • 该注解存在 required 属性
      • 默认 true ,此时被注入的 Bean 必须存在
      • 修改为 false 时 Bean 是否存在不影响
    • 该注解作用为根据类型 byType 进行自动装配
  • UserDao.java

    public interface UserDao {
        void insert();
    }
    
  • UserService.java

    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.stereotype.Service;
    
    @Service
    public class UserService {
        @Autowired
        protected UserDao userDao;
        public void generate() {
            userDao.insert();
        }
    }
    
  • UserImpl.java

    import org.springframework.stereotype.Repository;
    
    @Repository
    public class UserImpl implements UserDao {
        @Override
        public void insert() {
            System.out.println("UserImpl");
        }
    }
    
  • @Autowired@Qualifier 联合可以根据名字进行装配

c. @Resource

  • 作用:完成非简单类型注入

  • 特性:

    • 作为 JDK 一部分,更具备通用性
    • 默认根据名称装配 byName,未指定时使用属性名;当 name 找不到时会自动启用 byType装配
    • 用在属性上或 setter 方法上
  • 注解属于 JDK 扩展包,不在 JDK 中,需要额外引入,pom.xml

    <dependency>
        <groupId>jakarta.annotation</groupId>
        <artifactId>jakarta.annotation-api</artifactId>
        <version>2.1.1</version>
    </dependency>
    
  • 使用

    • UserDao.java

      package com.spring.bean;
      
      public interface UserDao {
          void insert();
      }
      
    • UserImpl.java

      package com.spring.bean;
      
      import org.springframework.stereotype.Repository;
      
      @Repository("userImpl")
      public class UserImpl implements UserDao {
          @Override
          public void insert() {
              System.out.println("UserImpl");
          }
      }
      
    • UserService.java

      package com.spring.bean;
      
      import jakarta.annotation.Resource;
      import org.springframework.stereotype.Service;
      
      @Service
      public class UserService {
          @Resource(name = "userImpl")
          private UserDao userDao;
          public void insertUser() {
              userDao.insert();
          }
      }
      
    • spring.xml

      <context:component-scan base-package="com.spring.bean" />
      
    • Test.java

      @Test
      public void test() {
          ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.xml");
          UserService userService = applicationContext.getBean("userService", UserService.class);
          userService.insertUser();
      }
      

(6)全注解开发

  • 使用全注解开发方式时需要写配置类来替代 Spring 配置文件(spring.xml)

  • 使用

    • 在 src/main/java/com.spring.bean 目录下新建配置类文件 SpringConfig.java

      package com.spring.bean;
      
      import org.springframework.context.annotation.ComponentScan;
      import org.springframework.context.annotation.Configuration;
      
      @Configuration
      @ComponentScan({"com.spring", "com.spring.bean"})
      public class SpringConfig {
      }
      
    • Test.java

      package com.spring.test;
      
      import com.spring.bean.SpringConfig;
      import com.spring.bean.UserService;
      import org.junit.Test;
      import org.springframework.context.annotation.AnnotationConfigApplicationContext;
      
      public class SpringTest {
          @Test
          public void test() {
              AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(SpringConfig.class);
              UserService userService = context.getBean("userService", UserService.class);
              userService.insertUser();
          }
      }
      

0x0C JDBCTemplate

JDBCTemplate 是 Spring 提供的一个 JDBC 模板类,是对 JDBC 的封装,简化 JDBC 代码

(1)环境配置

  • 新建数据库

    CREATE SCHEMA spring DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci ;
    
  • 新建表

    CREATE TABLE spring.user (
        id int AUTO_INCREMENT NOT NULL,
        name varchar(225),
        age int,
        PRIMARY KEY(id)
    );
    
  • 插入数据

    INSERT INTO spring.user (name, age) VALUES ('张三', 30);
    INSERT INTO spring.user (name, age) VALUES ('李四', 40);
    
  • 新建 src/main/java/com.spring.bean/User.java

    package com.spring.bean;
    
    public class User {
        private Integer id;
        private String name;
        private Integer age;
    
        public User() {}
    
        public User(Integer id, String name, Integer age) {
            this.id = id;
            this.name = name;
            this.age = age;
        }
    
        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 Integer getAge() { return age; }
        public void setAge(Integer age) { this.age = age; }
    
        @Override
        public String toString() {
            return "User{" +
                    "id=" + id +
                    ", name='" + name + '\'' +
                    ", age=" + age +
                    '}';
        }
    }
    
  • 新建 src/main/java/com.spring.bean/MyDataSource.java

    package com.spring.bean;
    
    import javax.sql.DataSource;
    import java.io.PrintWriter;
    import java.sql.Connection;
    import java.sql.DriverManager;
    import java.sql.SQLException;
    import java.sql.SQLFeatureNotSupportedException;
    import java.util.logging.Logger;
    
    public class MyDataSource implements DataSource {
    
        private String driver;
        private String url;
        private String username;
        private String password;
    
        public void setDriver(String driver) { this.driver = driver; }
        public void setUrl(String url) { this.url = url; }
        public void setUsername(String username) { this.username = username; }
        public void setPassword(String password) { this.password = password; }
    
        @Override
        public Connection getConnection() throws SQLException {
            try {
                // 注册驱动
                Class.forName(driver);
    
                // 获取连接
                Connection connection = DriverManager.getConnection(url, username, password);
    
                return connection;
            } catch (ClassNotFoundException e) {
                e.printStackTrace();
            }
            return null;
        }
    
        @Override
        public Connection getConnection(String username, String password) throws SQLException { return null; }
        @Override
        public PrintWriter getLogWriter() throws SQLException { return null; }
        @Override
        public void setLogWriter(PrintWriter out) throws SQLException {}
        @Override
        public void setLoginTimeout(int seconds) throws SQLException {}
        @Override
        public int getLoginTimeout() throws SQLException { return 0; }
        @Override
        public Logger getParentLogger() throws SQLFeatureNotSupportedException { return null; }
        @Override
        public <T> T unwrap(Class<T> iface) throws SQLException { return null; }
        @Override
        public boolean isWrapperFor(Class<?> iface) throws SQLException { return false; }
    }
    
  • 在 pom.xml 添加依赖

    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-jdbc</artifactId>
        <version>6.0.4</version>
    </dependency>
    
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <version>8.0.31</version>
    </dependency>
    
  • 在 src/main/resource/spring.xml 中配置 Bean

    <bean id="datasource" class="com.spring.bean.MyDataSource">
        <property name="driver" value="com.mysql.cj.jdbc.Driver" />
        <property name="url" value="jdbc:mysql://localhost:3306/spring" />
        <property name="username" value="root" />
        <property name="password" value="root" />
    </bean>
    
    <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
        <property name="dataSource" ref="datasource" />
    </bean>
    
  • 测试

    @Test
    public void test() {
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.xml");
        JdbcTemplate jdbcTemplate = applicationContext.getBean("jdbcTemplate", JdbcTemplate.class);
        System.out.println(jdbcTemplate);
    }
    

(2)增 / 删 / 改

在测试文件中进行增加操作

a. 增加

  • 单值

    @Test
    public void test() {
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.xml");
        JdbcTemplate jdbcTemplate = applicationContext.getBean("jdbcTemplate", JdbcTemplate.class);
        
        // 插入操作
        String sql = "INSERT INTO user (name, age) VALUES (?, ?)";
        int count = jdbcTemplate.update(sql, "王五", 50);
        System.out.println(count);
    }
    
  • 多值

    @Test
    public void test() {
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.xml");
        JdbcTemplate jdbcTemplate = applicationContext.getBean("jdbcTemplate", JdbcTemplate.class);
        String sql = "INSERT INTO user (name, age) VALUES (?, ?)";
        Object[] objs1 = {"a", 1};
        Object[] objs2 = {"b", 2};
        Object[] objs3 = {"c", 3};
        List<Object[]> list = new ArrayList<>();
        list.add(objs1);
        list.add(objs2);
        list.add(objs3);
        int[] count = jdbcTemplate.batchUpdate(sql, list);
        System.out.println(Arrays.toString(count));
    }
    

b. 修改

  • 单值

    String sql = "UPDATE user SET name = ?, age = ? WHERE id = ?";
    int count = jdbcTemplate.update(sql, "zhangsan", "300", 1);
    System.out.println(count);
    
  • 多值

    String sql = "UPDATE user SET name = ?, age = ? WHERE id = ?";
    Object[] objs1 = {"d", 4, 4};
    Object[] objs2 = {"e", 5, 5};
    Object[] objs3 = {"f", 6, 6};
    List<Object[]> list = new ArrayList<>();
    list.add(objs1);
    list.add(objs2);
    list.add(objs3);
    int[] count = jdbcTemplate.batchUpdate(sql, list);
    System.out.println(Arrays.toString(count));
    

c. 删除

  • 单值

    String sql = "DELETE FROM user WHERE id = ?";
    int count = jdbcTemplate.update(sql, 1);
    System.out.println(count);
    
  • 多值

    String sql = "DELETE FROM user WHERE id = ?";
    Object[] objs1 = {4};
    Object[] objs2 = {5};
    Object[] objs3 = {6};
    List<Object[]> list = new ArrayList<>();
    list.add(objs1);
    list.add(objs2);
    list.add(objs3);
    int[] count = jdbcTemplate.batchUpdate(sql, list);
    System.out.println(Arrays.toString(count));
    

(3)查询

  • 查询一个对象

    String sql = "SELECT id, name, age FROM user WHERE id = ?";
    User user = jdbcTemplate.queryForObject(sql, new BeanPropertyRowMapper<>(User.class), 2);
    System.out.println(user);
    
  • 查询多个对象

    String sql = "SELECT id, name, age FROM user";
    List<User> users = jdbcTemplate.query(sql, new BeanPropertyRowMapper<>(User.class));
    System.out.println(users);
    
  • 查值

    String sql = "SELECT COUNT(1) FROM user";
    Integer count = jdbcTemplate.queryForObject(sql, int.class);
    System.out.println(count);
    

(4)回调函数

  • 通过使用回调函数可以添加更多操作细节

  • 举例:查询 id 为 2 的数据

    String sql = "SELECT id, name, age FROM user where id = ?";
    User user = jdbcTemplate.execute(sql, new PreparedStatementCallback<User>() {
        @Override
        public User doInPreparedStatement(PreparedStatement ps) throws SQLException, DataAccessException {
            User user = null;
            ps.setInt(1, 2);
            ResultSet rs = ps.executeQuery();
            if(rs.next()) {
                user = new User();
                user.setId(rs.getInt("id"));
                user.setName(rs.getString("name"));
                user.setAge(rs.getInt("age"));
            }
            return user;
        }
    });
    System.out.println(user);
    

(5)整合德鲁伊连接池

  • 在 pom.xml 中添加德鲁伊连接池依赖

    <dependency>
        <groupId>com.alibaba</groupId>
        <artifactId>druid</artifactId>
        <version>1.1.10</version>
    </dependency>
    
  • 在 spring.xml 中配置

    <bean id="druidDataSource" class="com.alibaba.druid.pool.DruidDataSource">
        <property name="driverClassName" value="com.mysql.cj.jdbc.Driver" />
        <property name="url" value="jdbc:mysql://localhost:3306/spring" />
        <property name="username" value="root" />
        <property name="password" value="admin123" />
    </bean>
    
    <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
        <property name="dataSource" ref="druidDataSource" />
    </bean>
    

0x0D 面向切面编程 AOP

(1)概述

  • AOP(Aspect Oriented Programming):面向切面编程 / 面向方面编程
  • AOP 是对 OOP 的补充延申,其底层使用动态代理实现,能够捕捉系统中常用功能并转化为组件
  • Spring 的 AOP 使用的动态代理是 JDK 动态代理 + CGLIB 动态代理技术
  • 作用:将与核心业务无关的代码独立抽取出来,形成一个独立的组件,然后以横向交叉的方式应用到业务流程当中
  • 优点
    • 代码复用性强
    • 代码易维护
    • 使开发者更关注业务逻辑

(2)相关术语

  • 连接点 Joinpoint
    • 在程序的整个执行流程中,可以织入切面的位置。方法执行前后、异常抛出之后等位置
  • 切点 Pointcut
    • 在程序执行流程中。真正织入切面的方法。一个切点对应多个连接点
  • 通知 Advice
    • 又称增强,是需要具体织入的代码
    • 通知包括:前置通知、后置通知、环绕通知、异常通知、最终通知
  • 切面 Aspect
    • 切点加通知
  • 织入 Weaving
    • 把通知应用到目标对象的过程
  • 代理对象 Proxy
    • 一个目标对象被织入通知后产生的新对象
  • 目标对象 Target
    • 被织入的对象

(3)切点表达式

  • 切点表达式用于定义通知对哪些方法进行切入

  • 语法格式:execution([访问控制权限修饰符] 返回值类型 [全限定类名]方法名(形参列表) [异常])

    • 访问控制权限修饰符
      • 选填,默认 4 个权限全包括
    • 返回值类型
      • 必填,*表示返回值类型任意
    • 全限定类名
      • 选填,..表示当前包下的所有类,省略则表示所有类
    • 方法名
      • 必填,*表示所有方法,set*表示所有 set 方法
    • 形参列表
      • 必填,省略表示没有参数,..表示参数类型和个数随意的方法,*只有一个参数的方法,*, String表示第一个参数类型随意,第二个参数为字符串类型
    • 异常
      • 选填,省略时表示任意异常类型
  • 举例

    • service 包下的所有类中以 delete 开头的所有方法

      execution(public * com.spring.service).*.delete*(..))
      
    • service 包下的所有类的所有方法

      execution(* com.spring.service..*(..))
      
    • 所有类的所有方法

      execution(* *(..))
      

(4)Spring AOP

  • Spring 对 AOP 的实现方式
    1. 基于注解方式,结合 Spring + AspectJ
    2. 基于 XML 方式,结合 Spring + AspectJ
    3. 基于 XML 方式,结合 Spring
  • AspectJ 是独立于 Spring 框架之外的框架
  • Spring + AspectJ 最为常用,以下内容以 Spring + AspectJ 方式为主

a. 环境配置

  • 在 pom.xml 中添加依赖项

    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-context</artifactId>
        <version>6.0.4</version>
    </dependency>
    
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-aop</artifactId>
        <version>6.0.4</version>
    </dependency>
    
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-aspects</artifactId>
        <version>6.0.4</version>
    </dependency>
    
  • 在 spring.xml 添加 context 命名空间和 aop 命名空间

    <?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">
    
    </beans>
    

b. 基于注解方式

I. 实现步骤

  1. 定义目标类及目标方法

    新建 com.spring.service 包并新建 UserService.java 文件

    package com.spring.service;
    
    public class UserService {  // 目标类
        public void login() {   // 目标方法
            System.out.println("Login");
        }
    }
    
  2. 定义切面类

    在 com.spring.service 包新建 MyAspect.java 文件

    package com.spring.service;
    
    import org.aspectj.lang.annotation.Aspect;
    import org.aspectj.lang.annotation.Before;
    
    @Aspect
    public class MyAspect { // 切面
        // 切面 = 通知 + 切点
    
        // 前置通知
        @Before("execution(* com.spring.service.UserService.*(..))")
        public void loginAspect() {
            System.out.println("Login Aspect");
        }
    }
    
  3. 将两个类纳入 Bean 管理

    // filename: UserService.java
    @Service("userService")
    public class UserService {...}
    
    // filename: MyAspect.java
    @Component("myAspect")
    public class MyAspect {...}
    
  4. 在 spring.xml 中添加组件扫描并开启自动代理

    <context:component-scan base-package="com.spring.service" />
    <aop:aspectj-autoproxy proxy-target-class="true" />
    
    • proxy-target-class:true-强制使用 CGLIB 动态管理,false-使用 JDK 动态管理(默认)
  5. 测试

    @Test
    public void test() {
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.xml");
        UserService userService = applicationContext.getBean("userService", UserService.class);
        userService.login();
    }
    

II. 通知

  • 通知类型

    • 前置通知:@Before 目标方法执行之前的通知
    • 后置通知:@AfterReturning 目标方法执行之后的通知
    • 环绕通知:@Around 目标方法之前添加通知,同时目标方法执行之后添加通知
    • 异常通知:@AfterThrowing 发生异常之后执行的通知
    • 最终通知:@After 放在 finally 语句块中执行的通知
  • 通知顺序

    @Aspect
    @Component("myAspect")
    public class MyAspect {
        // 前置通知
        @Before("execution(* com.spring.service.UserService.*(..))")
        public void beforeAdvice() {
            System.out.println("前置通知");
        }
    
        // 后置通知
        @AfterReturning("execution(* com.spring.service.UserService.*(..))")
        public void afterReturningAdvice() {
            System.out.println("后置通知");
        }
    
        // 环绕通知
        @Around("execution(* com.spring.service.UserService.*(..))")
        public void aroundAdvice(ProceedingJoinPoint joinPoint) throws Throwable {
            System.out.println("环绕(前)");
            joinPoint.proceed();    // 执行目标
            System.out.println("环绕(后)");
        }
    
        // 异常通知
        @AfterThrowing("execution(* com.spring.service.UserService.*(..))")
        public void afterThrowingAdvice() {
            System.out.println("异常通知");
        }
    
        // 最终通知
        @After("execution(* com.spring.service.UserService.*(..))")
        public void afterAdvice() {
            System.out.println("最终通知");
        }
    }
    
    graph LR 环绕通知-前--> 前置通知--> Service--> A{异常}--否--> 后置通知--> B[最终通知]--> 环绕通知-后 A--是--> 异常通知--> 最终通知

III. 切面顺序

  • 可以使用 @Order 注解标识切面类,该注解需要 value 值,值越小,优先级越高

  • SecurityAspect.java

    package com.spring.service;
    
    import org.aspectj.lang.annotation.Aspect;
    import org.aspectj.lang.annotation.Before;
    import org.springframework.core.annotation.Order;
    import org.springframework.stereotype.Component;
    
    @Aspect
    @Component
    @Order(1)
    public class SecurityAspect {   // 安全切面
        // 前置通知
        @Before("execution(* com.spring.service.UserService.*(..))")
        public void beforeAdvice() {
            System.out.println("安全切面的前置通知");
        }
    }
    

IV. 通用切点

  • 定义并使用该通用切点

    public class MyAspect {
        // 定义通用切点表达式
        @Pointcut("execution(* com.spring.service.UserService.*(..))")
        public void CommonPointcut() {}
    
        // 前置通知
        @Before("CommonPointcut()")
        public void beforeAdvice() {
            System.out.println("前置通知");
        }
    
        // 后置通知
        @AfterReturning("CommonPointcut()")...
        // 环绕通知
        @Around("CommonPointcut()")...
        // 异常通知
        @AfterThrowing("CommonPointcut()")...
        // 最终通知
        @After("CommonPointcut()")...
    }
    
  • 在其他切点使用该通用切点

    @Before("com.spring.service.MyAspect.CommonPointcut()")
    public void beforeAdvice() {
        System.out.println("安全切面的前置通知");
    }
    

V. 连接点

  • 在 Spring 容器调用方法时会自动传递 joinPoint 连接点参数

  • 获得目标方法签名,从而获取目标方法的详细信息

    @Before("CommonPointcut()")
    public void beforeAdvice(JoinPoint joinPoint) {
        Signature signature = joinPoint.getSignature();
        System.out.println(signature);
    }
    

VI. 全注解开发

  • 编写配置类,在其中使用注解代替 Spring 配置文件,Spring Config.java

    package com.spring.service;
    
    import org.springframework.context.annotation.ComponentScan;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.context.annotation.EnableAspectJAutoProxy;
    
    @Configuration
    @ComponentScan("com.spring.service")
    @EnableAspectJAutoProxy(proxyTargetClass = true)
    public class SpringConfig {
    }
    
  • 修改测试文件

    @Test
    public void test() {
        ApplicationContext applicationContext = new AnnotationConfigApplicationContext(SpringConfig.class);
        UserService userService = applicationContext.getBean("userService", UserService.class);
        userService.generate();
    }
    

c. 基于 XML 方式

  1. 编写目标类

    package com.spring.service;
    
    public class UserService {
        public void generate() {
            System.out.println("UserService");
        }
    }
    
  2. 编写切面类和通知

    package com.spring.service;
    
    import org.aspectj.lang.ProceedingJoinPoint;
    
    public class MyAspect {
        public void advice(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
            long begin = System.currentTimeMillis();
            proceedingJoinPoint.proceed();
            long end = System.currentTimeMillis();
            System.out.println((end - begin) + " ms");
        }
    }
    
  3. 编写 Spring 配置文件

    <bean id="userService" class="com.spring.service.UserService"></bean>
    <bean id="myAspect" class="com.spring.service.MyAspect"></bean>
        
    <aop:config>
        <aop:pointcut id="mypointcut" expression="execution(* com.spring.service..*(..))" />
        <aop:aspect ref="myAspect">
            <aop:around method="advice" pointcut-ref="mypointcut" />
        </aop:aspect>
    </aop:config>
    
  4. 测试

    import com.spring.service.UserService;
    import org.junit.Test;
    import org.springframework.context.ApplicationContext;
    import org.springframework.context.support.ClassPathXmlApplicationContext;
    
    public class SpringTest {
        @Test
        public void test() {
            ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.xml");
            UserService userService = applicationContext.getBean("userService", UserService.class);
            userService.generate();
        }
    }
    

-End-