一、工厂设计模式
1.1、传统的容器——EJB的缺点
EJB(Enterprise Java Beans),被称为企业Java Beans。他是上一代使用的容器。我们来看看传统的J2EE的体系。
EJB具有的缺点是很致命的:
- 运行环境苛刻。
- 代码移植性很差。
- EJB是重量级框架。
1.2、什么是Spring
Spring是一个轻量级的JavaEE解决方案,整合众多优秀的设计模式,其中最重要的设计模式是——工厂设计模式。他还包含其他的设计模式,比如说:代理设计模式、模板设计模式、策略设计模式等等。
1.3、什么是工厂设计模式
在传统的创建对象的时候,我们都是调用无参构造函数来创建对象的即new的方式来创建,这样创建对象的方式的耦合程度(指定是代码间的强关联关系,一方的改变会影响到另一方)就十分高。、
一旦我们需要修改类型,就需要代码中修改,并且重新编译和部署。
1.4、工厂设计模式的实现
package com.factory; import java.io.IOException; import java.io.InputStream; import java.util.Properties; public class BeanFactory { private static Properties env = new Properties(); static{ try { //第一步 获得IO输入流 InputStream inputStream = BeanFactory.class.getResourceAsStream("/applicationContext.properties"); //第二步 文件内容 封装 Properties集合中 key = userService ,value = com.service.impl.UserServiceImpl env.load(inputStream); inputStream.close(); } catch (IOException e) { e.printStackTrace(); } } /* 对象的创建的两种方式: 1. 直接调用构造方法创建对象 UserService userService = new UserServiceImpl(); 2. 通过反射的形式创建对象可以解耦合 Class clazz = Class.forName("com.service.impl.UserServiceImpl"); UserService userService = (UserService)clazz.newInstance(); */ public static UserService getUserService() { UserService userService = null; try { Class clazz = Class.forName(env.getProperty("userService")); userService = (UserService) clazz.newInstance(); } catch (ClassNotFoundException e) { e.printStackTrace(); } catch (InstantiationException e) { e.printStackTrace(); } catch (IllegalAccessException e) { e.printStackTrace(); } return userService; } public static UserDAO getUserDAO(){ UserDAO userDAO = null; try { Class clazz = Class.forName(env.getProperty("userDAO")); userDAO = (UserDAO) clazz.newInstance(); } catch (ClassNotFoundException e) { e.printStackTrace(); } catch (InstantiationException e) { e.printStackTrace(); } catch (IllegalAccessException e) { e.printStackTrace(); } return userDAO; } }
userDAO = com.dao.userDAO userService = com.service.userService
1.5、简单工厂的代码修改
我们可以发现,上面一个工厂设计模式的代码是又臭又长,我们每创建一个新的对象,都需要重新写一个工厂类,并且代码很大一部分都是相同的,仅仅只是创建的对象不同,于是我们可以抽取出共同的代码,组成一个通用的工厂类。
public class BeanFactory{ public static Object getBean(String key){ Object ret = null; try { Class clazz = Class.forName(env.getProperty(key)); ret = clazz.newInstance(); } catch (Exception e) { e.printStackTrace(); } return ret; } }
1.6、总结
Spring本质上就是一个工厂,只不过我们在日常开发的时候使用的不是自己写的工厂,因为这个工厂的功能很少,性能很低下,Spring帮我们写好了一个大型工厂(ApplicationContext),我们只需要在一个固定的配置文件(applicationContext.xml)中进行配置即可。
二、Spring入门
2.1、Spring简介
Spring 是于 2003 年兴起的一个轻量级的 Java 开发框架,它是为了解决企业应用开发的复杂性而创建的。Spring 的核心是控制反转(IoC)和面向切面编程(AOP)。Spring 是可以在 Java SE/EE 中使用的轻量级开源框架。
Spring 的主要作用就是为代码“解耦”,降低代码间的耦合度。就是让对象和对象(模块和模块)之间关系不是使用代码关联,而是通过配置来说明。即在 Spring 中说明对象(模块)的关系。
Spring 根据代码的功能特点,使用 Ioc 降低业务对象之间耦合度。IoC 使得主业务在相互调用过程中,不用再自己维护关系了,即不用再自己创建要使用的对象了。而是由 Spring容器统一管理,自动“注入”,注入即赋值。 而 AOP 使得系统级服务得到了最大复用,且不用再由程序员手工将系统级服务“混杂”到主业务逻辑中了,而是由 Spring 容器统一完成“织入”。
2.2、Spring的优点
Spring 是一个框架,是一个半成品的软件。由 20 个模块组成。它是一个容器管理对象,容器是装东西的,Spring 容器不装文本,数字。装的是对象。Spring 是存储对象的容器。他的优点主要是以下几个方面。
- 轻量。
- 面向接口编程。
- 面向切面编程
- 可以轻易集成其他优秀的框架。
2.2.1、轻量
Spring 框架使用的 jar 都比较小,一般在 1M 以下或者几百 kb。Spring 核心功能的所需的 jar 总共在 3M 左右。
Spring 框架运行占用的资源少,运行效率高。不依赖其他 jar。
2.2.2、面向接口编程
Spring 提供了 Ioc 控制反转,由容器管理对象,对象的依赖关系。原来在程序代码中的对象创建方式,现在由容器完成。对象之间的依赖解耦合。
2.2.3、面向切面编程(AOP)
通过 Spring 提供的 AOP 功能,方便进行面向切面的编程,许多不容易用传统 OOP 实现的功能可以通过 AOP 轻松应付。
在 Spring 中,开发人员可以从繁杂的事务管理代码中解脱出来,通过声明式方式灵活地进行事务的管理,提高开发效率和质量。
2.2.4、集成其他优秀的框架
Spring 不排斥各种优秀的开源框架,相反 Spring 可以降低各种框架的使用难度,Spring提供了对各种优秀框架(如 Shiro、MyBatis)等的直接支持。简化框架的使用。
Spring 像插线板一样,其他框架是插头,可以容易的组合到一起。需要使用哪个框架,就把这个插头放入插线板。不需要可以轻易的移除。
2.3、Spring的体系结构
Spring 由 20 多个模块组成,它们可以分为数据访问/集成(Data Access/Integration)、Web、面向切面编程(AOP, Aspects)、提供 JVM的代理(Instrumentation)、消息发送(Messaging)、核心容器(Core Container)和测试(Test)。
2.4、Spring的核心API
Spring的核心就是一个大工厂:ApplicationContext,他的作用是用于对象的创建,且可以解除耦合,他是一个接口,但是ApplicationContext是一个重量级的工厂对象占用大量的内存,所以我们不会频繁得去创建对象,一般一个应用只会创建一个工厂对象。ApplicationContext是线程安全的,可以被多线程并发访问。
他有两种实现方式:
适用于非WEB环境的:ClassPathXmlApplication
适用于WEB环境的:XmlApplicationContext
2.5、Spring的案例
2.5.1、引入依赖
<dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>5.1.5.RELEASE</version> </dependency>
2.5.3、创建类型
package com.domain; /** * @author Xiao_Lin * @date 2021/2/4 15:57 */ public class Person { }
2.5.4、修改配置文件
在applicationContext.xml的配置文件中更改配置
<!-- id属性:名字--> <!-- class属性:需要创建对象的全限定名--> <bean id="person" class="com.domain.Person"/>
2.5.5、创建对象
/** * 用于测试Spring的第一个程序 */ @Test public void testSpring(){ // 1. 获得Spring的工厂 ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("/applicationContext.xml"); // 2. 通过工厂类来获得对象 Person person = (Person)applicationContext.getBean("person"); System.out.println(person); }
2.6、细节分析
2.6.1、名词解释
Spring工厂创建的对象,叫做bean或者组件(componet)
2.6.2、相关方法
//通过这种方式获得对象,就不需要强制类型转换 Person person = ctx.getBean("person", Person.class); System.out.println("person = " + person); //当前Spring的配置文件中 只能有一个<bean>标签的class是Person类型 Person person = ctx.getBean(Person.class); System.out.println("person = " + person); //获取的是 Spring工厂配置文件中所有bean标签的id值 person person1 String[] beanDefinitionNames = ctx.getBeanDefinitionNames(); for (String beanDefinitionName : beanDefinitionNames) { System.out.println("beanDefinitionName = " + beanDefinitionName); } //根据类型获得Spring配置文件中对应的id值 String[] beanNamesForType = ctx.getBeanNamesForType(Person.class); for (String id : beanNamesForType) { System.out.println("id = " + id); } //用于判断是否存在指定id值的bean if (ctx.containsBeanDefinition("a")) { System.out.println("true = " + true); }else{ System.out.println("false = " + false); } //用于判断是否存在指定id(name)值的bean if (ctx.containsBean("person")) { System.out.println("true = " + true); }else{ System.out.println("false = " + false); }
2.7、Spring创建对象的简易原理图
注意:反射底层调用的是无参构造函数来进行实例化对象的,即使构造方法私有了,依然可以调用进行实例化对象。
2.8、注意
在未来开发的过程中,理论上所有的对象都是交给Spring工厂来创建,但是有一类特殊的对象——实体对象是不会交给Spring来创建的,它是交给持久层来创建的。
三、注入
3.1、什么是注入
注入是指 Spring 创建对象的过程中,将对象依赖属性通过配置设值给该对象。
3.2、为什么需要注入
通过编码的方式(setXxx),为成员变量进行赋值,存在耦合。
3.3、注入的方式
- set注入:其类必须提供对应 setter 方法。
- 构造器注入:利用构造器进行注入。
3.4、set注入
package com.domain; /** * @author Xiao_Lin * @date 2021/2/4 15:57 */ public class Person { private String username; private Integer password; @Override public String toString() { return "Person{" + "username='" + username + '\'' + ", password=" + password + '}'; } public Person(String username, Integer password) { this.username = username; this.password = password; } public Person() { } public String getUsername() { return username; } public void setUsername(String username) { this.username = username; } public Integer getPassword() { return password; } public void setPassword(Integer password) { this.password = password; } }
<bean id="person" class="com.domain.Person"> <property name="username"> <value>Xiao_Lin</value> </property> <property name="password"> <value>123456</value> </property> </bean>
/** * 用于测试注入 */ @Test public void testDI(){ ClassPathXmlApplicationContext application = new ClassPathXmlApplicationContext("applicationContext.xml"); Person person = application.getBean("person", Person.class); System.out.println(person); }
3.4.1、set注入的原理图
Spring通过底层调用对象属性对应的set方法完成对成员变量的赋值操作。
3.4.2、set注入详解
针对不同的不同类型的成员变量,我们不可能一直是使用value
标签,我们需要嵌套其他的标签,我们将成员变量可能的类型分类两大类:
- JDK内置类型。
- 用户自定义类型。
3.4.2.1、JDK内置类型
3.4.2.1.1、String+8种基本数据类型
都直接使用value
标签即可
<property name="password"> <value>123456</value> </property>
3.4.2.1.2、数组类型
对于数组类型,我们需要在配置文件中,使用list
标签,表明是数组类型,嵌套value
标签来进行赋值。
@Data @AllArgsConstructor @NoArgsConstructor public class Person { private String[] emails; }
<property name="emails"> <list> <value>124@qq.com</value> <value>456@163.com</value> </list> </property>
3.4.2.1.3、Set集合
对于set集合类型,我们需要在配置文件中,使用set
标签,表明是set
集合类型,嵌套Set泛型中对应的标签来进行赋值。
@Data @AllArgsConstructor @NoArgsConstructor public class Person { private Set<String> tels; }
<property name="tels"> <set> <value>123456</value> <value>456789</value> <value>13579</value> </set> </property>
对于set集合由于我们规范了泛型为String,她是8种基本数据类型,所以在set标签中才嵌套value标签。如果没有规定泛型或者说是规定了其他的泛型,set嵌套的标签需要根据具体的情况来具体分析。
3.4.2.1.4、List集合
对于List集合类型,我们需要在配置文件中,使用list
标签,表明是List集合类型,嵌套List泛型中对应的标签来进行赋值。
list便签中嵌套什么标签,取决于List集合中的泛型。
@Data @AllArgsConstructor @NoArgsConstructor public class Person { private List<String> address; }
<property name="address"> <list> <value>sz</value> <value>sz</value> <value>gz</value> </list> </property>
3.4.2.1.5、Map集合
对于Map集合,有一个内部类——Entry,所以我们在配置文件中需要使用的标签是用map
标签来嵌套entry
标签,里面是封装了一对键值对。我们使用key
标签来表示键,里面嵌套键对应的标签,值要根据对应的类型来选择对应的标签。
@Data @AllArgsConstructor @NoArgsConstructor public class Person { private Map<String,String> qq; }
<property name="qq"> <map> <entry> <key><value>zs</value></key> <value>123456</value> </entry> <entry> <key><value>lisi</value></key> <value>456789</value> </entry> </map> </property>
3.4.2.1.6、Properties集合
Properties类似是特殊的Map,他的key
和value
都必须是String
类型。
在配置文件中,我们使用props
标签,里面嵌套prop
标签,一个prop
就是一个键值对,键写在key
属性中,值写在标签内部。
@Data @AllArgsConstructor @NoArgsConstructor public class Person { private Properties properties; }
<property name="properties"> <props> <prop key="username">admin</prop> <prop key="password">123456</prop> </props> </property>
3.4.2.2、自定义类型
3.4.2.2.1、第一种注入方式
@Data @AllArgsConstructor @NoArgsConstructor public class Hobby { private String name; }
<property name="hobby"> <bean class="com.domain.Hobby"/> </property>
我们可以发现第一种注入方式其实就是在property标签里面写一个bean标签,他的劣势也很明显:
配置文件代码冗余,我有一万个类需要引用同一个对象的时候,我同一段代码需要写一万次。
被注入的对象,被多次创建,浪费(JVM)内存资源,因为我每写一个bean标签意味着就创建一个新的对象。
3.4.2.2.2、第二种注入方式
鉴于第一种注入方式的缺点很明显,我们就需要改进,于是就有了第二种注入方式,这种方式是将我们需要注入的对象提前先创建一份出来,谁需要谁去引用即可。
<bean> <property name="hobby"> <ref bean="hobby"/> </property> </bean> <bean id="hobby" class="com.domain.Hobby"> <property name="name"> <value>admin</value> </property> </bean>
3.4.3、set注入的简化写法
3.4.3.1、基于属性的简化
JDK类型注入
3.4.3、set注入的简化写法
3.4.3.1、基于属性的简化
JDK类型注入
我们可以使用value
属性来简化value
标签的值,但是只可以简化8种基本数据类型➕Stirng类型的值。
<!--以前的方式--> <property name="name"> <value>Xiao_Lin</value> </property> <!--简化后的方式--> <property name="name" value="Xiao_Lin"/>
用户自定义类型的注入
我们可以使用ref属性来简化ref
标签的值.
<!--以前的方式--> <property name="hobby"> <ref bean="hobby"/> </property> <!--简化后的方式--> <property name="hobby" ref="hobby"/>
3.4.3.2、基于p命名空间的简化
我们可以发现,bean
标签的很多值都是重复且冗余的,于是可以使用p
命名空间来进行简化。
<!--内置数据类型--> <bean id="person" class="com.domain.Person" p:username="zs" p:password="123456" /> <!--用户自定义类型--> <bean id="hobbyBean" class="com.domain.Hobby"></bean> <bean id="hobby" class="com.domain.Person" p:hobby-ref="hobbyBean"
3.5、构造注入
Spring调用构造方法,通过配置文件为成员变量赋值。如果要使用构造注入,必须提供有参的构造方法。
构造注入使用的标签是constructor-arg标签,一个构造参数就是一对constructor-arg标签。顺序和个数都必须和构造参数一样。
当出现构造方法重载的时候,我们可以通过控制constructor-arg的个数来进行控制。如果出现构造参数个数相同的重载的时候(如第一个构造方法是给name赋值,第二个构造方法给type赋值),我们需要用type属性来指定类型。
四、控制反转(IOC)和依赖注入(DI)
4.1、控制反转(IOC)
控制反转(IoC,Inversion of Control),是一个概念,是一种思想。指将传统上由程序代码直接操控的对象调用权交给容器,通过容器来实现对象的装配和管理。控制反转就是对对象控制权的转移,从程序代码本身反转到了外部容器。通过容器实现对象的创建,属性赋值,依赖的管理。
简单来说控制反转就是把对于成员变量赋值的控制权,从代码中反转(转移)到Spring工厂和配置文件中完成。
IoC 是一个概念,是一种思想,其实现方式多种多样。Spring 框架使用依赖注入(DI)实现 IoC。
4.2、依赖注入(DI)
依赖:classA 类中含有 classB 的实例,在 classA 中调用 classB 的方法完成功能,即 classA对 classB 有依赖。
依赖注入(Dependency Injection):当一个类需要另一个类时,就可以把另一个类作为本类的成员变量,最终通过Spring的配置文件进行注入(赋值)。简单来说就是指程序运行过程中,若需要调用另一个对象协助时,无须在代码中创建被调用者,而是依赖于外部容器,由外部容器创建后传递给程序。
Spring 的依赖注入对调用者与被调用者几乎没有任何要求,完全支持对象之间依赖关系的管理。
4.3、总结
Spring 容器是一个超级大工厂,负责创建、管理所有的 Java 对象,这些 Java 对象被称为 Bean。Spring 容器管理着容器中 Bean 之间的依赖关系,Spring 使用“依赖注入”的方式来管理 Bean 之间的依赖关系。使用 IoC 实现对象之间的解耦和。
五、Spring工厂
5.1、简单对象和复杂对象
5.1.1、简单对象
简单对象指的就是可以直接通过调用构造方法(new)创建出来的对象。
5.1.2、复杂对象
复杂对象指的就是不可以直接通过调用构造方法(new)创建出来的对象。比如JDBC的Connection
对象、Mybatis的SqlSessionFactory
对象。
5.2、Spring创建复杂对象的三种方式
5.2.1、FactoryBean
5.2.1.1、FactoryBean接口
如果在applicationContext.xml
配置文件中配置的class
属性是FactoryBean
接口的实现类,那么通过id
属性获得的是这个类所创建的复杂对象(底层会调用重写的getObject()
方法)。
public class MyFactoryBean implements FactoryBean<Connection> { // 用于书写创建复杂对象的代码,并且把复杂对象作为方法的返回值返回 @Override public Connection getObject() throws Exception { Class.forName("com.mysql.jdbc.Driver"); Connection connection = DriverManager.getConnection("jdbc:mysql:///javaweb?characterEncoding=utf-8&useSSL=false","root","123456"); return connection; } // 返回所创建的复杂对象的Class对象 @Override public Class<?> getObjectType() { return Connection.class; } // 配置是否是单例模式 @Override public boolean isSingleton() { return false; }
<bean id="factoryBean" class="com.test.MyFactoryBean">
/** * 用于测试factoryBean */ @Test public void testFactoryBean(){ ClassPathXmlApplicationContext ctr = new ClassPathXmlApplicationContext("/applicationContext.xml"); Connection conn = (Connection) ctr.getBean("factoryBean"); System.out.println(conn); }
5.2.1.2、FactoryBean接口的细节
- 如果我不想获得创建的复杂对象(Connection),想获得普通的简单对象(FactoryBean),我们仅仅只需在getBean(id)的前面加一个
&
即可。
import java.sql.Connection; import java.sql.DriverManager; import org.springframework.beans.factory.FactoryBean; /** * @Description * @Author XiaoLin * @Date 2021/2/24 19:47 */ public class MyFactoryBean implements FactoryBean<Connection> { @Override public Connection getObject() throws Exception { Class.forName("com.mysql.jdbc.Driver"); Connection connection = DriverManager .getConnection("jdbc:mysql:///javaweb?characterEncoding=utf-8&useSSL=false", "root", "1101121833"); return connection; } @Override public Class<?> getObjectType() { return Connection.class; } @Override public boolean isSingleton() { return true; } }
<?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="factoryBean" class="MyFactoryBean"> </bean> </beans>
import java.sql.Connection; import org.junit.Test; import org.springframework.beans.factory.FactoryBean; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; /** * @Description * @Author XiaoLin * @Date 2021/2/24 19:50 */ public class MyFactoryBeanTest { /** * 用于测试复杂类型对象的创建 */ @Test public void testMyFactoryBeanTest(){ ApplicationContext applicationContext = new ClassPathXmlApplicationContext("/applicationContext.xml"); MyFactoryBean connection = (MyFactoryBean)applicationContext.getBean("&factoryBean");// 获取普通的简单对象FactoryBean,不获取复杂的Connection对象 System.out.println(connection); } }
- isSingleton()方法,如果返回值为true时,他只会创建一个对象,返回false时会创建多个对象,一般根据对象的特点来判断返回true(SqlSessionFactory)还是false(Connection)。
5.2.1.3、BeanFactory实现原理图
5.2.1.4、FactoryBean总结
FactoryBean是Spring中用于创建复杂对象的一种方式 也是Spring原生提供的,后面框架整合会大量运用。
5.2.2、实例工厂
5.2.2.1、FactoryBean的弊端
使用FactoryBean的话有Spring的侵入,实现了FactoryBean接口,一旦离开了Spring,整个类都无法使用。
5.2.2.2、实例工厂的使用
// 实例工厂 public class ConnectionFactory { public Connection getConnection(){ Connection conn = null; try { Class.forName("com.mysql.jdbc.Driver"); conn = DriverManager.getConnection("jdbc:mysql:///javaweb?characterEncoding=utf-8&useSSL=false","root","1101121833"); } catch (ClassNotFoundException | SQLException e) { e.printStackTrace(); } return conn; } }
<bean id="connFactory" class="com.factory.ConnectionFactory"/> <bean id="conn" factory-bean="connFactory" factory-method="getConnection"/>
5.2.3、静态工厂
前面我们学了实例工厂,由于实例工厂的getConnection()方法是实例方法,需要由对象来调用,所以需要先创建对象然后再通过对象来调用方法。
而静态工厂由于getConnection()方法是静态方法,不需要由对象来调用,直接通过类进行调用。这就是实例工厂与静态工厂最大的区别。
public class ConnectionStaticBeanFactory { public static Connection getConnection(){ Connection conn = null; try { Class.forName("com.mysql.jdbc.Driver"); conn = DriverManager.getConnection("jdbc:mysql:///javaweb?characterEncoding=utf-8&useSSL=false","root","1101121833"); } catch (ClassNotFoundException | SQLException e) { e.printStackTrace(); } return conn; } }
<bean id="staticBeanFactory" class="com.factory.ConnectionStaticBeanFactory" factory-method="getConnection"/>
5.3、创建对象的细节
5.3.1、控制简单对象的创建次数
控制简单对象的创建次数我们只需要配置bean
标签的scope
属性值即可。他常用的有两个值:
- singleton:默认为单例模式,只会创建一个简单对象。
- prototype:每次都会创建一个新的对象。
<bean id="person" scope="singleton(prototype)" class="com.doamin.Person"/>
5.3.2、控制复杂对象的创建次数
FactoryBean接口的isSingleton()
方法的返回值来进行控制(如果没有isSingleton()
方法,那么还是通过scope
属性来进行控制):
- 返回true:只会创建一次。
- 返回false:每一次都会创建一个新的对象。
5.3.3、控制对象创建次数的原因
可以被大家共享的对象(SqlSessionFactory、各种Dao、Service)可以只创建一次,不可以被大家共享的对象(Connection、SqlSession、Controller)可以创建多次,控制对象创建次数的最大好处是可以节省不必要的内存浪费。
5.4、对象的生命周期
生命周期指的是一个对象的创建、存活、消亡的一个完整过程。由Spring来负责对象的创建、存活、销毁。了解生命周期,有利于我们使用好Spring为我们创建的对象。
Spring帮我们创建的对象有三个阶段:
- 创建阶段
- 初始化阶段
- 销毁阶段
5.4.1、创建阶段
当 scope = “singleton” 时,Spring工厂创建的同时,对象会随之创建。如果我们不想在Spring工厂创建的同时创建,想在获取对象的时候创建,只需在配置文件的bean标签添加一个lazy-init = true即可。
当 scope = “prototype” 时,Spring工厂会在获取对象的同时创建对象。
5.4.2、初始化阶段
Spring工厂在创建完对象后,会调用对象的初始化方法,完成对应的初始化操作。
初始化方法是由程序员根据需求提供初始化方法,由Spring工厂调用,最终完成初始化操作。他有两种调用的方式:
实现InitializingBean接口(有Spring侵入的问题)。
提供一个普通方法并修改配置文件。
5.4.2.1、InitializingBean接口
// 这个就是初始化方法,做一些初始化操作,Spring会进行调用 @Override public void afterPropertiesSet() throws Exception { // 初始化操作 }
5.4.2.2、提供普通方法
由于实现InitializingBean接口存在Spring侵入的问题,所以Spring提供了另一个方法给我们进行初始化操作,那就是提供一个普通的方法,然后去配置文件中增加init-method="方法名"
熟悉的配置即可。
public void init(){ System.out.println("我是初始化方法"); }
<bean id="product" class="com.domain.Product" init-method="init"/>
5.4.2.3、注意
如果一个对象既实现了InitializingBean接口同时又提供了普通的初始化方法,那么两个初始化方法都会执行,先执行的是InitializingBean接口的方法,再执行普通的初始化方法。
在执行初始化操作之前,会先进行属性的注入,注入在前,初始化在后。
初始化需要做的操作一般是数据库、IO、网络操作。
5.4.3、销毁阶段
在工厂关闭之前,Spring会在销毁对象前,会调用对象的销毁方法,完成销毁操作。
销毁方法是程序员根据需求定义销毁方法,由Spring工厂调用销毁方法,完成销毁操作。他也有两种方法:
- 实现DisposableBean接口。
- 定义普通的销毁方法在配置文件中配置。
5.4.3.1、实现DisposableBean接口
public class Product implements InitializingBean, DisposableBean { @Override public void destroy() throws Exception { System.out.println("销毁操作,资源释放"); } }
5.4.3.2、定义普通方法
public class Product implements InitializingBean, DisposableBean { public void MyDestory(){ System.out.println("自己定义的销毁方法"); } }
<bean id="product" class="com.domain.Product" destroy-method="MyDestory"/>
5.4.3.3、注意
- 销毁方法的操作只适用于scope=“singleton”。
- 销毁操作主要指的是一些资源的释放操作。
5.5、Spring整合配置文件
一般来说像数据库的一些配置信息我们都不会直接写在代码里面,会将他们抽取出来成一个配置文件,再利用Spring进行注入。我们只需要加入一个标签即可完成。
<!--告诉Spring你的db.properties在哪里--> <context:property-placeholder location="classpath:/db.properties"/> <!--用$(db.properties中的key)来进行取值--> <bean id="conn" class="com.factory.BeanFactory"> <property name="driverClassName" value="${jdbc.driverClassName}"/> <property name="url" value="${jdbc.url}"/> <property name="username" value="${jdbc.username}"/> <property name="password" value="${jdbc.password}"/> </bean>
六、自定义类型转换器
6.1、类型转换器
我们写在Spring配置文件中赋值的值都是String类型的,但是我们的实体类是Interger类型的值,按照语法来说,String类型的值是不可以直接赋值给Integer类型的,但是为什么能直接赋值呢?
因为Spring内部帮我们进行了自动的类型转换,Spring通过类型转换器将配置文件中字符串类型的数据,转换成了对象中成员变量对应类型的数据,从而完成注入。
6.2、自定义类型转换器
6.2.1、问题引入
@Data @AllArgsConstructor @NoArgsConstructor public class People { private String name; private Date birthday; }
<bean id="people" class="com.domain.People"> <property name="name" value="XiaoLin"/> <property name="birthday" value="2021-2-6"/> </bean>
我们运行代码之后发现报错了,说String类型的值不可以转化为Date类型的值,说明Spring内部没有这个转换器。
Caused by: java.lang.IllegalStateException: Cannot convert value of type 'java.lang.String' to required type 'java.util.Date' for property 'birthday': no matching editors or conversion strategy found at org.springframework.beans.TypeConverterDelegate.convertIfNecessary(TypeConverterDelegate.java:262) at org.springframework.beans.AbstractNestablePropertyAccessor.convertIfNecessary(AbstractNestablePropertyAccessor.java:585) ... 39 more
Spring内部没有提供特定类型转换器时,而程序员在应用的过程中又需要使用,所以需要程序员自己定义类型转换器。
6.2.2、代码实现
自定义类型转换器我们分为两步实现:
- 实现
Converter<转换前的类型, 转换后的类型>
接口,并且重写里面的方法。 - 在配置文件中进行转换器的注册
public class MyConverter implements Converter<String, Date> {// 他有两个泛型,一个是转换前的类型,另一个是转换后的类型 /* convert方法的作用是将String->Date parm:source代表的是配置文件中需要转换的内容 return:把转换好的值作为返回值,Spring会自动为属性赋值 */ @Override public Date convert(String source) { SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd"); Date parse = null; try { parse = sdf.parse(source); } catch (ParseException e) { e.printStackTrace(); } return parse; } }
<!-- 类型转换器的注册,告诉Spring我们所创建的MyConverter类是类型转换器类, Spring提供了一个类ConversionServiceFactoryBean来完成类型转换器的注册--> <bean id="conversionService" class="org.springframework.context.support.ConversionServiceFactoryBean"> <property name="converters" > <set> <!-- 注册类型转换器--> <ref bean="myConvert"/> </set> </property> </bean>
6.2.3、注意细节
创建ConversionServiceFactoryBean标签的id必须为conversionService,不然不生效。
其实Spring已经内置了日期类型的转换器,但是他只支持以/作为分隔符的字符串的格式:2021/2/6,不支持其他的格式,如果你的字符串格式已经是这种,就无需再写自定义类型转换器。
七、BeanPostProcessor
7.1、概述
BeanPostProcessor称为后置处理Bean,他的作用是对Spring工厂所创建的对象进行二次加工,他是AOP的底层实现,他本质上是一个接口。
7.2、BeanPostProcessor分析
他是一个接口,要实现他的两个方法:
Object postProcessBeforeInitialization(Object bean,String beanName):他的作用是在Spring创建完对象后,在进行初始化方法之前,
通过参数获取到Spring创建好的对象,执行postProcessBeforeInitialization方法进行加工,最终通过返回值返回这个加工好的对象给Spring。
Object postProcessAfterInitialization(Object bean,Stirng beanName):Spring执行完对象的初始化操作之后,运行postProcessAfterInitialization方法进行加工,通过参数获取Spring创建好的对象,最终通过返回值返回给Spring。
在日常的开发中,我们很少去处理Spring的初始化操作,所以没有必要区分前后,所以一般只需要实现其中一个方法即可,且BeanPostProcessor会对Spring工厂中的所有对象进行加工。
Spring知识点,这篇搞定(二)
https://developer.aliyun.com/article/1583471?spm=a2c6h.13148508.setting.28.3cca4f0eq8AZ11