1.Spring入门系列
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 程序步骤
-
创建一个
Person
类。public class Person { // 类的内容 }
-
在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>
-
使用
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
-
使用不同方法获取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); } }
-
默认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工厂的底层实现原理(简易版)
-
读取配置文件
使用
ClassPathXmlApplicationContext
工厂读取applicationContext.xml
配置文件。<!-- applicationContext.xml --> <beans> <bean id="account" class="com.rainsoul.injection.Account"/> </beans>
-
解析配置文件
从配置文件中获取
<bean>
标签的id
和class
属性值。 -
通过反射创建对象
使用反射机制根据
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);
-
反射创建对象的底层原理
反射机制创建对象实际上调用了对象的无参构造方法。
// 通过反射创建对象 Class<?> clazz = Class.forName(className); Object beanInstance = clazz.newInstance(); // 等效于 Account account = new Account();
总结
Spring工厂的底层实现原理(简易版)可以归纳为以下几个步骤:
- 读取配置文件:使用
ClassPathXmlApplicationContext
读取配置文件。 - 解析配置文件:提取
<bean>
标签的id
和class
属性值。 - 反射创建对象:使用
Class.forName(className).newInstance()
根据class
属性值创建对象实例,实际上调用了对象的无参构造方法。 - 存储与管理对象:将创建的对象实例与
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注入的步骤
-
类成员提供get和set方法。
-
配置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内置类型和用户自定义类型之间关系的图表:
3.4.1 JDK内置类型
- String + 八种基本类型
<value>Anlan</value>
- 数组
<list>
<value>suns@zparkhr.com.cn</value>
<value>liucy@zparkhr.com.cn</value>
<value>chenyn@zparkhr.com.cn</value>
</list>
- Set集合
<set>
<value>11111</value>
<value>112222</value>
</set>
- List集合
<list>
<value>11111</value>
<value>2222</value>
</list>
- Map集合
<map>
<entry>
<key><value>suns</value></key>
<value>3434334343</value>
</entry>
<entry>
<key><value>chenyn</value></key>
<ref bean="someBean"/>
</entry>
</map>
- Properties类型
<props>
<prop key="key1">value1</prop>
<prop key="key2">value2</prop>
</props>
- 复杂的JDK自定义类型 (Date)
需要自定义转换器。
3.4.2 用户自定义类型
- 第一种方式
- 为成员变量提供set和get方法。
- 在配置文件中进行注入
<bean id="userService" class="xxxx.UserServiceImpl">
<property name="userDao">
<bean class="xxx.UserDAOImpl"/>
</property>
</bean>
- 第二种方式
第一种方式配置文件代码冗余,被注入的对象(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注入简化写法
- 基于属性简化
<!-- 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"/>
- 基于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开发步骤
-
提供构造方法:
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 + '}'; } }
-
配置文件
<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注入总结
这个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 的实现原理
接口回调:
- 为什么 Spring 规定 FactoryBean 接口实现并且
getObject()
? ctx.getBean("conn")
获得是复杂对象Connection
而没有获得ConnectionFactoryBean
(用&
前缀获取FactoryBean
本身)。
Spring 内部运行流程:
- 通过
conn
获得ConnectionFactoryBean
类的对象,进而通过instanceof
判断出是FactoryBean
接口的实现类。 - Spring 按照规定:调用
getObject()
方法获取实际对象。 - 返回
Connection
。
5.2.2 工厂方法
除了 FactoryBean
接口,Spring 还支持通过工厂方法来创建复杂对象。工厂方法可以是静态方法,也可以是实例方法。
- 静态工厂方法
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"/>
- 实例工厂方法
实例工厂方法类似于静态工厂方法,不同之处在于需要先创建工厂类的实例,然后调用其实例方法来创建复杂对象。
- 避免Spring框架的侵入
- 整合遗留系统
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生命周期的三个阶段
-
创建阶段
scope = "singleton" Spring工厂创建的同时,创建对象 注意:加入lazy-init="true",也可以达到获取对象的同时再创建对象。 <bean id="product" scope="singleton" lazy-init="true" class="com.rainsoul.basic.life.Product"/> scope = "prototype" Spring工厂获取对象的同时,创建对象
-
初始化阶段
- 初始化方法提供:程序员根据需求,提供初始化方法,最终完成初始化操作
- 初始化方法调用: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 > 自定义
注入一定发生在初始化前面。
-
销毁阶段 Spripg销毁对象前,会调用对象的销毁方法,完成销毁操作
-
Spring什么时候销毁所创建的对象? ctx.close();
-
销毁方法:程序员根据自己的需求,定义销毁方法,完成销毁操作 调用:Spring工厂完成调用
-
实现方式:
实现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配置文件中需要经常修改的字符串信息,转移到一个更小的配置文件中
-
Spring的配置文件中存在需要经常修改的字符串? 存在以数据库连接相关的参数代表
-
经常变化字符串,在Spring的配置文件中,直接修改 不利于项目维护(修改)
-
转移到一个小的配置文件(.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