1. 概念:通过工厂类,创建对象
User user = new User();
UserDAO userDAO = new UserDAOImpl();
2. 好处: 解耦合
耦合:指的是代码间的强关联关系,一方的改变会影响到另一方
问题:不利于代码维护
UserService userService = new UserServiceImpl();
package com.jujuxiaer.basic;
import java.io.IOException;
import java.io.InputStream;
import java.util.Properties;
/**
* @author Jujuxiaer
* @date 2020-11-06 20:34
*/
public class BeanFactory {
private static Properties env = new Properties();
/* 将applicationContext.properties文件中的内容读取到Properties集合中,
然后这个IO操作属于系统级资源,避免重复性的打开IO,最后在程序启动的时候一次性的读取想要的内容
所以读这个配置文件时候,可以在静态代码块中进行
*/
static {
// 1. 获得IO输入流
InputStream inputStream = BeanFactory.class.getResourceAsStream("/applicationContext.properties");
// 2. 文件内容封装到Properties集合中
try {
env.load(inputStream);
} catch (IOException e) {
e.printStackTrace();
}
}
/*
对象创建的三种方式:
1. 直接调用构造方法 创建对象 UserService userService = new UserServiceImpl();
2. 通过反射的方式 创建对象 解耦合
Class clazz = Class.forName("com.jujuxiaer.basic.UserServiceImpl");
UserService userService = (UserService) clazz.newInstance();
*/
public static UserService getUserService() {
// 使用new 的方式创建对象
// return new UserServiceImpl();
// 使用反射方式创建对象
UserService userService = null;
try {
// 但是在这里类的全限定类名字符串还是存在耦合,可通过配置文件的方式解决
// Class clazz = Class.forName("com.jujuxiaer.basic.UserServiceImpl");
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;
}
}
// 创建一切想要的对象
public class BeanFactory {
/**
* @param key 配置文件中的key,比如 userService userDAO
* @return
*/
public static Object getBean(String key){
Object ret = null;
try{
Class<?> clazz = Class.forName(env.getProperty(key));
ret = clazz.newInstance();
} catch (Exception e){
}
return ret;
}
}
1. 定义类型(类)
2. 通过配置文件的配置来告知工厂(applicationContext.properties)
key = value
3. 通过工厂获得类的对象
Object ret = BeanFactory.getBean("key");
Spring本质: 工厂ApplicationContext(applicationContext.xml)
1. JDK1.8+
2. Maven3.5
3. IDEA2020.2
4. SpringFramework 5.1.4
官方网址 www.spring.io
Spring的Jar包
# 设置pom依赖
org.springframework
spring-context
5.1.4.RELEASE
Spring的配置文件
1. 配置文件的放置位置: 任意位置,没有硬性要求
2. 配置文件的命名:没有硬性要求,建议我们叫applicationContext.xml
思考:日后应用Spring框架时,需要进行配置文件路径的设置
ApplicationContext
作用: Spring提供的ApplicationContext这个工厂,用于地对象的创建
好处:解耦合
1. 创建类型(类)
2. 配置文件的配置 applicationContext.xml
3. 通过工厂类,获取对象
ApplicationContext
|- ClassPathXmlApplicationContext
// 1. 获取Spring工厂
ApplicationContext ctx = new ClassPathXmlApplicationContext("/applicationContext.xml");
// 2. 通过工厂获取对象
Person person = (Person) ctx.getBean("person");
名词解释
Spring工厂创建的对象,叫做Bean或者组件(Component)
Spring工厂中的相关方法
// 1. 获取Spring工厂
ApplicationContext ctx = new ClassPathXmlApplicationContext("/applicationContext.xml");
// 2. 通过工厂获取对象
Person person = (Person) ctx.getBean("person");
// 不需要进行强转
Person person = ctx.getBean("person", Person.class);
// 此时在Spring工厂的配置当中,只能有一个Bean标签是Person类型, 不然的话会抛异常
// 抛的异常信息: expected single matching bean but found 2: person,person1
Person person = ctx.getBean(Person.class);
// 返回的是 Bean标签中定义的bean的名字 数组
// ctx.getBeanDefinitionNames() 获取的是Spring工厂配置文件中所有Bean标签的id值
String[] beanDefinitionNames = ctx.getBeanDefinitionNames();
for (String beanDefinitionName : beanDefinitionNames) {
System.out.println("beanDefinitionName = " + beanDefinitionName);
}
System.out.println("person = " + person);
// 根据类型获取Spring工厂配置文件中对应的Bean标签中id的值
String[] beanNamesForType = ctx.getBeanNamesForType(Person.class);
for (String beanName : beanNamesForType) {
System.out.println("beanName = " + beanName);
}
// 用于判断会否存在指定的id值的Bean
if (ctx.containsBeanDefinition("11person")) {
System.out.println("True = " + true);
} else {
System.out.println("False = " + false);
}
// 用于判断会否存在指定的id值的Bean, 暂时来看和上面的ctx.containsBeanDefinition("person")作用一样
if (ctx.containsBean("person")) {
System.out.println("True = " + true);
} else {
System.out.println("False = " + false);
}
配置文件中需要注意的细节
1. 只配置class属性
a) 上述这种配置,有没有id值呢,也就是Spring会为我们赋上id值吗
回答: 有id值,值为 com.jujuxiaer.basic.Person#0
b) 上述这种配置方式,应用功能场景:如果这个bean只需使用一次,那么就可以省略id值
如果这个bean会使用多次或者被其他bean引用,则需要设置id值
2. name属性
作用: 用于在Spring配置文件中,为bean对象定义别名
相同:
1. ctx.getBean("id|name"); --> Object
2.
等效
区别:
1. 别名可以定义多个,但是id属性只能有一个值
多个别名之间以逗号分隔
2. XML的id属性的值,命名要求: 必须要以字母开头,后面可跟字母、数字、下划线、连字符,不能以特殊字符开头,比如"/person"
name属性的值,命名没有要求,比如可以设置成"/person"
name属性会应用在特殊命名的场景中: /person (spring+struct1)
XML发展到了今天:id属性的限制,不存在了, 比如亦可以用"/person"命名
3. 代码
// 用于判断会否存在指定的id值的Bean,不能判断name属性值
if (ctx.containsBeanDefinition("p")) {
System.out.println("True = " + true);
} else {
System.out.println("False = " + false);
}
// 用于判断会否存在指定的id值的Bean, 暂时来看和上面的ctx.containsBeanDefinition("person")作用一样
// 也可以判断name属性值
if (ctx.containsBean("p")) {
System.out.println("True = " + true);
} else {
System.out.println("False = " + false);
}
注意:Spring工厂是可以调用对象私有构造函数创建对象的
问题: 在未来的开发过程中,是不是所有的对象都交由Spring工厂来创建呢?
回答: 理论来说,是的。但是又特例:对于实体对象(entity)不会交给Spring创建,而是交由持久层创建。
Spring与日志框架整合后,日志框架就能在控制台中,输出Spring框架运行过程中的一些重要的信息。
好处:便于了解Spring框架的运行过程,利于程序的调试。
Spring 如何整合日志框架
默认
Spring1.2.3都是与commons-logging.jar整合
Spring5.x默认整合的日志框架为logback log4j2
Spring5.x整合log4j
1. 引入log4j jar包
2. 引入log4j.properties配置文件
pom
<dependency>
<groupId>org.slf4jgroupId>
<artifactId>slf4j-log4j12artifactId>
<version>1.7.25version>
<scope>testscope>
dependency>
<dependency>
<groupId>log4jgroupId>
<artifactId>log4jartifactId>
<version>1.2.17version>
dependency>
log4j.properties
# resources文件夹根目录下
### 配置根
log4j.rootLogger = debug,console
### 日志输出到控制台显示
log4j.appender.console = org.apache.log4j.ConsoleAppender
log4j.appender.console.Target = System.out
log4j.appender.console.layout = org.apache.log4j.PatternLayout
log4j.appender.console.layout.ConversionPattern = %d{yyyy-MM-dd HH:mm:ss} %-5p %c{1}:%L - %m%n
通过Spring的工厂以及配置文件,为所创建对象的成员变量赋值
通过编码的方式为成员变量赋值,存在耦合
类为成员变量提供get/set方法
配置Spring的配置文件
<bean id="person" class="com.jujuxiaer.basic.Person">
<property name="id">
<value>10value>
property>
<property name="name">
<value>juzhihuavalue>
property>
bean>
解耦合
Spring底层通过调用对象属性对应的set方法,完成成员变量的赋值,这种方式我们称为set注入
针对不同类型的成员变量,在标签中需要嵌入其他标签
jujuxiaer
<property name="emails">
<list>
<value>[email protected]value>
<value>[email protected]value>
list>
property>
对应的成员变量为Set tels; 因为Set的泛型此时是String,所有标签中嵌套的为标签,如果Set泛型为其他的类型,则标签中嵌套的为其他标签
<property name="tels">
<set>
<value>1341111111value>
<value>1342222222value>
set>
property>
<property name="addresses">
<list>
<value>湖北省武汉市value>
<value>上海市黄浦区value>
list>
property>
<list>
list>
注意: map -- entry -- 键有特定的标签 <key>key>
值根据对应的类型选择对应类型的标签,比如<ref bean 等等
name="qqs">
<map>
<entry>
<key><value>jujuxiaervalue>key>
<value>1225449109value>
entry>
map>
property>
Properties类型:特殊的Map,该map的键为String,值也为String
<property name="p">
<props>
<prop key="key1">value1prop>
<prop key="key2">value2prop>
props>
property>
需要程序员自定义类型转换器,处理。
为成员变量提供get/set方法
配置文件中进行注入(赋值)
<bean id="userService" class="com.jujuxiaer.basic.UserServiceImpl">
<property name="userDAO">
<bean class="com.jujuxiaer.basic.UserDAOImpl"/>
property>
bean>
第一种方式存在的问题
1. 配置文件代码冗余
2. 被注入的对象(userDAO),多次创建,浪费(JVM)内存资源
为成员变量提供get/set方法
配置文件中进行注入(赋值)
<bean id="userDAO" class="com.jujuxiaer.basic.UserDAOImpl"/>
<bean id="userService" class="com.jujuxiaer.basic.UserServiceImpl">
<property name="userDAO">
<ref bean="userDAO"/>
property>
bean>
# Spring4.x 废弃了 <ref local=""/> 基本等效 <ref bean=""/>
# JDK类型注入
<property name="name">
<value>jujuxiaervalue>
property>
<property name="name" value="jujuxiaer"/>
# 注意:value属性,只能简化8种基本类型+String 注入标签
# 用户自定义类型
<bean id="userDAO" class="com.jujuxiaer.basic.UserDAOImpl"/>
<bean id="userService" class="com.jujuxiaer.basic.UserServiceImpl">
<property name="userDAO">
<ref bean="userDAO"/>
property>
bean>
# 用户自定义类型,简化后
<bean id="userDAO" class="com.jujuxiaer.basic.UserDAOImpl"/>
<bean id="userService" class="com.jujuxiaer.basic.UserServiceImpl">
<property name="userDAO" ref="userDAO"/>
bean>
# JDK类型注入
<bean id="person" class="com.jujuxiaer.basic.Person">
<property name="name">
<value>jujuxiaervalue>
property>
bean>
<bean id="person" class="com.jujuxiaer.basic.Person" p:name="jujuxiaer"/>
# 注意:value属性,只能简化8种基本类型+String 注入标签
# 用户自定义类型
<bean id="userDAO" class="com.jujuxiaer.basic.UserDAOImpl"/>
<bean id="userService" class="com.jujuxiaer.basic.UserServiceImpl">
<property name="userDAO">
<ref bean="userDAO"/>
property>
bean>
# 用户自定义类型,简化后
<bean id="userDAO" class="com.jujuxiaer.basic.UserDAOImpl"/>
<bean id="userService" class="com.jujuxiaer.basic.UserServiceImpl" p:userDAO-ref="userDAO"/>
注入:通过Spring的配置文件,为成员变量赋值
set注入: Spring通过调用set方法,通过配置文件为成员变量赋值
构造注入: Spring通过调用构造方法,通过配置文件为成员变量赋值
提供有参的构造方法
public class Customer implements Serializable {
private String name;
private int age;
public Customer(String name, int age) {
this.name = name;
this.age = age;
}
}
Spring的配置文件
<bean id="customer" class="com.jujuxiaer.basic.constructer.Customer">
<constructor-arg>
<value>jujuxiaervalue>
constructor-arg>
<constructor-arg>
<value>102value>
constructor-arg>
bean>
通过控制标签数量进行区分
2.2 参数个数相同时
通过在标签引入type属性来进行类型的区分<constructor-arg type="">
<bean id="customer" class="com.jujuxiaer.basic.constructer.Customer">
<constructor-arg type="int">
<value>23value>
constructor-arg>
bean>
未来实战中使用set注入,还是构造注入?
回答:set注入会更多
原因:1. 构造注入麻烦,有重载
2. Spring框架底层,大量应用了set注入
控制: 对于成员变量的赋值的控制权
反转控制:把对于成员变量赋值的控制权,从代码中反转(转移)到了Spring工厂和配置文件中完成
好处: 解耦合
底层实现:工厂设计模式
注入:通过Spring工厂和配置文件,为对象(bean/组件)的成员变量赋值
依赖注入:一个类需要另一个类时,就意味着依赖,一旦出现依赖,就可以把另一个类作为本类的成员变量,最终通过Spring配置文件进行注入(赋值)。
复杂对象:指的是不能直接通过new构造方法创建的对象
Connection
SqlSessionFactory
开发步骤
Spring配置文件的配置
# 如果Class属性中指定的类型是FactoryBean接口的实现类,那么通过id值获得的是这个类所创建的复杂对象,比如Connection
<bean id="conn" class="com.jujuxiaer.factorybean.ConnectionFactoryBean"/>
细节
如果 ,ctx.getBean("&conn"); 获得的就是ConnectionFactoryBean对象
isSingleton方法
返回true,只会创建一个复杂对象
返回false,每一次都会创建新的对象
问题:根据这个对象的特点,决定是返回true(SqlSessionFactory),还是false(Connection)
mysql高版本在连接创建时,需要指定SSL证书,解决问题的方式
url = "jdbc:mysql://localhost:3306/suns?useSSL=false"
依赖注入的体会(DI)
把ConnectionFactoryBean中依赖的四个字符串信息,进行配置文件的注入
好处:解耦合
FactoryBean的实现原理(简易版)
接口回调
1. 为什么Spring规定FactoryBean接口 实现 并且getObject()?
2. ctx.getBean("conn");获得的是复杂对象Connection,而不是获得ConnectionFactoryBean
Spring内部运行流程:
1. 通过conn获得ConnectionFactoryBean类对象,进而通过instanceod判断出是FactoryBean接口的实现类
2. Spring按照规定getObejct() --> Connection
3. 返回Connection
Factory总结
Spring中用于创建复杂对象的一种方式,也是Spring原生提供的,后续我们在讲解Spring整合其他框架时,会大量应用FactoryBean
1. 避免Spring框架的侵入
2. 整合遗留系统
开发步骤
<bean id="connFactory" class="com.jujuxiaer.factorybean.ConnectionFactory"/>
<bean id="conn" factory-bean="connFactory" factory-method="getConnection"/>
开发步骤
<bean id="conn" class="com.jujuxiaer.factorybean.StaticConnectionFactory" factory-method="getConnection"/>
<bean id="account" class="com.jujuxiaer.scope.Account" scope="singleton|prototype"/>
singleton: 只会创建一次简单对象
prototype: 每一次都会创建简单对象
默认为singleton
FactoryBean {
isSingleton() {
return true 只会创建一次
return false 每一次都会创建新的
}
}
如果没有isSingleton方法,还是通过制定scope属性,进行对象创建次数的控制。
好处:节省不必要的内存浪费
什么样的对象只创建一次
1. SqlSessionFactoryBean
2. DAO
3. Service
什么样的对象,每一次都要创建新的呢?
1. Connection(因为要控制事务,不能被大家共用)
2. SqlSession(里面封装了Connection) | Session
3. Struct2 Action
指的是一个对象创建、存活、消亡的一个完整过程。
由Spring来负责对象的创建、存活、销毁,了解生命周期后,有利于我们使用好Spring为我们创建的对象。
创建阶段
Spring工厂何时创建
scope=“singleton”
Spring工厂创建的同时,对象创建
注意: 设置scope="singleton" 这种情况下,也需要在获取对象的同时,创建对象
scope=“prototype”
Spring工厂会在获取对象的同时,创建对象
ctx.getBean("")
初始化阶段
Spring工厂在创建完成对象后,调用对象的初始化方法,完成对应的初始化操作
1. 初始化方法提供:程序员根据需求,提供初始化方法,最终完成初始化操作
2. 初始化方法调用:Spring工厂进行调用
InitializingBean接口
public class Product implements InitializingBean {
public Product() {
System.out.println("Product.Product");
}
/**
* 完成对象的初始化操作
* @throws Exception
*/
public void afterPropertiesSet() throws Exception {
System.out.println("Product.afterPropertiesSet");
}
}
对象中提供一个普通的方法
public void myInit() {
}
//
细节分析
如果一个对象既实现了InitializingBean,又提供了普通的初始化方法,执行顺序是怎样呢?
1. InitializingBean
2. 普通的初始化方法
注入一定发生在初始化操作的前面
什么叫做初始化操作
资源的初始化:数据库 IO 网络 ......
销毁阶段
Spring销毁对象前,会调用对象的销毁方法,完成销毁操作
1. Spring什么时候销毁所创建的对象?
ctx.close();
2. 销毁方法:程序员根据自己的需求,定义销毁方法,完成销毁操作
调用:Spring工厂完成调用
public class Product implements InitializingBean, DisposableBean {
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
System.out.println("Product.setName");
}
public Product() {
System.out.println("Product.Product");
}
// 完成对象的初始化操作
public void afterPropertiesSet() throws Exception {
System.out.println("Product.afterPropertiesSet");
}
public void myInit() {
System.out.println("Product.myInit");
}
public void destroy() throws Exception {
System.out.println("Product.destroy");
}
public void myDestroy() throws Exception {
System.out.println("Product.myDestroy");
}
}
<bean id="product" class="com.jujuxiaer.life.Product" init-method="myInit" destroy-method="myDestroy">
<property name="name" value="productName"/>
</bean>
@Test
public void test17() {
// 1. 获取Spring工厂
ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("/applicationContext.xml");
ctx.close();
}
销毁方法的操作只适用于scope=“singleton”
什么叫做销毁操作
主要指的是 资源的释放操作,比如 io.close(); connection.close();
把Spring配置文件中需要经常修改的字符串信息,转移到一个更小的配置文件中
1. Spring的配置文件中存在经常修改的字符串?
存在,以数据库连接相关的参数 代表
2. 经常变化字符串,在Spring配置文件中,直接修改
不利于项目维护(修改)
3. 转移到一个小的配置文件(.properties)
利于维护(修改)
配置文件参数化:利于Spring配置文件的维护(修改)
提供一个小的配置文件(.properties)
# 文件名称:随意;放置位置:随意
jdbc.driverClassName = com.mysql.jdbc.Driver
jdbc.url =jdbc:mysql://localhost:3306/suns?useSSL=false
jdbc.user = root
jdbc.password = 123456
Spring的配置文件和小配置文件的整合
<context:property-placeholder location="classpath:/db.properties"/>
在Spring配置文件中通过${key}获取小配置文件中对应的值
作用:Spring通过类型转换器把配置文件中字符串类型的数据,转换成了对象中成员变量对应类型的数据,进而完成了注入
需要自定义类型转换器的原因:
当Spring内部没有提供特定类型转换器时,而程序员在应用的过程中还需要使用个,那么就需要程序员自己定义类型转换器
类实现Convert接口
/**
* @author Jujuxiaer
* @date 2020-11-08 14:25
* Converter中String是待转换的原始数据类型,Date是转换后的数据类型
*/
public class MyDateConverter implements Converter<String, Date> {
/*
convert作用: String --> Date
param: source 代表的是配置文件中 日期字符串 2020-11-07
return: 当把转换好的Date作为convert方法返回值后,Spring自动为birthday属性进行注入(赋值)
*/
@Override
public Date convert(String s) {
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
Date date = null;
try {
date = sdf.parse(s);
} catch (ParseException e) {
e.printStackTrace();
}
return date;
}
}
在Spring的配置文件中进行配置
MyDateConverter对象创建出来
<bean id="myDateConverter" class="com.jujuxiaer.converter.MyDateConverter"/>
类型转换器的注册
<bean id="conversionService" class="org.springframework.context.support.ConversionServiceFactoryBean">
<property name="converters">
<set>
<ref bean="myDateConverter"/>
set>
property>
bean>
MyDateConverter中的日期格式,通过依赖注入的方式,由配置文件完成赋值。
public class MyDateConverter implements Converter<String, Date> {
private String pattern;
public String getPattern() {
return pattern;
}
public void setPattern(String pattern) {
this.pattern = pattern;
}
/*
convert作用: String --> Date
param: source 代表的是配置文件中 日期字符串 2020-11-07
return: 当把转换好的Date作为convert方法返回值后,Spring自动为birthday属性进行注入(赋值)
*/
@Override
public Date convert(String s) {
SimpleDateFormat sdf = new SimpleDateFormat(pattern);
Date date = null;
try {
date = sdf.parse(s);
} catch (ParseException e) {
e.printStackTrace();
}
return date;
}
}
<bean id="myDateConverter" class="com.jujuxiaer.converter.MyDateConverter">
<property name="pattern" value="yyyy-MM-dd"/>
bean>
ConversionServiceFactoryBean定义id属性,必须为conversionService
Spring框架内置了日期类型转换器
内置的日期类型转换器支持的日期格式:2010/11/08
而不支持,比如2020-11-08 日期格式
BeanPostProcessor作用:对Spring工厂所创建的对象,进行再加工
AOP底层实现:
注意: BeanPostProcesser接口
xxx (){
}
程序员源实现BeanPostProcesser规定接口中的方法:
Object postProcessBeforeInitialization(Object bean, String beanName)
作用:Spring创建完对象,并进行注入后,可以运行Before方法进行加工
获得创建好的对象:通过方法的参数
最终通过返回值交给Spring框架
Object postProcessAfterInitialization(Object bean, String beanName)
作用:Spring执行完对象的初始化操作后,可以运行After方法进行加工
获得创建好的对象:通过方法的参数
最终通过返回值交给Spring框架
实战中:
很少处理Spring的初始化操作(InitializingBean),没有必要区分Before After。只需要实现其中一个After方法即可。但注意postProcessBeforeInitialization方法也需要将bean对象返回。
BeanPostProcesser开发步骤
类 实现BeanPostProcesser接口
public class MyBeanPostProcessor 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("xiaowangba");
return bean;
}
}
Spring的配置文件进行配置
<bean id="myBeanPostProcessor" class="com.jujuxiaer.beanpost.MyBeanPostProcessor"/>
BeanPostProcessor细节
BeanPostProcessor会对Spring工厂中所有创建的对象进行加工