Spring6

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-

posted @ 2023-08-11 15:30  SRIGT  阅读(34)  评论(0编辑  收藏  举报