上节记录了用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方法才行,如果不设置,配置文件中的标签会直接爆红的。

5.PNG

6.PNG

好处还是解耦合,那么,set注入方式,原理如下图(Spring通过底层调用对象属性对应的set方法,完成成员变量的赋值,这种方式我们也称之为set注入):

7.PNG

二(1)、Set注入详解:

那么针对于不同类型的成员变量,在标签里,需要嵌套其他标签或者是使用不同的标签:

8.PNG

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方法:

9.PNG

第一种注入方式,配置如下:

<!--    <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"/>

可见简写还是挺清晰的,程序运行也通过:

10.PNG

二(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注入,算是默认其比较通用吧。

11.PNG

三、反转控制(IOC)和依赖注入(DI) !

反转控制就是纯概念,而依赖注入我理解为他是反转控制的具体实现方式。

控制:对于成员变量赋值的控制权
反转控制:把对于成员变量赋值的控制权,从代码中反转(转移)到Spring工厂和配置文件中完成
好处:解耦合
底层实现:工厂设计模式

12.PNG

概念和图示都非常清晰,多写就明白了😄

依赖注入:当一个类需要另一个类时,就意味着依赖,一旦出现依赖,就可以把另一个类作为本
类的成员变量,最终通过Spring配置文件进行注入(赋值)。(再广泛一点,不仅限于类间的调用,值也一样)
好处:解耦合

四、Spring工厂创建复杂对象

复杂对象:指的就是不能直接通过new构造方法创建的对象,比如Connection等。

13.PNG

创建复杂对象有三种方式,分别是继承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)

实现原理:

14.PNG

简易流程:

  1. 通过conn(bean id)获得 ConnectionFactoryBean类的对象 ,进而通过instanceof 判断
    出是FactoryBean接口的实现类
  2. Spring按照规定 getObject() ---> Connection
  3. 返回Connection

实例工厂和静态工厂在这里就不记了,因为FactoryBean的应用场景是非常多的,不过另外两种工厂在整合遗留系统方面是有应用的,需要注意。

五、控制Spring工厂创建对象的次数

控制简单对象的创建次数:

<bean id="account" scope="singleton|prototype" class="xxxx.Account"/>
sigleton:只会创建一次简单对象 默认值
prototype:每一次都会创建新的对象

控制复杂对象的创建次数:

FactoryBean{
   isSingleton(){
      return true  只会创建一次
      return false 每一次都会创建新的
   }
 
}
如没有isSingleton方法 还是通过scope属性 进行对象创建次数的控制

15.PNG

六、Spring工厂的总结 !!

IOC的本质是工厂+反射,底层为工厂设计模式,目的是解耦、解耦、解耦!重要的事说三遍吧,为了消除硬编码,以Spring工厂+配置文件的方式实现对象的创建、注入

反射+接口,基本就啥都能做。。

Spring创建对象、注入的整合图,都理解清晰了Spring工厂这块就算学个大概了吧。

16.PNG

实现IOC(解耦)的具体做法->DI->(需要)创建对象和注入(简单、复杂对象;Set、构造注入)->(使用)Spring工厂+配置文件方式创建(工厂+反射)->底层设计模式:工厂模式


标题:Spring(2)
作者:jyl
地址:http://jinyunlong.xyz/articles/2021/11/23/1637679326058.html