likes
comments
collection
share

1.Spring入门系列

作者站长头像
站长
· 阅读数 17

Spring

1.Spring 工厂设计模式及简单工厂的实现

1.1 传统开发模式及问题

1.1.1 传统的类及其实现

首先,我们定义了User类及其实现:

public class User {
    private String name;
    private String password;

    public User(String name, String password) {
        this.name = name;
        this.password = password;
    }

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

1.1.2 服务接口及其实现

定义了UserService接口及其实现类UserServiceImpl

public interface UserService {
    void register(User user);
    void login(String name, String password);
}

public class UserServiceImpl implements UserService {

    private UserDao userDao = new UserDaoImpl();

    @Override
    public void register(User user) {
        userDao.save(user);
    }

    @Override
    public void login(String name, String password) {
        userDao.queryUserByNameAndPassword(name, password);
    }
}

1.1.3 数据访问接口及其实现

定义了UserDao接口及其实现类UserDaoImpl

public interface UserDao {
    void save(User user);
    void queryUserByNameAndPassword(String name, String password);
}

public class UserDaoImpl implements UserDao {
    @Override
    public void save(User user) {
        System.out.println("insert into user = " + user);
    }

    @Override
    public void queryUserByNameAndPassword(String name, String password) {
        System.out.println("query User name = " + name + ", password = " + password);
    }
}

1.1.4 测试类

在测试文件中进行测试:

 @Test
 public void test1() {
     UserService userService = new UserServiceImpl();
     userService.login("name", "password");

     User user = new User("name", "123456");
     userService.register(user);
 }

这种传统的方式存在很高的耦合度,UserService如果有了新的实现类,需要修改测试文件中的代码,例如将UserService userService = new UserServiceImpl()改成UserService userService = new UserServiceImplNew()

1.2 简单工厂的实现

为了降低耦合度,我们可以使用工厂来创建对象,而不是直接new对象。

1.2.1 简单工厂模式

public class BeanFactory {
    public static UserService getUserService() {
        return new UserServiceImpl();
    }
}

在测试类中使用工厂方法:

UserService userService = BeanFactory.getUserService();

1.2.2 反射工厂

为了进一步减少耦合,我们可以使用反射机制来创建对象:

public class BeanFactory {
    public static UserService getUserService() {
        UserService userService = null;
        try {
            Class<?> clazz = Class.forName("com.rainsoul.basic.UserServiceImpl");
            userService = (UserService) clazz.newInstance();
        } catch (ClassNotFoundException | InstantiationException | IllegalAccessException e) {
            throw new RuntimeException(e);
        }
        return userService;
    }
}

虽然我们减少了直接依赖类名,但还是依赖了类的完全限定名。

1.2.3 使用配置文件

我们可以使用配置文件来存储类名,进一步降低耦合:

# applicationContext.properties
userService = com.rainsoul.basic.UserServiceImpl

修改BeanFactory类读取配置文件:

public class BeanFactory {

    private static Properties env = new Properties();

    static {
        try (InputStream inputStream = BeanFactory.class.getResourceAsStream("/applicationContext.properties")) {
            env.load(inputStream);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    public static UserService getUserService() {
        UserService userService = null;
        try {
            Class<?> clazz = Class.forName(env.getProperty("userService"));
            userService = (UserService) clazz.newInstance();
        } catch (ClassNotFoundException | InstantiationException | IllegalAccessException e) {
            throw new RuntimeException(e);
        }
        return userService;
    }
}

通过修改配置文件中的类名,可以动态改变实现类,而不需要修改代码。

1.3 通用工厂的设计

为了进一步优化,我们可以设计一个通用工厂方法:

public class BeanFactory {

    private static Properties env = new Properties();

    static {
        try (InputStream inputStream = BeanFactory.class.getResourceAsStream("/applicationContext.properties")) {
            env.load(inputStream);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    public static Object getBean(String key) {
        Object object = null;
        try {
            Class<?> clazz = Class.forName(env.getProperty(key));
            object = clazz.newInstance();
        } catch (ClassNotFoundException | InstantiationException | IllegalAccessException e) {
            throw new RuntimeException(e);
        }
        return object;
    }
}

使用通用工厂获取对象:

UserDao userDao = (UserDao) BeanFactory.getBean("userDao");
UserService userService = (UserService) BeanFactory.getBean("userService");

配置文件:

# applicationContext.properties
userService = com.rainsoul.basic.UserServiceImpl
userDao = com.rainsoul.basic.UserDaoImpl

1.4 通用工厂的使用

1.4.1 定义类

定义一个Person类:

public class Person {
}

1.4.2 配置文件

在配置文件中添加Person类的配置:

person = com.rainsoul.basic.Person

1.4.3 测试类

在测试文件中通过工厂获取Person对象:

 @Test
 public void test2() {
     Person person = (Person) BeanFactory.getBean("person");
     System.out.println("person = " + person);
 }

 // 结果:person = com.rainsoul.basic.Person@1d251891

通过使用工厂模式,我们显著降低了代码的耦合度,提高了代码的灵活性和可维护性。只需修改配置文件,就可以动态改变类的实现,而无需修改代码。

2.第一个Spring程序

2.1 环境搭建

在开始第一个Spring程序之前,先进行环境搭建。我们需要在项目的 pom.xml 文件中添加以下依赖:

<dependencies>
    <dependency>
        <groupId>junit</groupId>
        <artifactId>junit</artifactId>
        <version>4.13.2</version>
        <scope>test</scope>
    </dependency>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-context</artifactId>
        <version>5.3.19</version>
    </dependency>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-webmvc</artifactId>
        <version>5.3.19</version>
    </dependency>
    <dependency>
        <groupId>ch.qos.logback</groupId>
        <artifactId>logback-classic</artifactId>
        <version>1.4.6</version>
    </dependency>
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <version>8.0.25</version>
    </dependency>
</dependencies>

2.2 Spring的核心API

Spring提供的核心API是 ApplicationContext,它是一个工厂,用于创建和管理对象。

优点:

  • 解耦合:对象的创建和依赖关系的管理由Spring容器负责,减少了代码之间的耦合。
  • 线程安全:ApplicationContext 是线程安全的,可以在多线程环境中使用。

类型:

  • 非Web环境:ClassPathXmlApplicationContext(用于main方法和JUnit测试)
  • Web环境:XmlWebApplicationContext

2.3 程序步骤

  1. 创建一个 Person 类。

    public class Person {
        // 类的内容
    }
    
  2. 在Spring配置文件 applicationContext.xml 中定义 Person bean。

    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="http://www.springframework.org/schema/beans"
           xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
           xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
    
        <bean id="person" class="com.rainsoul.basic.Person"/>
    </beans>
    
  3. 使用JUnit测试代码来获取Spring工厂,并通过工厂获取 Person 对象。

    @Test
    public void test1() {
        // 获取Spring工厂
        ApplicationContext context = new ClassPathXmlApplicationContext("/applicationContext.xml");
        
        // 通过工厂获取对象
        Person person = (Person) context.getBean("person");
        System.out.println("person = " + person);
    }
    

    结果:

    17:36:22.100 [main] DEBUG org.springframework.context.support.ClassPathXmlApplicationContext -- Refreshing org.springframework.context.support.ClassPathXmlApplicationContext@4d50efb8
    17:36:22.352 [main] DEBUG org.springframework.beans.factory.xml.XmlBeanDefinitionReader -- Loaded 1 bean definitions from class path resource [applicationContext.xml]
    17:36:22.400 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory -- Creating shared instance of singleton bean 'person'
    person = com.rainsoul.basic.Person@7748410a
    
  4. 使用不同方法获取Spring工厂中的对象。

    @Test
    public void test1() {
        // 获取Spring工厂
        ApplicationContext context = new ClassPathXmlApplicationContext("/applicationContext.xml");
        
        // 通过id获取对象
        Person person = (Person) context.getBean("person");
        System.out.println("person = " + person);
    
        // 通过id和类型获取对象
        Person person = context.getBean("person", Person.class);
        System.out.println("person = " + person);
    
        // 通过类型获取对象 (配置文件中只能有一个Person类型的bean)
        Person person = context.getBean(Person.class);
        System.out.println("person = " + person);
    
        // 获取所有bean定义的名称
        String[] beanDefinitionNames = context.getBeanDefinitionNames();
        for (String beanDefinitionName : beanDefinitionNames) {
            System.out.println("beanDefinitionName = " + beanDefinitionName);
        }
    
        // 获取所有类型为Person的bean的名称
        String[] beanNamesForType = context.getBeanNamesForType(Person.class);
        for (String id : beanNamesForType) {
            System.out.println("id = " + id);
        }
    
        // 检查是否包含名为"person1"的bean定义,只能判断id值,不能判断name
        if (context.containsBeanDefinition("person1")) {
            System.out.println("true = " + true);
        } else {
            System.out.println("false = " + false);
        }
    
        // 检查是否包含名为"person"的定义,id和name都可以判断
        if (context.containsBean("person")) {
            System.out.println("true = " + true);
        } else {
            System.out.println("false = " + false);
        }
    }
    
  5. 默认id和name属性

    @Test
    public void test2() {
        ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
        String[] beanDefinitionNames = context.getBeanDefinitionNames();
        for (String beanDefinitionName : beanDefinitionNames) {
            // 默认id值 beanDefinitionName = com.rainsoul.basic.Person#0
            System.out.println("beanDefinitionName = " + beanDefinitionName);
        }
    }
    

    默认id:

    如果bean只需要使用一次,可以省略id值;如果bean会使用多次或被其他bean引用,需要设置id值。

    name属性:

    name属性用于定义bean的别名,可以有多个别名,而id属性只能有一个值。

2.4 Spring工厂的底层实现原理(简易版)

  1. 读取配置文件

    使用 ClassPathXmlApplicationContext 工厂读取 applicationContext.xml 配置文件。

    <!-- applicationContext.xml -->
    <beans>
        <bean id="account" class="com.rainsoul.injection.Account"/>
    </beans>
    
  2. 解析配置文件

    从配置文件中获取 <bean> 标签的 idclass 属性值。

  3. 通过反射创建对象

    使用反射机制根据 class 属性值创建对象实例。

    // 假设读取到了 class 和 id 值
    String className = "com.rainsoul.injection.Account";
    String beanId = "account";
    
    // 通过反射创建对象
    Class<?> clazz = Class.forName(className);
    Object beanInstance = clazz.newInstance();
    
    // 将创建的实例与 id 关联
    Map<String, Object> beanMap = new HashMap<>();
    beanMap.put(beanId, beanInstance);
    
  4. 反射创建对象的底层原理

    反射机制创建对象实际上调用了对象的无参构造方法。

    // 通过反射创建对象
    Class<?> clazz = Class.forName(className);
    Object beanInstance = clazz.newInstance();
    
    // 等效于
    Account account = new Account();
    

总结

Spring工厂的底层实现原理(简易版)可以归纳为以下几个步骤:

  1. 读取配置文件:使用 ClassPathXmlApplicationContext 读取配置文件。
  2. 解析配置文件:提取 <bean> 标签的 idclass 属性值。
  3. 反射创建对象:使用 Class.forName(className).newInstance() 根据 class 属性值创建对象实例,实际上调用了对象的无参构造方法。
  4. 存储与管理对象:将创建的对象实例与 id 进行关联,并存储在容器中以便后续使用。

3. 注入 (Injection)

3.1 什么是注入?为什么注入?

注入是指通过Spring工厂和配置文件,为所创建的对象的成员变量赋值。

public class Person {
    private Integer id;
    private String name;

    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 Person() {
        System.out.println("Person.Person");
    }
}
<bean id="person" class="com.rainsoul.basic.Person"/>
@Test
public void test4() {
    ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
    Person person = (Person) context.getBean("person");
    person.setId(1);
    person.setName("Anlan");
    System.out.println("person = " + person);
}

直接在代码中为对象赋值存在耦合。使用Spring注入可以解耦合。

3.2 Spring注入的步骤

  1. 类成员提供get和set方法。

  2. 配置Spring配置文件:

<bean id="person" class="com.rainsoul.basic.Person">
    <property name="id">
        <value>10</value>
    </property>
    <property name="name">
        <value>Anlan</value>
    </property>
</bean>

3.3 注入的原理 (简易版)

Spring底层通过调用对象属性的set方法进行注入。

// Spring注入的工作原理:
1. <bean id="account" class="xxx.Account" /> 
   // 等效于 Account account = new Account();
2. <property name="name">
       <value>suns</value>
   </property>
   // 等效于 account.setName("suns")
3. <property name="password">
       <value>123456</value>
   </property>
   // 等效于 account.setPassword("123456")

3.4 set注入详解

set注入的类型有JDK内置类型和用户自定义类型。JDK内置类型包括八种基本数据类型和String、数组类型、Set集合、List集合、Map集合、Properties集合。自定义类型包括自定义的Service、DAO等。

以下是描述JDK内置类型和用户自定义类型之间关系的图表:

数据类型
JDK内置类型
用户自定义类型
基本数据类型
String
数组类型
Set集合
List集合
Map集合
Properties集合
Service
DAO
其他
int
long
double
float
char
boolean
byte
short

3.4.1 JDK内置类型

  1. String + 八种基本类型
<value>Anlan</value>
  1. 数组
<list>
    <value>suns@zparkhr.com.cn</value>
    <value>liucy@zparkhr.com.cn</value>
    <value>chenyn@zparkhr.com.cn</value>
</list>
  1. Set集合
<set>
    <value>11111</value>
    <value>112222</value>
</set>
  1. List集合
<list>
    <value>11111</value>
    <value>2222</value>
</list>
  1. Map集合
<map>
    <entry>
        <key><value>suns</value></key>
        <value>3434334343</value>
    </entry>
    <entry>
        <key><value>chenyn</value></key>
        <ref bean="someBean"/>
    </entry>
</map>
  1. Properties类型
<props>
    <prop key="key1">value1</prop>
    <prop key="key2">value2</prop>
</props>
  1. 复杂的JDK自定义类型 (Date)

需要自定义转换器。

3.4.2 用户自定义类型

  1. 第一种方式
  • 为成员变量提供set和get方法。
  • 在配置文件中进行注入
<bean id="userService" class="xxxx.UserServiceImpl">
    <property name="userDao">
        <bean class="xxx.UserDAOImpl"/>
    </property>
</bean>
  1. 第二种方式

第一种方式配置文件代码冗余,被注入的对象(UserDAO)多次创建,浪费内存资源。

<bean id="userDAO" class="xxx.UserDAOImpl"></bean>

<bean id="userService" class="xxx.UserServiceImpl">
    <property name="userDAO">
        <ref bean="userDAO"/>
    </property>
</bean>

3.5 set注入简化写法

  1. 基于属性简化
<!-- JDK类型注入 -->
<property name="name">
    <value>rains</value>
</property>

<property name="name" value="rains"/>
<!-- 注意:value属性只能简化八种基本类型 + String注入标签 -->

<!-- 用户自定义类型 -->
<property name="userDAO">
    <ref bean="userDAO"/>
</property>

<property name="userDAO" ref="userDAO"/>
  1. 基于P命名空间的简化
<!-- JDK类型注入 -->
<bean id="person" class="xxxx.Person">
    <property name="name">
        <value>rains</value>
    </property>
</bean>

<bean id="person" class="xxx.Person" p:name="rains"/>
<!-- 注意:value属性只能简化八种基本类型 + String注入标签 -->

<!-- 用户自定义类型 -->
<bean id="userService" class="xx.UserServiceImpl">
    <property name="userDAO">
        <ref bean="userDAO"/>
    </property>
</bean>

<bean id="userService" class="xxx.UserServiceImpl" p:userDAO-ref="userDAO"/>

3.6构造方法注入

注入:通过Spring的配置文件,为成员变量赋值
Set注入:Spring调用Set方法通过配置文件为成员变量赋值
构造注入:Spring调用构造方法通过配置文件为成员变量赋值

3.6.1开发步骤

  1. 提供构造方法:

    public class Consumer {
        private String name;
        private int age;
    
        public Consumer(String name, int age) {
            this.name = name;
            this.age = age;
        }
    
        @Override
        public String toString() {
            return "Consumer{" +
                    "name='" + name + '\'' +
                    ", age=" + age +
                    '}';
        }
    }
    
  2. 配置文件

    <bean id="consumer" class="com.rainsoul.basic.constructor.Consumer">
        <constructor-arg>
            <value>Anlan</value>
        </constructor-arg>
        <constructor-arg>
            <value>18</value>
        </constructor-arg>
    </bean>
    

3.6.2构造方法重载

参数个数相同:

通过控制<constructor-arg>标签的数量进行区分

参数个数不同:

通过在标签引l入type属性进行类型的区分<constructor-arg type=">

3.7注入总结

构造注入
构造方法
Set注入
Set方法
constructor-arg
property
JDK内置类型
用户自定义类型
8种基本类型+String
数组类型
Set集合
List集合
Map集合
Properties集合
自定义类型

这个Mermaid图描述了构造注入和Set注入的流程,并展示了各自支持的JDK内置类型和用户自定义类型。JDK内置类型进一步包括基本类型、数组类型和各种集合类型。

4.控制反转与依赖注入

4.1反转控制(IOC Inverse of control)

控制:指的是对于成员变量的的控制权
反转控制:把对于成员变量的控制权,从代码中转移到Spring工厂和配置文件中完成
底层实现:工厂设计模式

4.2依赖注入(DI Dependency Injection)

注入:通过Spring的工厂及配置文件,为对象(bean,组件)的成员变量赋值

依赖注入:当一个类需要另外一个类时,就意味着依赖,一旦出现依赖就可以把另外一个类作为本类的成员变量,最终通过Spring进行注入(赋值)

5. Spring 工厂创建复杂对象

5.1 什么是复杂对象

在软件开发中,对象可以分为简单对象和复杂对象。简单对象通常可以通过直接使用 new 关键字创建,比如基本的 POJO(普通 Java 对象)。而复杂对象则需要通过一些特殊的 API 或设计模式(如工厂模式)进行创建,因为它们的创建过程涉及更多的配置和初始化步骤。

例如:

// 创建数据库连接
Class.forName("com.mysql.jdbc.Driver");
Connection conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/rainsoul", "root", "root");

// 创建 SqlSessionFactory
InputStream inputStream = Resources.getResourceAsStream("mybatis-config.xml");
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);

5.2 Spring 创建复杂对象的三种方式

5.2.1 FactoryBean 接口

FactoryBean 接口是 Spring 提供的一种创建复杂对象的机制。通过实现 FactoryBean 接口,可以自定义复杂对象的创建过程,并将其注册为 Spring 容器中的 Bean。

前面的例子中,我们创建了一个 ConnectionFactoryBean 来创建 MySQL 数据库连接。

package com.rainsoul.basic.factorybean;

import org.springframework.beans.factory.FactoryBean;

import java.sql.Connection;
import java.sql.DriverManager;

public class ConnectionFactoryBean implements FactoryBean<Connection> {
    
    //getObject() 方法:加载MySQL驱动,并创建一个到指定数据库的连接。
    @Override
    public Connection getObject() throws Exception {
        Class.forName("com.mysql.cj.jdbc.Driver");
        Connection connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/rainsoul", "root", "root");
        return connection;
    }

    //getObjectType() 方法:返回创建对象的类型,这里是 Connection 类。
    @Override
    public Class<?> getObjectType() {
        return Connection.class;
    }

    //isSingleton() 方法:返回 false,表示这个 FactoryBean 创建的对象不是单例,每次请求都会创建一个新的连接。
    @Override
    public boolean isSingleton() {
        return false;
    }
}

Spring 配置:

<bean id="conn" class="com.rainsoul.basic.factorybean.ConnectionFactoryBean"></bean>

测试代码:

@Test
public void testConnectionFactoryBean3() {
    ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
    Connection conn = (Connection) context.getBean("conn");
    System.out.println("conn = " + conn);
}

使用 FactoryBean 接口可以很方便地封装复杂对象的创建逻辑,并将其交给 Spring 管理。

体会依赖注入:

public class ConnectionFactoryBean implements FactoryBean<Connection> {
    private String driverClassName;
    private String url;
    private String username;
    private String password;

    //get set

    @Override
    public Connection getObject() throws Exception {
        Class.forName(driverClassName);
        Connection connection = DriverManager.getConnection(url, username, password);
        return connection;
    }

    @Override
    public Class<?> getObjectType() {
        return Connection.class;
    }

    @Override
    public boolean isSingleton() {
        return true;
    }
}
<bean id="conn" class="com.rainsoul.basic.factorybean.ConnectionFactoryBean">
    <property name="driverClassName" value="com.mysql.cj.jdbc.Driver"/>
    <property name="url" value="jdbc:mysql://localhost:3306"/>
    <property name="username" value="root"/>
    <property name="password" value="root"/>
</bean>
@Test
public void testConnectionFactoryBean2() {
    ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
    Connection cfb1 = (Connection) context.getBean("conn");
    Connection cfb2 = (Connection) context.getBean("conn");
    System.out.println(cfb1 == cfb2);
    System.out.println("cfb1 = " + cfb1);
    System.out.println("cfb2 = " + cfb2);
}
cfb1 = com.mysql.cj.jdbc.ConnectionImpl@49872d67
cfb2 = com.mysql.cj.jdbc.ConnectionImpl@49872d67

FactoryBean 的实现原理

接口回调:

  1. 为什么 Spring 规定 FactoryBean 接口实现并且 getObject()
  2. ctx.getBean("conn") 获得是复杂对象 Connection 而没有获得 ConnectionFactoryBean(用 & 前缀获取 FactoryBean 本身)。

Spring 内部运行流程:

  1. 通过 conn 获得 ConnectionFactoryBean 类的对象,进而通过 instanceof 判断出是 FactoryBean 接口的实现类。
  2. Spring 按照规定:调用 getObject() 方法获取实际对象。
  3. 返回 Connection

5.2.2 工厂方法

除了 FactoryBean 接口,Spring 还支持通过工厂方法来创建复杂对象。工厂方法可以是静态方法,也可以是实例方法。

  1. 静态工厂方法
public class StaticConnectionFactory {
    public static Connection getConnection() {
        Connection conn = null;
        try {
            Class.forName("com.mysql.cj.jdbc.Driver");
            conn = DriverManager.getConnection("jdbc:mysql://localhost:3306", "root", "root");
        } catch (ClassNotFoundException | SQLException e) {
            throw new RuntimeException(e);
        }
        return conn;
    }
}

Spring 配置:

<bean id="conn" class="com.rainsoul.basic.factorybean.StaticConnectionFactory" factory-method="getConnection"/>
  1. 实例工厂方法

实例工厂方法类似于静态工厂方法,不同之处在于需要先创建工厂类的实例,然后调用其实例方法来创建复杂对象。

  1. 避免Spring框架的侵入
  2. 整合遗留系统
public class ConnectionFactory {
    public Connection getConnection() {
        Connection conn = null;
        try {
            Class.forName("com.mysql.cj.jdbc.Driver");
            conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/rainsoul", "root", "root");
        } catch (ClassNotFoundException | SQLException e) {
            throw new RuntimeException(e);
        }
        return conn;
    }
}

Spring 配置:

<bean id="connFactory" class="com.rainsoul.basic.factorybean.ConnectionFactory"/>
<bean id="conn" factory-bean="connFactory" factory-method="getConnection"/>

5.2.3 通过 Spring 的初始化方法

Spring 还可以通过初始化方法(init-method)来创建和配置复杂对象。初始化方法是在对象实例化并注入依赖后被调用的,可以用来完成一些初始化工作。

public class MyService {
    private DataSource dataSource;

    public void setDataSource(DataSource dataSource) {
        this.dataSource = dataSource;
    }

    public void init() {
        // 初始化工作
    }
}

Spring 配置:

<bean id="myService" class="com.example.MyService" init-method="init">
    <property name="dataSource" ref="dataSource"/>
</bean>

通过 init-method,我们可以在对象的生命周期中插入初始化逻辑,从而创建和配置复杂对象。

5.3总结

通过以上三种方式,Spring 提供了强大的机制来创建和管理复杂对象,使得应用程序的配置更加灵活和可扩展。无论是通过实现 FactoryBean 接口、使用工厂方法,还是通过初始化方法,开发者都可以选择最适合的方式来满足复杂对象的创建需求。

6.控制Spring工厂创建对象的次数

6.1如何控制简单对象的创建次数

单次:<bean id="account" class="com.rainsoul.basic.scope.Account" scope="singleton"/>或者默认
多次:<bean id="account" class="com.rainsoul.basic.scope.Account" scope="prototype"/>

6.2如何控制复杂对象的创建次数

FactoryBean{
	isSingleton(){
		return true	只会创建一次
		return false	每一次都会创建新的
    }
}
如没有isSingleton方法还是通过scope属性进行对象创建次数的控制

6.3为什么要控制对象的创建次数

好处:节省不别要的内存浪费

  • 什么样的对象只创建一次?

    1.SqlSessionFactory
    2.DAO
    3.Service
    
  • 什么样的对象每一次都要创建新的?

    1.Connection
    2.SqlSession | Session
    3.Struts2 Action
    

7.对象的生命周期

7.1生命周期的三个阶段

  1. 创建阶段

    scope = "singleton" Spring工厂创建的同时,创建对象
    注意:加入lazy-init="true",也可以达到获取对象的同时再创建对象。
    <bean id="product" scope="singleton" lazy-init="true" class="com.rainsoul.basic.life.Product"/>
    
    
    scope = "prototype" Spring工厂获取对象的同时,创建对象
    
  2. 初始化阶段

    • 初始化方法提供:程序员根据需求,提供初始化方法,最终完成初始化操作
    • 初始化方法调用:Spring工厂进行调用
    //InitializingBean接口
    public class Product implements InitializingBean {
        public Product() {
            System.out.println("Product constructor");
        }
    
        //初始化方法,Spring调用
        @Override
        public void afterPropertiesSet() throws Exception {
            System.out.println("Product afterPropertiesSet");
        }
    }  //无需配置
    
    //自己提供方法
    public class Product {
        public Product() {
            System.out.println("Product constructor");
        }
    
        public void myInit() {
            System.out.println("My init method");
        }
    } 
    <bean id="product" init-method="myInit" class="com.rainsoul.basic.life.Product"/>
    

    如果两者都提供了,顺序?

    InitializingBean > 自定义

    注入一定发生在初始化前面。

  3. 销毁阶段 Spripg销毁对象前,会调用对象的销毁方法,完成销毁操作

    1. Spring什么时候销毁所创建的对象? ctx.close();

    2. 销毁方法:程序员根据自己的需求,定义销毁方法,完成销毁操作 调用:Spring工厂完成调用

    3. 实现方式:

      实现DisposableBean接口
      
      自定义销毁方法并在配置文件中配置:destroy-method="myDestroy"
      
      销毁方法的操作只适用于scope="singleton"
      
public class Product implements InitializingBean, DisposableBean {
    private String name;

    public Product() {
        System.out.println("Product.Product");
    }

    public void setName(String name) {
        System.out.println("Product.setName");
        this.name = name;
    }

    //初始化方法,Spring调用
    @Override
    public void afterPropertiesSet() throws Exception {
        System.out.println("Product.afterPropertiesSet");
    }

    public void myInit() {
        System.out.println("Product.myInit");
    }

    @Override
    public void destroy() throws Exception {
        System.out.println("Product.destroy");
    }

    public void myDestroy() {
        System.out.println("Product.myDestroy");
    }
}

<bean id="product" init-method="myInit" destroy-method="myDestroy" class="com.rainsoul.basic.life.Product">
    <property name="name">
        <value>18</value>
    </property>
</bean>
@Test
public void testProduct2() {
    ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
    Product product = context.getBean("product", Product.class);
    context.close();
}

Product.Product
Product.setName
Product.afterPropertiesSet
Product.myInit
Product.destroy
Product.myDestroy

8.配置文件参数化

把Spring配置文件中需要经常修改的字符串信息,转移到一个更小的配置文件中

  1. Spring的配置文件中存在需要经常修改的字符串? 存在以数据库连接相关的参数代表

  2. 经常变化字符串,在Spring的配置文件中,直接修改 不利于项目维护(修改)

  3. 转移到一个小的配置文件(.properties) 利于维护(修改)

    配置文件参数化:利于Spring配置文件的维护(修改)

开发步骤:

提供一个小的配置文件(.properities)

名字:随便
放置位置:随便

jdbc.driverClassName = com.mysql.jdbc.Driver
jdbc.url = jdbc:mysql://localhost:3306/suns?useSSL=false
jdbc.username =root
jdbc.password =123456
  • pring的配置文件与小配置文件进行整合
applicationContext.xml
<context:property-placeholder location="classpath:/db.properties"/>
  • 在Spring配置文件中通过${key}获取小配置文件对应的值。

9.自定义类型转换器

9.1类型转换器

作用:Spring通过类型转换器把配置文件中字符串类型的数据,转换成了对象中成员变量对应类型的数据,进而完成了注入

9.2自定义类型转换器

public class Person implements Serializable {
    private String name;
    private Date birthday;

    public String getName() {
        return name;
    }

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

    public Date getBirthday() {
        return birthday;
    }

    public void setBirthday(Date birthday) {
        this.birthday = birthday;
    }
}
<bean id="person" class="com.rainsoul.basic.converter.Person">
    <property name="name" value="Anlan"/>
    <property name="birthday" value="2024-06-24"/>
</bean>
@Test
public void test() {
    ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext2.xml");
    // Cannot convert value of type 'java.lang.String' to required type 'java.util.Date' for property 'birthday':
    // no matching editors or conversion strategy found
    Person person = (Person) context.getBean("person");
    System.out.println("person = " + person);
}

Date类型无法正确转换为String类型。自定义转换器实现Convert接口:

public class MyDateConverter implements Converter<String, Date> {
    convert方法作用:String ---> Date
                    SimpleDateFormat sdf = new SimpleDateFormat();
                    sdf.parset(String) --->Date
    param:source代表的是配置文件中日期字符串<value>2024-06-09</value>
    return:当把转换好的Date作为convert方法的返回值后,Spring自动的为birthday属性进行注入(赋值)
    @Override
    public Date convert(String source) {
        Date date = null;
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
        try {
            date = sdf.parse(source);
        } catch (ParseException e) {
            throw new RuntimeException(e);
        }
        return date;
    }
}
类型转换器的注册:告知Spring框架,我们所创建的DateConverter是一个类型转换器
<bean id="myDateConverter" class="com.rainsoul.basic.converter.MyDateConverter"/>

<bean id="conversionService" class="org.springframework.context.support.ConversionServiceFactoryBean">
    <property name="converters">
        <set>
            <ref bean="myDateConverter"/>
        </set>
    </property>
</bean>

10.后置处理Bean

10.1BeanProcessor的作用

对Spring工厂所创建的对象进行再加工。

程序员实现BeanPostProcessor规定接口中的方法:
Object postProcessBeforeInitiallization(Object bean String beanName)
作用:Spring创建完对象,并进行注入后,可以运行Before方法进行加工
获得Spring创建好的对象:通过方法的参数
最终通过返回值交给Spring框架

Object postProcessAfterInitiallization(object beanString beanName)
作用:Spring执行完对象的初始化操作后,可以运行After方法进行加工
获得Spring创建好的对象:通过方法的参数
最终通过返回值交给Spring框架

10.2BeanPostProcessor开发步骤

public class Category {
    private Integer id;
    private String name;

	//get set
}

public class MyBeanPostProcesser implements BeanPostProcessor {

    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        return bean;
    }

    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        Category category = (Category) bean;
        category.setName("anlan");
        return category;
    }
}
<bean id="category" class="com.rainsoul.basic.beanpost.Category">
    <property name="name" value="Anlan"/>
    <property name="id" value="10"/>
</bean>

<bean id="myBeanPostProcessor" class="com.rainsoul.basic.beanpost.MyBeanPostProcesser"/>
@Test
public void test1() {
    ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext3.xml");
    Category category = context.getBean("category", Category.class);
    System.out.println("category.getName() = " + category.getName());   //category.getName() = anlan
}
转载自:https://juejin.cn/post/7385483271700398143
评论
请登录