1.Spring引言与设计模式
1.1Spring概述
Spring是一个轻量级的JavaEE解决方案,整合了众多的设计模式
1.工厂设计模式
2.代理模式
3.模板模式
4.策略模式
....
Spring最重要的三个概念
IOC(Inverse of Control)
控制反转DI(Dependency Injection)
依赖注入AOP(Aspect Oriented Programming)
面向切面
1.2工厂设计模式
-
概念:通过工厂类,创建对象
-
好处
解耦合,利于代码的维护
将对象的创建过程封装起来
工厂通俗的说就是进行生产的,在Java中的工厂就是生产对象的,我们知道,我们在开发中的对象应该都交给SpringIOC的容器来管理,我们用对象,就从工厂中拿,而不是我们自己去new对象
1.2.1简单工厂实现
编码实现
比如说我们有一个UserServiceImpl,我们使用的时候从工厂中拿,那么就可以写一个简单的工厂类,写一个方法里创建一个UserServiceImpl对象,然后我们在外面直接调用这个方法就给我们返回了这个对象
public class EasyBeanFactory {
public static UserService getUserService(){
return new UserServiceImpl();
}
}
//在Controller中使用
public class UserController{
UserService userService = EasyBeanFactory.getUserService();
//调用方法
public Result login(){
userService.xx();...
}
}
存在的问题
虽然说外面使用时没有了耦合,但是在工厂类中存在耦合,我们硬编码了new UserServiceImpl(),那么有没有一种方式可以解决这种问题呢? ----反射
反射的话创建对象时只需要知道Class对象即可创建不同的对象,下面我们使用反射的方式创建对象
1.2.2反射工厂
我们使用反射工厂创建对象,可以使用一个方法就可以创建出不同的对象,我们只需要传入一个类的全类名就可以为我们创建不同对象
public class ReflectFactory {
public static Object getBean(String className) {
Class clazz = null;
Object obi = null;
try {
clazz = Class.forName(className);
obi = clazz.newInstance();
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
return obi;
}
1.2.3反射工厂+配置文件
上面的反射工厂其实已经可以是非常通用的工厂了,我们想要什么对象只需要传给类名即可,那么还有一些问题:
基于上面的反射工厂方式,工厂如何管理我们的对象呢?如何控制对象的单例和多例?如何在项目启动的时候我们的对象工厂就帮我们创建出来呢?
基于上面的反射工厂方式,我们只能是在我们用到对象的时候才能获得对象,在真实的开发中,我们希望我们想要的对象在项目启动的时候就可以帮我们将我们需要的对象全部创建好,我们都知道,创建对象的过程是消耗资源的,并且反射的效率也是低的,那么如果不能在项目启动的时候就帮我们创建好对象,而是我们想要调用对象的功能时才开始为我们创建对象,那么整个系统的性能将会大幅下降。(使用配置文件解决)
还有一个致命的缺陷,如果说这个对象有一个必要的属性,我们希望在对象创建出来后我们可以直接使用,那么我们使用上面的方式创建对象时为属性赋值将非常的繁琐。
最最致命的缺陷就是,我们希望工厂不仅可以帮我们创建对象,并且可以管理这些对象,即类似于容器。管理对象的后续的一些加工等
。这些需求都是上面的反射工厂方式都是做不到的。
因为 上面的工厂方式只能帮我们创建出一个对象交给我们,因为在启动项目的时候工厂并不知道你需要什么对象,而是在你调用方法的时候他才知道你需要什么对象并帮你创建
所以我们就可以使用配置文件的形式将我们想要用的对象配在文件中,在工厂启动的时候读取配置文件,然后根据配置文件帮我们创建相应的对象。
解决
想要实现上面的需求,我们可以将我们在开发中想要交给工厂(或者容器)管理的对象,全部写在一个配置文件中,配置文件可以写很多的信息,比如是否单例,是否懒加载,最重要的就是我们可以给这个对象配置属性值,只要项目一启动,就去读取这个配置文件,按照我们的配置为我们创建出我们需要的对象,并可以设置对象的一些属性等,我们在用的使用直接就去拿,省去了运行过程中的创建过程,最重要的就是 工厂可以管理这些对象,保证后续的一些操作
编码
配置文件
想要让工厂创建对象,就在配置文件里定义
#key代表唯一表示 value代表类的全类名
user=com.shwsh.simulate.entity.User
#可以配置一些属性的信息 单例,懒加载,属性信息 因为这里使用的是Properties,不好表示,使用xml形式更简单 (spring的配置文件就是使用的xml形式)
...
public class ReflectPropertiesFactory {
private static Properties pro = new Properties();
private static InputStream inputStream = null;
//读取配置文件,将k-v键值对加载到pro中
static {
inputStream = ClassLoader.getSystemResourceAsStream("bean.properties");
try {
pro.load(inputStream);
} catch (IOException e) {
e.printStackTrace();
}finally {
if(inputSterem!=null){
try{
inputStream.close();
}catch(Exception e){
}
}
}
}
public static Object getBean(String key) {
//通过传入的key找到配置文件中对应的全类名 创建对象
Class<?> clazz = Class.forName(pro.getProperty(key));
Object obj = clazz.newInstance();
//通过配置文件读取其他的信息,比如是否单例 是否懒加载等,下面可以进行操作
return obj;
}
}
我们使用配置文件的方式,这样容器启动时就知道我们要创建出的对象的所有信息,并且可以为我们管理这些对象,(实际上,Spring就是这样做的)
1.2.4总结
通过工厂模式的一步步演变,我们找到了最适合开发的一种,就是配置文件+反射工厂的模式
事实上,Spring的IOC核心的思想就是配置文件+反射工厂的模式,我们在applicationContext.xml(Spring的配置文件)
中配置各个对象的信息,然后Spring的工厂就读取这个配置文件,并按照这个配置文件中的我们配置的各个对象信息,为我们创建出对应的对象,并对这些对象进行管理
2.Spring工厂
2.1初识Spring
2.1Spring的各个部分的依赖
2.2Spring的配置文件
配置文件的命名:没有硬性要求,但是一般都使用 applicationContext.xml
<?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">
</beans>
2.3ApplicationContext工厂及其实现
ApplicationContext是一个顶级的接口
,是一个重量级
资源,占用大量的内存,不会频繁的创建对象,一个应用只会创建一个工厂对象,(线程安全)
//两个最常用的实现类
非Web环境 ClassPathXmlApplicationContext
Web环境 XmlWebApplicationContext(在spring-webmvc的依赖中)
2.4第一个Spring程序
分为三步
-
第一步:创建类型
public class Person { private String name; private int age; }
-
第二步:将对象的信息写到配置文件中
这一步就相当于我们在1.2.3节的将一个类的信息写到properties文件中一样
<?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">
<!--id必须唯一 class是这个类的全类名 -->
<bean id="person" class="com.shwsh.twosection.Person"></bean>
</beans>
- 第三步:获取工厂
(ApplicationContext)
,通过工厂拿对象
这一步就相当于1.2.3节的在工厂中读取配置文件的信息,然后通过传来的key给你返回指定的对象
@Test
public void test1(){
//指定配置文件的位置,获取ApplicationContext获取工厂
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("/applicationContext.xml");
//通过工厂获取配置的Person的对象 传入配置文件的id
Person person = (Person) applicationContext.getBean("person");
System.out.println(person);
}
2.2ApplicationContext工厂
2.2.1创建ApplicationContext工厂
//指定配置文件的位置创建ClassPathXmlApplicationContext对象
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("/applicationContext.xml");
2.2.2常用的方法
//传入配置文件的标签的(id或者name属性)获取对应的对象 返回类型是Object 需要强转
Person person1 = (Person) applicationContext.getBean("person");
//传入配置文件的标签的(id或者name属性)获取对应的对象,返回类型是传入的类型
Person person2 = applicationContext.getBean("person",Person.class);
//直接传入类型信息获取对象,注意:配置文件中不能有多个同一类型的对象
Person person3 = applicationContext.getBean(Person.class);
//获取所有bean的id属性
String[] idsAll = applicationContext.getBeanDefinitionNames();
//获取指定类型的bean的id属性
String[] idsForType = applicationContext.getBeanNamesForType(Person.class);
//判断是否存在指定id的bean, 注意:不能判断name属性
boolean b = applicationContext.containsBeanDefinition("person");
//判断是否存在指定id的bean 可以判断name属性
boolean person = applicationContext.containsBean("person");
//获取配置文件中配置的bean标签的个数
int count = applicationContext.getBeanDefinitionCount();
2.3applicationContext.xml
2.3.1清晰认识
配置文件中配置的对象,一个标签代表的不一定是一个对象(可以创建多个),也不一定是多个对象(默认为单例)
,一个标签代表的是一个类具有相同属性的一类对象
,所以说可以配置同一个类的多个标签
2.3.2bean标签基本属性详解
2.3.2.1class属性
class属性是bean标签中必须要写的,id和name属性都可以省略
class属性代表创建对象的类型
规定:一个类想要使用Spring的配置文件来配置,必须要有无参构造器
<?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="person1" class="com.shwsh.twosection.Person"></bean>
<bean id="person2" class="com.shwsh.twosection.Person"></bean>
</beans>
2.3.2.2id属性
id属性代表的对象的唯一表示
id属性可以不写(多用于对象只用一次的情况),Spring为其分配默认名就是 全名类#0 从0开始
看下面的例子
<?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">
<!--不写id属性 默认com.shwsh.twosection.Person#0-->
<bean class="com.shwsh.twosection.Person"></bean>
<!--不写id属性 默认com.shwsh.twosection.Person#1-->
<bean class="com.shwsh.twosection.Person"></bean>
</beans>
2.3.2.3name属性
使用较少
name属性可以定义多个,以逗号分割
使用ApplicationContext工厂对象的getBean()方法可以传入标签的name属性来获取对象
注意,一个配置文件中所有标签的id和name属性的设置不能有重复的
<?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">
<!--id只能定义一个 name可以定义多个,逗号分割-->
<bean id="person" name="p1,p2" class="com.shwsh.twosection.Person"></bean>
<bean id="person2" name="p3,p4" class="com.shwsh.twosection.Person"></bean>
</beans>
2.4Spring工厂简单原理
这个原理就跟我们1.2.3的反射工厂+配置文件类似,都是先读取配置文件,获取所有的bean标签拿到id或name以及class属性值,然后然后通过反射为我们创建对象最后在我们想要获取某个对象是就将对象给我们返回
因为使用反射,所以默认
会调用类的无参构造器
,注意即使你是私有的照样可以获取(反射的强大之处) 且对象是在工厂初始化完成时就帮我们创建的,默认就是非懒加载,我们可以使用标签的属性设置为懒加载,即我们需要对象时工厂才帮我们创建
2.5对象属性的注入(Injection)
2.5.1概述
通过Spring的工厂和配置文件,为所创建的对象的成员变量赋值,就是说我们在工厂到的对象的属性已经被赋值,而不用我们获取对象后在为其属性赋值,这样就有了耦合
分为set注入和构造注入 即通过调用set方法为属性赋值或者调用构造器为属性赋值
@Test
public void test(){
//我们希望当我们工厂中获取这个对象的时候,这个对象的属性已经被初始化(非默认初始化)
Person person = (Person) applicationContext.getBean("person");
//即我们希望Spring的工厂帮我们创建对象的时候就帮我们做下面的赋值操作,而不是我们拿到一个对象后还要为其属性赋值,
person.setAge(1);
person.setName("tom");
}
2.5.2set方法注入
2.5.2.1要求与概述
- 要求:
对应的属性必须要有set方法,并且set方法名必须按照严格的规范,例如属性是name,那么方法必须是setName这种格式,并且set方法的权限必须是public
- 因为这里为属性赋值没有用到反射获取set方法为其赋值,是直接
调用
的对象的set方法为其赋值,所以必须保证有set方法并且set方法的权限必须是public
- 一共分为两大类的注入 JDK内置类型注入 以及 自定义类型的注入
set方法对应的标签是 一个property代表一个属性的set方法
<property></property>
2.5.3set方法注入之JDK内置类型注入
2.5.3.1基本数据类型+String类型注入
使用value标签完成赋值操作
<bean id="person" class="com.shwsh.twosection.Person">
<!--使用value属性直接为其赋值-->
<property name="属性名">
<value>值</value>
</property>
<property name="属性名">
<value>值</value>
</property>
<!--简单的写法 直接将value写在 property内-->
<property name="属性名" value="值"> </property>
</bean>
代码演示
实体类
@Data
@ToString
@NoArgsConstructor
@AllArgsConstructor
public class Person {
private int id;
private char sex;
private String name;
private double score;
}
配置文件
<?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">
<!--简单类型+Stirng直接使用 <property>标签name和value属性 即可完成赋值-->
<bean id="person" class="com.shwsh.twosection.Person">
<property name="name" value="tom"></property>
<property name="id" value="1"></property>
<property name="sex" value="男"></property>
<property name="score" value="123.8"></property>
</bean>
</beans>
测试
@Test
public void test(){
Person person = (Person) applicationContext.getBean("person");
System.out.println("person = " + person);
//person = Person(id=1, sex=男, name=tom, score=123.8)
}
2.5.3.2数组类型的注入
使用list标签完成赋值操作
<bean id="person" class="com.shwsh.twosection.Person">
<property name="属性名">
<list>
<value>value1</value>
<value>value2</value>
<value>value3</value>
</list>
</property>
</bean>
代码示例
<bean id="person" class="com.shwsh.twosection.Person">
<!--emails属性是数组类型-->
<property name="emails" >
<!--使用list标签-->
<list>
<!--这里的标签不一定是value value只是8种数据类型+String的赋值方式 数组的类型,如果是Object类型的数组,任何类型都能加-->
<value>wsh@qq.com</value>
<value>wsh@gmail.com</value>
<value>wsh@123.com</value>
<!--Object类型的数组所有数据类型都能存 下面的集合类似-->
<set></set>
<list></list>
.......
</list>
</property>
</bean>
2.5.3.2List类型的注入
跟数组的注入类似 这里也使用list标签完成赋值操作
<bean id="person" class="com.shwsh.twosection.Person">
<!--qqs属性是List类型-->
<property name="qqs" >
<!--使用list标签-->
<list>
<!--赋值-->
<value>wsh@qq.com</value>
<value>wsh@gmail.com</value>
<value>wsh@123.com</value>
<!--没有泛型任何数据类型都能存-->
<set></set>
<list></list>
.....
</list>
</property>
</bean>
2.5.3.3Set类型的注入
跟数组的注入类似 这里使用set标签完成赋值操作
<bean id="person" class="com.shwsh.twosection.Person">
<!--tels属性是Set集合类型-->
<property name="tels" >
<!--使用set标签-->
<set>
<!--为集合赋值-->
<value>123232</value>
<value>22322</value>
<value>2222454</value>
<!--不加泛型 任何类型都能加-->
<set></set>
<list></list>
.....
</set>
</property>
</bean>
2.5.3.4Map类型的注入
使用map标签完成赋值
<bean id="person" class="com.shwsh.twosection.Person">
<!--Map<String,String> map -->
<property name="map">
<map>
<!--一个map里面嵌套多个entry标签 因为map本质就是entrt数组-->
----entry <entry>
<!--entry的key使用key标签 里面嵌套值 因为这里Map的key是String类 型,所以使用value标签为key赋值-->
---key <key>
<value>k1</value>
---key </key>
<!--entry的value直接使用赋值标签 value list set ref等
因为这里Map的泛型是Stirng,那么就是用value标签为其赋值-->
---value <value>ssdd</value>
-----entry </entry>
<entry>
...
</entry>
...
</map>
</property>
</bean>
2.5.4Properties类型的注入(特殊)
使用props标签完成赋值
Properties是一个特殊的Map,key和value都是String类型
<bean id="person" class="com.shwsh.twosection.Person">
<!--private Properties pro-->
<property name="pro">
<props>
<!--key写在key标签里 value直接写在标签之间-->
<prop key="k1">v1</prop>
<prop key="k2">v2</prop>
<prop key="k3">v3</prop>
</props>
</property>
2.5.5JDK内置类型注入总结
-
基本数据类型+Stirng
使用value
标签赋值 -
List和数组
使用list
标签赋值 -
Set
使用set
标签赋值 -
Map
使用map
标签赋值里面使用entry标签,key有特定的标签 value没有特定标签
-
Properties
使用props
标签
2.5.4set注入之自定义类型的注入
使用ref标签
第一种写法(不常用)
问题:
- 配置文件代码冗余
- 被注入的对象被多次创建,浪费内存
<bean id="person" class="com.shwsh.twosection.Person">
<property name="dog">
<!--这里的这个对象只用这一次 可以不写id和name属性-->
<!--这里直接写bean标签即可-->
<bean class="com.shwsh.twosection.Dog">
<property name="dage" value="123"></property>
<property name="dname" value="小黑"></property></bean>
</property>
</bean>
第二种写法 (最常用)
使用ref标签引用其他的bean标签
好处:可以共用一个对象,节省内存
创建一个bean,让其他的bean去引用
<bean id="person" class="com.shwsh.twosection.Person">
<property name="dog" ref="dog">
<!--使用ref标签引用其他bean 写其他bean标签的id属性
简单写法也可以直接在property标签写ref属性-->
<ref bean="dog"></ref>
</property>
</bean>
<!--被引用的bean-->
<bean id="dog" class="com.shwsh.twosection.Dog">
<property name="dname" value="大黄"></property>
<property name="dage" value="12"></property>
</bean>
2.5.5简化写法(命名空间p)
第一步引入命名空间(手动导入即可)
- 简单类型+String直接写
p:属性名="值"
- 自定义类型
p:属性名-ref="引用bean的id"
<!--示例-->
<bean id="person" class="com.shwsh.twosection.Person"
<!--基本类型+String-->
p:age="13"
<!--引用类型-->
p:dog-ref="dog"/>
2.5.7注入之构造器注入(很少用)
2.5.7.1概述
Spring调用构造器 通过配置文件 为属性赋值
前提:提供有参
的构造器
2.5.7.2基本注入
Java类
public class Customer {
//有参构造器
public Customer(String cname, Integer cage) {
this.cname = cname;
this.cage = cage;
}
private String cname;
private Integer cage;
public Customer() {
}
}
配置文件
<bean id="customer" class="com.shwsh.twosection.Customer">
<!--使用哪个构造器就要跟参数列表的属性对应
有几个参数写几个constructor-arg标签,且数据类型要对应-->
<constructor-arg value="战三"> </constructor-arg>
<constructor-arg value="123"></constructor-arg>
</bean>
2.5.7.3构造器重载指定type注入
参数个数不同时:控制<constructor-arg>标签的数量进行区分
参数个数相同时:通过指定type属性进行区分<constructor-arg type="">
2.5.8注入总结图解
2.6控制反转IOC
反转控制IOC(Inverse of Control)
对于成员变量的赋值的控制权由在代码中直接new的过程 转移到 Spring的配置文件+Spring工厂当中
控制:对于成员变量赋值的控制权
好处:解耦合
- 底层实现:
工厂设计模式
2.7依赖注入DI
依赖注入DI (Dependency Injection)
注入:通过Spring的配置文件+工厂为对象的成员变量赋值
依赖注入:当一个类需要另一个类时,就意味着依赖,一旦出现依赖,就可以把另一个类作为本类的成员变量最终通过Spring的配置文件进行注入(赋值)
2.8Spring工厂创建复杂对象
2.8.1简单对象与复杂对象
- 简单对象:
可以直接使用new关键字创建的对象
比如 Person UserService等 - 复杂对象:
不能通过new关键字创建的对象,
比如 Connection SqlSessionFactory等
2.8.2FactoryBean创建复杂对象
FactoryBean是一个接口 要实现这个接口重写方法创建复杂对象
如果一个bean标签的class的类型是一个FactoryBean接口的实现类,那么通过id获取的这个Bean是实现类创建的复杂对象
Spring中用于创建复杂对象的一种方式,也是Spring原生提供的,在Spring整合其他框架时大量使用
开发步骤
1.实现FactoryBean
接口
2.Spring配置文件的配置
2.8.2.1FactoryBean接口详解
//实现此接口时可以指定一个泛型,此泛型就是创建的复杂对象的类型
public interface FactoryBean<T> {
//此方法用于书写复杂对象的创建过程,最终将创建好的对象返回
@Nullable
T getObject() throws Exception;
//此方法用于返回复杂对象的Class对象
@Nullable
Class<?> getObjectType();
//设置此对象是否是单例的 默认是true
default boolean isSingleton() {
return true;
}
}
2.8.2.2演示创建MySQL的连接对象
Java代码
//自定义类实现FactoryBean接口
public class ConnectionFactoryBean implements FactoryBean<Connection> {
//因为使用了泛型 所以返回值类型就是Connection
@Override
public Connection getObject() throws Exception {
Class.forName("com.mysql.jdbc.Driver");
Connection connection = DriverManager.getConnection("", "", "");
return connection;
}
//返回创建对象类型的Class对象
@Override
public Class<?> getObjectType() {
return Connection.class;
}
//连接对象 不可能是单例的,因为要控制事务,所以这里设置为false
@Override
public boolean isSingleton() {
return false;
}
}
配置文件
注意:我们这里配置的是ConnectionFactoryBean的对象,但是我们获取时ConnectionFactoryBean创建的复杂对象
<bean id="conn" class="com.shwsh.twosection.ConnectionFactoryBean"></bean>
测试
@Test
public void 注入(){
//通过id获取的是创建的复杂对象
Connection conn = (Connection) applicationContext.getBean("conn");
System.out.println("conn = " + conn);
//测试成功
//conn = com.mysql.jdbc.JDBC4Connection@4b44655e
}
2.8.2.3FactoryBean细节分析
如果一个bean标签的class的类型是一个FactoryBean接口的实现类,那么通过id获取的这个对象是实现类创建的复杂对象
如果只想获取这个实现类的对象,使用getBean("&id") id前面加一个&符号
上面我们创建连接对象时将DB的一些信息都写在了实现类里,我们可以通过依赖注入的方式,将这些信息都声明为成员变量,然后在配置文件中为其赋值。
改造
@Data//get set方法
public class ConnectionFactoryBean implements FactoryBean<Connection> {
//声明为成员变量,在配置文件中为其赋值
private String driver;
private String url;
private String username;
private String password;
@Override
public Connection getObject() throws Exception {
Class.forName(driver);
Connection connection = DriverManager.getConnection(url, username, password);
return connection;
}
@Override
public Class<?> getObjectType() {
return Connection.class;
}
//连接对象 不可能是单例的,因为要控制事务,所以这里设置为false
@Override
public boolean isSingleton() {
return false;
}
}
配置文件
<bean id="conn" class="com.shwsh.twosection.ConnectionFactoryBean">
<property name="driver" value=""></property>
<property name="url" value=""></property>
<property name="username" value="root"></property>
<property name="password" value="xxx"></property>
</bean>
对于依赖注入有了更深一步的理解
2.8.2.4FactoryBean的工作原理
读取bean标签,通过bean标签反射创建对象,然后判断这个类是否实现了FactoryBean接口,如果实现了FactoryBean接口,将调用对象的getObject()
方法,将创建的复杂对象给你返回.
2.8.3实例工厂创建复杂对象
好处
避免Spring框架的侵入
整合遗留系统
工厂
//不用实现FactoryBean接口
public class ConnFactoryBean {
//这个方法用于创建复杂对象 这里以创建连接对象为例
public Connection getConnection() throws Exception {
Class.forName("");
Connection connection = DriverManager.getConnection("", "", "");
return connection;
}
}
配置文件
<!--配置ConnFactoryBean-->
<bean id="connFactory" class="com.shwsh.twosection.ConnFactoryBean"></bean>
<bean
<!--复杂对象的id-->
id="connection"
<!--指定这个复杂对象由哪个对象创建 这里是ConnFactoryBean对象创建的,
所以引用上面配置的ConnFactoryBean对象bean标签的id属性-->
factory-bean="connFactory"
<!--指定这个调用ConnFactoryBean类的哪个方法创建这个对象-->
factory-method="getConnection">
测试
@Test
public void test(){
//直接写复杂对象的id 可以直接获取复杂对象
Connection conn = (Connection) applicationContext.getBean("connection");
System.out.println("conn = " + conn);
//conn = com.mysql.jdbc.JDBC4Connection@43ee72e6
}
2.8.4静态工厂创建复杂对象
跟实例工厂的代码的区别就是将获取对象的方法设置为静态方法
工厂代码
public class StaticConnFactory {
//改为静态方法
public static Connection getConn() throws Exception{
Class.forName("");
Connection connection = DriverManager.getConnection("", "", "");
return connection;
}
}
配置文件
也有改变,不需要工厂对象,直接调用方法即可获取对象(静态方法)
<bean id="staticConnFactory"
class="com.shwsh.twosection.StaticConnFactory"
<!--指定工厂方法-->
factory-method="getConn">
</bean>
测试
@Test
public void test(){
//直接获取工厂的id即可获取创建的复杂对象
Connection conn = (Connection)applicationContext.getBean("staticConnFactory");
System.out.println("conn = " + conn);
//conn = com.mysql.jdbc.JDBC4Connection@184cf7cf
}
2.8.5Spring工厂创建对象及注入的总结
2.9控制Spring工厂创建对象的次数
2.9.1概述
Spring工厂默认创建的对象都是单例
2.92控制简单对象的创建次数
只需在bean标签中指定一个scope属性即可
- singleton代表单例
(默认)
- prototype代表多例
<!--单例 / 多例-->
<bean id="xx" class="xxx" scope="singleton/prototype"></bean>
2.9.3控制复杂对象的创建次数
在FactoryBean接口中的isSingleton()方法中设置 是一个默认方法
true代表是单例(默认) false代表非单例
//设置此对象是否是单例的 默认是true
default boolean isSingleton() {
return true;
}
如果使用的不是实现FactoryBean接口创建的复杂对象如上面的实例工厂和静态工厂,还是使用bean标签的scope属性设置
2.10Bean的生命周期
2.10.1概述
由Spring负责对象的创建
存活
销毁
了解声明周期有利于我们使用好Spring为我们创建的对象
2.10.2Bean的创建
默认当一个bean的scope设置为单例时,那么在Spring的ApplicationContext工厂创建时这个对象就会被创建
,可以设置bean标签的lazy-init
属性设置懒加载
<!--true代表懒加载,false代表非懒加载,即工厂创建时这个对象就被创建-->
<bean id="account" class="xxx" scope="singleton" lazy-init="false"></bean>
- 默认当一个bean的scope设置为prototype(多例)时,
默认
是在获取这个Bean的时候才会被工厂创建 这种情况下设置lazy-init
属性不起效果
2.10.3Bean的初始化
2.10.3.1概述
Spring工厂在创建完对象后,调用对象的初始化方法,完成对应的初始化操作
注意:这个初始化 是一个对象被创建(调用完构造器)出来后的初始化 即对Bean进行加工
这些初始化方法是由Spring的工厂进行调用的,是由我们根据Spring提供的接口或者类自定义书写的
2.10.3.2实现InitializingBean接口加工Bean
不管是单例还是多例,懒加载还是非懒加载,只要在Spring工厂创建完这个Bean后,就会调用
afterPropertiesSet()
方法。
//让一个类实现InitializingBean接口并重写afterPropertiesSet方法
public class Dog implements InitializingBean {
private String dname;
//重写这个方法,完成一些操作
@Override
public void afterPropertiesSet() throws Exception {
System.out.println("afterPropertiesSet被调用");
}
}
<!--正常配置即可-->
<bean id="dog" class="com.shwsh.initBean.Dog"></bean>
2.10.3.3自定义方法+配置文件
- 上面的实现接口的方法基本不用,因为他必须要实现接口,对于代码来说造成了耦合
- Spring也为我们提供了另外一种形式,就是在一个类里定义一个普通方法
(必须是public void修饰) 方法名可以随意起,
然后将这个方法名配置到Bean标签的init-method属性
中,就是告诉Spring我这个方法就是用来加工Bean的,你创建完对象后要调用我的这个方法
代码示例
public class Dog {
private String dname;
//自定义方法
public void myinit() throws Exception {
System.out.println("afterPropertiesSet被调用");
}
}
配置文件
<!--在配置文件中配置 指定方法名-->
<bean id="dog" class="com.shwsh.initBean.Dog" init-method="myinit"></bean>
2.10.3.4初始化总结分析
- 如果一个类既实现了InitializingBean接口又提供了普通的初始化方法,那么Spring先调用的是接口的方法,在调用自定义的初始化方法
set注入发生在初始化阶段操作之前
实际上看InitializingBean接口中的方法名就可以看出来afterPropertiesSet(即set属性注入后)初始化操作对于单例和多例对象都有效
2.10.4Bean的销毁
2.10.4.1概述
-
概念:
Spring销毁对象前,会调用对象的销毁方法,完成销毁操作
-
Spring在工厂关闭的时候销毁对象
(工厂的close方法)
销毁方法根据Spring提供的接口或者自定义配置书写销毁方法,最终由Spring的工厂来调用。
-
跟初始化的方式基本类似,也是两种方式,一种实现接口,另一种自定义方法然后在配置文件中配置
-
跟初始化的调用顺序一样,如果两种方式都定义了,那么先调用的是接口的销毁方法,然后是自定义的销毁方法
-
销毁操作只适用于scope=singleton的对象,即单例对象,对于多例对象不起作用
2.10.4.2实现DisposableBean接口
//实现DisposableBean接口
public class Dog implements DisposableBean {
//重写destroy方法
@Override
public void destroy() throws Exception {
System.out.println("调用了destroy方法");
}
}
配置文件正常写即可
<bean id="dog" class="com.shwsh.initBean.Dog"></bean>
测试
注意:只有当Spring工厂关闭的时候,Spring才会调用销毁方法,我们这里测试,所以要显示的调用工厂的close方法
,注意close方法是ClassPathXmlApplicationContext有的,顶级父接口ApplicationContext没有此方法
@Test
public void test1(){
ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("/applicationContext.xml");
//调用工厂的关闭方法才会调用销毁方法
applicationContext.close();
}
2.10.4.3自定义方法+配置文件
自定义方法,然后在Bean标签的destroy-method
属性中配置方法名
public class Dog {
public void mydestory() throws Exception {
System.out.println("自定义销毁方法");
}
}
配置文件
<!--配置自定义销毁方法名-->
<bean id="dog" class="com.shwsh.initBean.Dog" destroy-method="mydestory">
</bean>
2.10.4.4生命周期图示
2.11配置文件参数化
将Spring的配置文件中的经常需要改动的信息(例如跟数据库连接相关的信息)抽取出来,放在一个普通的配置文件(properties等)中 然后在Spring的配置文件中引入这个普通的配置文件
使用步骤(以数据库的连接为例)
1、先将这些配置信息抽取到一个properties配置文件中(db.properties)
driver=com.mysql.jdbc.Driver
url=jdbc:mysql://ip:3306/dbname?useSSL=false
#注意不要用username 他会识别你的计算机名字
name=root
password=root
2、在Spring的配置文件中引入db.properties
细节:classpath:
是Spring专有的一个代表类路径的关键字,类路径就是项目的跟路径,因为按照Maven的编译,src/main/java/和src/main/resources下的所有Java文件或者或配置文件经过编译以后都会放在 target/classes/ 路径下,而这个classes代表的就是类路径(即classpath),我们在src/main/resources下面写的配置文件,就可以使用classpath:xx.properties
等识别路径信息 (P55)
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p"
xmlns:util="http://www.springframework.org/schema/util"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
<!--使用context:property-placeholder标签引入外部的配置文件--> <!--location属性 设置引入的db.properties的位置-->
<context:property-placeholder location="classpath:db.properties"/>
<!--使用上面FactoryBean创建复杂对象的例子-->
<bean id="connfactory" class="com.shwsh.twosection.ConnectionFactoryBean">
<!--使用${key}取出db.properties对应key的value值-->
<property name="driver" value="${driver}"></property>
<property name="url" value="${url}"></property>
<property name="username" value="${name}"></property>
<property name="password" value="${password}"></property>
</bean>
</beans>
2.12类型转换器
2.12.1概述
类型转换器的作用:Spring通过类型转换器将配置文件中字符串类型的数据,转换成对应的类型的数据,进而完成对于属性的注入
Spring内置了一个Conveter接口,用于规定各种类型转换器的规范
2.12.2自定义类型转换器
2.12.2.1问题
为什么需要自定义类型转换器?
当Spring内部没有提供特定的类型转换器时,而我们确实需要这种类型转换,那么我们就需要自定义类型转换器
如何自定义类型转换器?
1、实现Spring提供的Conveter
接口
2、在配置文件中配置
2.12.2.2Conveter接口
- 是一个函数式接口,规定了两个泛型,
第一个泛型S就是配置文件中的属性数据类型一般都是String,第二个泛型T就是对应的类中的属性类型
- convert方法的参数source就是配置文件中的为属性注入的值,返回值就是将字符串值转换为指定类型的值。
@FunctionalInterface
public interface Converter<S, T> {
@Nullable
T convert(S source);
}
2.12.2.3示例代码
需求:为Date类型的属性进行转换注入,格式 yyyy-MM-dd
实现接口代码
import org.springframework.core.convert.converter.Converter;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
public class MyDataConveter implements Converter<String, Date> {
@Override
public Date convert(String source) {
//注意 这里的格式最好不写死 还是使用注入的方式,定义成员变量,在配置文件中注入
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
Date date = null;
try {
date = sdf.parse(source);
} catch (ParseException e) {
e.printStackTrace();
}qqqqqq
return date;
}
}
在配置文件中进行配置
-
1.在配置文件中配置此转换器
<bean id="mydataconverter" class="MyDataConveter"></bean>
-
2.注册
使用
ConversionServiceFactoryBean
类(Spring提供)
注册自定义的类型转换器,将ConversionServiceFactoryBean也写到配置文件中
<!--一定注意:这个id不能随便起,必须是conversionService-->
<bean id="conversionService" class="org.springframework.context.support.ConversionServiceFactoryBean">
<!--converters属性是一个set集合,即可以注册多个转换器-->
<property name="converters">
<set>
<!--引用上面的自定义类型转换器mydataconverter-->
<ref bean="mydataconverter"></ref>
</set>
</property>
</bean>
2.12.2.4总结及注意事项
- Spring内部提供了日期格式的转换器,但是他要求的格式是
yyyy/MM/dd
一旦我们使用了自定义的某种类型的转换器,那么Spring提供的这种类型的转换器就不起作用了
,比如我们上面自定义了一个日期类型的转化器,格式是yyyy-MM-dd,那么Spring内置的日期类型的转换器就不起作用了,我们如果也想使用yyyy/MM/dd格式的日期,那么我们必须手动再写一个yyyy/MM/dd转换器进行注册
2.13后置处理Bean(BeanPostProcessor)
2.13.1概述与完整流程图
- 对Spring工厂所创建的
所有
对象进行再加工 要实现BeanPostProcessor接口,重写接口中的两个方法,在Spring创建完Bean后,就会按照下面的图示流程进行加工Bean
Spring对于Bean的完整加工流程图
2.13.2BeanPostProcessor接口
- 实战中很少做初始化操作,也即before和after也就没有什么区别,所以只需实现after方法即可!
最重要的一点:如果你实现了BeanPostProcessor接口,即使你什么也不做,但是必须把bean返回
public interface BeanPostProcessor {
/**
执行时机:初始化之前执行
在Bean被创建完并执行了set注入后(如果有),就执行这个方法
@param bean 要加工的对象
@param beanName 要加工的Bean对象的id值
@return Object 将加工完的对象返回
*/
@Nullable
default Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
return bean;
}
------------------------------------------------------------------------------
/**
执行时机: 初始化之后执行
在Bean被创建完并执行了set注入后(如果有) 并执行完初始化操作后(如果有) 就执行这个方法
@param bean 要加工的对象
@param beanName 要加工的Bean对象的id值
@return Object 将加工完成的对象返回
*/
@Nullable
default Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
return bean;
}
}
2.13.3代码示例
开发步骤
- 1、实现BeanPostProcessor接口重写方法
- 2、在Spring的配置文件中配置
编码
实现BeanPostProcessor接口,注意这里跟初始化和销毁实现接口是完全不同的,这里是将实现BeanPostProcessor接口单独的创建出来,就作为一个BeanPostProcessor实现类,因为:只要在Spring配置文件中配置了这个Bean,那么所有的Bean都会调用这个类中的方法
所以在做一些操作的时候一定要进行类型判断,
public class MyBeanPostProcessor implements BeanPostProcessor {
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
//一定要做类型判断,因为一旦在Spring的配置文件中配置了MyBeanPostProcessor
//那么这个配置文件中所有的bean都会经过此处理
if(bean instanceof xx){
...do Sth
}
return bean;
}
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
//一定要做类型判断,因为一旦在Spring的配置文件中配置了MyBeanPostProcessor
//那么这个配置文件中所有的bean都会经过此处理
if(bean instanceof xx){
...do Sth
}
return bean;
}
}
配置文件
直接将这个实现类配置到配置文件中即可,如果有多个BeanPostProcessor的实现类,那么按照在配置文件中的声明顺序进行执行
<bean id="myBeanPostProcessor" class="xxx"></bean>