上节记录了用Spring工厂+配置文件的方式创建简单对象。这节继续深入一下。看一看Spring工厂如何创建复杂对象以及注入的实现。
一、Spring5.x与日志框架的整合
日志的重要性不言而喻,就靠它分析问题了,所以记录的详细点(开发版)没啥坏处。早在Spring1、2、3时,其整合的是commons-logging.jar,而现在应用最广泛的Spring5整合的是logback、log4j2,我们日常应用比较广泛的是log4j,所以要在Spring5.x中引入log4j,需要引入以下依赖,并对配置文件进行配置:
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<version>1.7.25</version>
</dependency>
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>
# 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
注:引入org.slf4j依赖的原因是要排除默认的logback和log4j2,进而让Spring5支持log4j
二、注入!
加了个!因为在Spring工厂中注入和创建对象都是最重要的吧。
注入的概念:通过Spring工厂及配置文件,为所创建对象的成员变量赋值
和new 对象的问题一样,如果直接调用set方法赋值会存在硬编码的问题,强耦合,所以最好的方式还是去用Spring工厂+配置文件解耦合
硬编码耦合:
//用于测试注入
@org.junit.Test
public void test3(){
ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
Person person = (Person) ctx.getBean("person");
person.setId(1);
person.setName("sasasa");
System.out.println("person = " + person.toString());
}
Spring工厂+配置文件解耦合:
//用于测试:通过spring的配置文件进赋值(注入)
@org.junit.Test
public void test4(){
ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
Person person = (Person) ctx.getBean("person");
System.out.println("person = " + person.toString());
}
<bean id="person" class="com.jin.Person" p:name="xiaojianren" p:id="1239"> (简写)
<bean id="person" class="com.jin.Person">
<property name="id">
<value>34</value>
</property>
<property name="name">
<value>的斯</value>
</property>
</bean>
注意:这种注入的实现方式叫set注入,还要在对类的成员变量设置set get方法才行,如果不设置,配置文件中的标签会直接爆红的。
好处还是解耦合,那么,set注入方式,原理如下图(Spring通过底层调用对象属性对应的set方法,完成成员变量的赋值,这种方式我们也称之为set注入):
二(1)、Set注入详解:
那么针对于不同类型的成员变量,在
JDK内置类型:
(1)String+8种基本类型,都是用 value 标签赋值就行
<property name="id">
<value>34</value>
</property>
<property name="name">
<value>的斯</value>
</property>
(2)数组 String[] list标签嵌套value
<property name="emails">
<list>
<value>123</value>
<value>456</value>
<value>789</value>
</list>
</property>
(3)Set集合 set标签嵌套value
<property name="tels">
<set>
<value>123</value>
<value>456</value>
<value>789</value>
</set>
</property>
(4)List集合 list标签嵌套value
<property name="addresses">
<list>
<value>123</value>
<value>456</value>
<value>789</value>
</list>
</property>
(5)map集合 这个复杂一点 注意: map -- entry -- key
<property name="qqs">
<map>
<entry>
<key><value>jin</value></key>
<value>123</value>
</entry>
<entry>
<key><value>yun</value></key>
<value>456</value>
</entry>
<entry>
<key><value>long</value></key>
<value>789</value>
</entry>
</map>
</property>
(6)properties props中嵌套prop key
<property name="p">
<props>
<prop key="key1">value1</prop>
<prop key="key2">value2</prop>
<prop key="key3">value3</prop>
</props>
</property>
这几个集合通过Set方法,再通过Spring工厂+配置文件实现注入,测试代码如下:
//用于测试:JDK类型成员变量的赋值
@org.junit.Test
public void test5(){
ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
Person person = (Person) ctx.getBean("person");
String[] emails = person.getEmails();
for (String email : emails) {
System.out.println("email = " + email);
}
System.out.println("-----------------------------");
Set<String> tels = person.getTels();
for (String tel : tels) {
System.out.println("tel = " + tel);
}
System.out.println("-----------------------------");
List<String> addresses = person.getAddresses();
for (String address : addresses) {
System.out.println("address = " + address);
}
System.out.println("-----------------------------");
Map<String, String> qqs = person.getQqs();
Set<String> keySet = qqs.keySet();
for (String qq : keySet) {
System.out.println("key = " + qq + " value = " + qqs.get(qq));
}
System.out.println("-----------------------------");
Properties p = person.getP();
System.out.println("key is "+ " key1"+ " value "+p.getProperty("key1"));
System.out.println("key is "+ " key2"+ " value "+p.getProperty("key2"));
System.out.println("key is "+ " key3"+ " value "+p.getProperty("key3"));
}
用户自定义类型:
比如想注入userDao对象,那就也需要先设置get set方法:
第一种注入方式,配置如下:
<!-- <bean id = "userService" class="com.jin.UserServiceImpl">-->
<!-- <property name="userDao">-->
<!-- <bean class="com.jin.UserDaoImpl"></bean>-->
<!-- </property>-->
<!-- </bean>-->
可见 1.配置文件代码冗余 2.被注入的对象(userDao),多次创建,浪费(JVM)内存资源,改为如下配置:
<bean id="userDao" class="com.jin.UserDaoImpl"></bean>
<bean id="userService" class="com.jin.UserServiceImpl">
<property name="userDao">
<ref bean="userDao"></ref>
</property>
</bean>
这么看着感觉还是长,下面看看简写方式,(直接贴笔记了):
基于属性简化:
JDK类型注入
<property name="name">
<value>suns</value>
</property>
<property name="name" value="suns"/>
注意:value属性 只能简化 8种基本类型+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>suns</value>
</property>
</bean>
<bean id="person" class="xxx.Person" p:name="suns"/>
注意:value属性 只能简化 8种基本类型+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"/>
可见简写还是挺清晰的,程序运行也通过:
二(2)、构造注入:
Set注入和构造注入的区别,Set注入:Spring调用Set方法 通过配置文件 为成员变量赋值 构造注入:Spring调用构造方法 通过配置文件 为成员变量赋值
写一个有参构造方法测试:
private String name; private int age; public Customer(String name, int age) { this.name = name; this.age = age; }
配置文件配置:
<bean id="customer" class="com.jin.constructer.Customer">
<constructor-arg>
<value>jin</value>
</constructor-arg>
<constructor-arg>
<value>6666</value>
</constructor-arg>
</bean>
注意,使用构造注入方式的话可能会存在方法重载的问题,参数不同时用constructor-arg标签的数量区分,参数相同的话,需要对constructor-arg标签上加上 type="" 属性进行区分。
注入的总结:应该是使用Set注入的场景比较多,因为构造注入(1)涉及方法重载的问题,比较麻烦 (2)Spring框架底层也是较多的使用了Set注入,算是默认其比较通用吧。
三、反转控制(IOC)和依赖注入(DI) !
反转控制就是纯概念,而依赖注入我理解为他是反转控制的具体实现方式。
控制:对于成员变量赋值的控制权
反转控制:把对于成员变量赋值的控制权,从代码中反转(转移)到Spring工厂和配置文件中完成
好处:解耦合
底层实现:工厂设计模式
概念和图示都非常清晰,多写就明白了😄
依赖注入:当一个类需要另一个类时,就意味着依赖,一旦出现依赖,就可以把另一个类作为本
类的成员变量,最终通过Spring配置文件进行注入(赋值)。(再广泛一点,不仅限于类间的调用,值也一样)
好处:解耦合
四、Spring工厂创建复杂对象
复杂对象:指的就是不能直接通过new构造方法创建的对象,比如Connection等。
创建复杂对象有三种方式,分别是继承FactoryBean接口实现、实例工厂、静态工厂。
FactoryBean接口实现:
继承FactoryBean接口需要重写三个方法,作用分别是写具体创建对象、返回对象类、判断每次重新生成该对象是否要重新生成。
代码实例:
package com.jin.factorybean;
import org.springframework.beans.factory.FactoryBean;
import java.sql.Connection;
import java.sql.DriverManager;
/**
* @author jinyunlong
* @date 2021/11/23 13:48
* @profession ICBC锅炉房保安
*/
public class ConnectionFactoryBean implements FactoryBean<Connection> {
private String driverClassName;
private String url;
private String username;
private String password;
public String getDriverClassName() {
return driverClassName;
}
public void setDriverClassName(String driverClassName) {
this.driverClassName = driverClassName;
}
public String getUrl() {
return url;
}
public void setUrl(String url) {
this.url = url;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
//用于书写创建复杂对象的代码
@Override
public Connection getObject() throws Exception {
Class.forName(driverClassName);
Connection conn = DriverManager.getConnection(url, username, password);
return conn;
}
@Override
public Class<?> getObjectType() {
return Connection.class;
}
@Override
public boolean isSingleton() {
return false;
}
}
<bean id="conn" class="com.jin.factorybean.ConnectionFactoryBean">
<property name="driverClassName" value="com.mysql.jdbc.Driver" />
<property name="url" value="jdbc:mysql://localhost:3306/xxxx" />
<property name="username" value="root" />
<property name="password" value="xxxx~" />
</bean>
//用于测试:用于测试FactoryBean接口
@org.junit.Test
public void test8(){
ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
Connection conn = (Connection) ctx.getBean("conn");
Connection conn2 = (Connection) ctx.getBean("conn");
System.out.println("conn = " + conn);
System.out.println("conn2 = " + conn2);
}
如上,复杂对象也创建了,值也注入了。有个问题就是这里创建的对象不是ConnectionFactoryBean的类对象,而是泛型中的Connection对象,也就是getObject()中的conn。注意,1、如果就想获得FactoryBean类型的对象 ctx.getBean("&conn") 获得就是ConnectionFactoryBean对象。2、isSingleton方法 返回 true 只会创建一个复杂对象返回 false 每一次都会创建新的对象 问题:根据这个对象的特点 ,决定是返回true(SqlSessionFactory) 还是 false (Connection)
实现原理:
简易流程:
- 通过conn(bean id)获得 ConnectionFactoryBean类的对象 ,进而通过instanceof 判断
出是FactoryBean接口的实现类 - Spring按照规定 getObject() ---> Connection
- 返回Connection
实例工厂和静态工厂在这里就不记了,因为FactoryBean的应用场景是非常多的,不过另外两种工厂在整合遗留系统方面是有应用的,需要注意。
五、控制Spring工厂创建对象的次数
控制简单对象的创建次数:
<bean id="account" scope="singleton|prototype" class="xxxx.Account"/>
sigleton:只会创建一次简单对象 默认值
prototype:每一次都会创建新的对象
控制复杂对象的创建次数:
FactoryBean{
isSingleton(){
return true 只会创建一次
return false 每一次都会创建新的
}
}
如没有isSingleton方法 还是通过scope属性 进行对象创建次数的控制
六、Spring工厂的总结 !!
IOC的本质是工厂+反射,底层为工厂设计模式,目的是解耦、解耦、解耦!重要的事说三遍吧,为了消除硬编码,以Spring工厂+配置文件的方式实现对象的创建、注入
反射+接口,基本就啥都能做。。
Spring创建对象、注入的整合图,都理解清晰了Spring工厂这块就算学个大概了吧。
实现IOC(解耦)的具体做法->DI->(需要)创建对象和注入(简单、复杂对象;Set、构造注入)->(使用)Spring工厂+配置文件方式创建(工厂+反射)->底层设计模式:工厂模式