当前位置 : 主页 > 编程语言 > 其它开发 >

Spring学习笔记(1) IOC

来源:互联网 收集:自由互联 发布时间:2022-05-30
Spring 工厂什么是Spring Spring是一个轻量级的JavaEE解决方案,它整合了众多优秀的设计模式。 什么是轻量级? 对于运行环境没有额外要求的; 开源: tomcat、resion、jetty 收费:weblogic、websp
Spring 工厂 什么是Spring

Spring是一个轻量级的JavaEE解决方案,它整合了众多优秀的设计模式。

什么是轻量级?
  • 对于运行环境没有额外要求的;

    开源: tomcat、resion、jetty

    收费:weblogic、websphere

  • 代码移植性高:不需要实现额外接口。

工厂设计模式
  • 好处: 解决耦合
  • 耦合: 指定是代码间的强关联关系,⼀方的改变会影响到另⼀方;
  • 问题: 把接口的实现类硬编码在了程序中,不利于代码维护;
UserService userService = new UserServiceImpl();
简单工厂的设计
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.baizhixx.UserServiceImpl
            env.load(inputStream);

            inputStream.close();
        } catch (IOException e) {
            e.printStackTrace();
        }

    }
    
    /*
	   对象的创建方式:
	       1. 直接调用构造方法 创建对象  UserService userService = new UserServiceImpl();
	       2. 通过反射的形式 创建对象 解耦合
	       Class clazz = Class.forName("com.2hu0.basic.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;
    }

}

配置文件

# Properties 集合 存储 Properties文件的内容
# 特殊Map key=String value=String
# Properties [userService = com.2hu0.xxx.UserServiceImpl]
# Properties.getProperty("userService")

userService = com.2hu0.basic.UserServiceImpl
userDAO = com.2hu0.basic.UserDAOImpl

通用工厂的设计

简单工厂存在代码冗余,需要进一步简化

1.png

通用工厂代码

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.baizhixx.UserServiceImpl
            env.load(inputStream);

            inputStream.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    
     /*
      key 小配置文件中的key [userDAO,userService]
      */
     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;
     }

}

同时我们需要更新配置文件

# Properties 集合 存储 Properties文件的内容
# 特殊Map key=String value=String
# Properties [userService = com.baizhiedu.xxx.UserServiceImpl]
# Properties.getProperty("userService")

userService = com.baizhiedu.basic.UserServiceImpl
userDAO = com.baizhiedu.basic.UserDAOImpl
通用工厂的使用方式
1. 定义类型(类)
2. 通过配置文件的配置告知工厂
   `applicationContext.properties` 中 `key = value`;
3. 通过工厂获得类的对象
   `Object ret = BeanFactory.getBean("key");`
总结:

Spring本质:工厂ApplicationContext(applicationContext.xml)

第一个Spring程序 环境搭建

依赖查询网站

配置Spring的jar包:
<!-- https://mvnrepository.com/artifact/org.springframework/spring-context -->
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-context</artifactId>
    <version>5.1.4.RELEASE</version>
</dependency>
Spring的配置文件:
  • 配置文件的放置位置:任意位置,没有硬性要求
  • 命名建议为:applicationContext.xml
Spring的核心API ApplicationContext
  • 作用:Spring 提供的 ApplicationContext 这个工厂,用于对象的创建;
    好处:解耦合

  • ApplicationContext是接口类型

    接口:屏蔽实现的差异

    非Web环境: ClassPathXmlApplicationContext

    Web环境: XmlWebApplicationContext

  • 重量级资源:

    ApplicationContext 工厂的对象占用大量的内存

    一个应用只会创建一个工厂对象。

    ApplicationContext 工厂:一定是线程安全的

样例

1.创建类型:Person.java

public class Person{}

2.配置文件的配置

<?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="person" class="com.yusael.basic.Person"/>

</beans>

3.通过工厂类获得对象

/**
 * 用于测试Spring的第一个程序
 */
@Test
public void test() {
    // 1、获取spring的工厂
    ApplicationContext ctx = new ClassPathXmlApplicationContext("/applicationContext.xml");
    // 2、通过工厂类获得对象
    Person person = (Person)ctx.getBean("person");
    System.out.println(person);
}
细节:

Spring工厂创建的对象,叫做bean或者组件(componet)

Spring工厂常见的方法

getbean:传入id值 和 类名 获取对象,不需要强制类型转换。

// 通过这种方式获得对象,就不需要强制类型转换
Person person = ctx.getBean("person", Person.class);
System.out.println("person = " + person);

getbean只指定类名,Spring的配置文件中只能有一个bean是这个类型

// 使用这种方式的话, 当前Spring的配置文件中 只能有一个bean class是Person类型
Person person = ctx.getBean(Person.class);
System.out.println("person = " + person);

getBeanDefinitionNames:获取Spring配置文件中所有的bean标签的id值

// 获取的是Spring工厂配置文件中所有bean标签的id值  person person1
String[] beanDefinitionNames = ctx.getBeanDefinitionNames();
for (String beanDefinitionName : beanDefinitionNames) {
	System.out.println("beanDefinitionName = " + beanDefinitionName);
}

getBeanNamesForType:根据类型获得Spring配置文件中对应的id值

// 根据类型获得Spring配置文件中对应的id值
String[] beanNamesForType = ctx.getBeanNamesForType(Person.class);
for (String id : beanNamesForType) {
	System.out.println("id = " + id);
}

containsBeanDefinition:用于判断是否存在指定id值的bean,不能判断name值

// 用于判断是否存在指定id值的bean,不能判断name值
if (ctx.containsBeanDefinition("person")) {
	System.out.println(true);
} else {
	System.out.println(false);
}
配置文件中的细节

如果bean只配置class属性:

<bean class="com.yusael.basic.Person"></bean>
  • 会自动生成一个id

  • 应用场景:

    如果这个bean只需要使用一次,那么就可以省略id值

    如果这个bean会使用多次,或者被其他bean引用则需要设置id值

name属性:

  • 作用:用于在 Spring 的配置文件中,为 bean 对象定义别名(小名)
  • name 与 id 的相同点:
    • ctx.getBean("id")ctx.getBean("name") 都可以创建对象;
    • <bean id="person" class="Person"/><bean name="person" class="Person"/> 等效;
  • name 和 id 的区别
    • 别名可以定义多个,但是 id 属性只能有⼀个值;
    • XML 的 id 属性的值,命名要求:必须以字母开头,可以包含 字母、数字、下划线、连字符;不能以特殊字符开头 /person
    • XML 的 name 属性的值,命名没有要求,/person 可以。
      但其实 XML 发展到了今天:ID属性的限制已经不存在,/person也可以。
思考:

问题:未来在开发过程中,是不是所有的对象,都会交给 Spring 工厂来创建呢?

回答:理论上是的,但是有特例 :实体对象(entity) 是不会交给Spring创建,它由持久层框架进行创建。

Spring整合日志框架

Spring 与日志框架进行整合,日志框架就可以在控制台中,输出Spring框架运行过程中的⼀
些重要的信息。
好处:便于了解Spring框架的运行过程,利于程序的调试。

默认日志框架
Spring 1.x、2.x、3.x 早期都是基于commonslogging.jar
Spring 5.x 默认整合的日志框架 logback、log4j2

导入log4j依赖
<dependency>
  <groupId>org.slf4j</groupId>
  <artifactId>slf4j-log4j12</artifactId>
  <version>1.7.21</version>
</dependency>
<dependency>
  <groupId>log4j</groupId>
  <artifactId>log4j</artifactId>
  <version>1.2.17</version>
</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

注入(Injection) 什么是注入

注入:通过 Spring 工厂及配置文件,为所创建对象的成员变量赋值。

为什么要注入?
  • 通过编码的方式,为成员变量进行赋值,存在耦合。
  • 注入的好处:解耦合
public void test4() {
    ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("/applicationContext.xml");
    Person person = (Person) ctx.getBean("person");
    // 通过代码为变量赋值, 存在耦合, 如果我们以后想修改变量的值, 需要修改代码, 重新编译
    person.setId(1);
    person.setName("zhenyu");
    System.out.println(person);
}
具体步骤
  • 类的成员变量提供 set get 方法
  • 配置 spring 的配置文件
<bean id="person" name="p" class="com.yusael.basic.Person">
    <property name="id">
        <value>10</value>
    </property>
    <property name="name">
        <value>yusael</value>
    </property>
</bean>

Spring 底层通过调用对象属性对应的 set 方法,完成成员变量的赋值,这种方式也称为 Set注入

2.png

Set注入详解

Set注入的变量类型:

  • JDK内置类型
    8种基本类型 + String、数组类型、set集合、list集合、Map集合、Properties集合。
  • 用户自定义类型

针对于不同类型的成员变量,在<property标签中,需要嵌套其他标签:

JDK内置类型 String+8种基本类型
<property name="id">
	<value>10</value>
</property>
<property name="name">
	<value>yusael</value>
</property>
数组
<property name="emails">
    <list>
        <value>abc@qq.com</value>
        <value>123@qq.com</value>
        <value>hello@qq.com</value>
    </list>
</property>
Set集合
<property name="tels">
	<set>
		<value>138xxxxxxxxxx</value>
		<value>139xxxxxxxxxx</value>
		<value>138xxxxxxxxxx</value><!--set会自动去重-->
	</set>
</property>
List集合
<property name="addresses">
	<list>
		<value>China</value>
		<value>Earth</value>
		<value>hell</value>
	</list>
</property>

Map集合

<property name="qqs">
    <map>
        <entry>
            <key><value>hello</value></key>
            <value>12312312312</value>
        </entry>
        <entry>
            <key><value>world</value></key>
            <value>21314214214</value>
        </entry>
    </map>
</property>

Properties

<property name="p">
    <props>
        <prop key="key1">value1</prop>
        <prop key="key2">value2</prop>
        <prop key="key3">value3</prop>
    </props>
</property>
复杂JDK类型(Date、)

需要程序员自定义类型转换器

用户自定义类型 第一种方式
  • 为成员变量提供 set get 方法
  • 配置文件中进行注入(赋值)
<bean id="userService" class="com.yusael.service.UserServiceImpl">
    <property name="userDAO">
        <bean class="com.yusael.dao.UserDAOImpl"/>
    </property>
</bean>
第二种方式

第⼀种赋值方式存在的问题:

  1. 配置文件代码冗余;
  2. 被注入的对象 (UserDAO)多次创建,浪费(JVM)内存资源。

[开发步骤]:

  • 为成员变量提供 set get 方法;
  • 配置文件中进行配置;
<bean id="userDAO" class="com.yusael.dao.UserDAOImpl"></bean>

<bean id="userService" class="com.yusael.service.UserServiceImpl">
    <property name="userDAO">
        <ref bean="userDAO"/>
    </property>
</bean>
Set注入简化 基于属性的简化

JDK类型注入

<property name="id">
	<value>10</value>
</property>

JDK类型注入简化:value 属性只能简化 8种基本类型 + String 注入标签;

<property name="id" value="10"/>

用户自定义类型注入:

<bean id="userDAO" class="com.yusael.dao.UserDAOImpl"></bean>

<bean id="userService" class="com.yusael.service.UserServiceImpl">
    <property name="userDAO">
        <ref bean="userDAO"/>
    </property>
</bean>

用户自定义类型注入简化:

<bean id="userDAO" class="com.yusael.dao.UserDAOImpl"></bean>

<bean id="userService" class="com.yusael.service.UserServiceImpl">
    <property name="userDAO" ref="userDAO"/>
</bean>
基于p命名空间的简化

JDK类型注入

<bean id="person" name="p" class="com.yusael.basic.Person">
	<property name="id">
		<value>10</value>
	</property>
	<property name="name">
		<value>yusael</value>
	</property>
</bean>

JDK类型注入-基于p命名空间的简化

<bean id="person" name="p" class="com.yusael.basic.Person" 
p:name="yusael" p:id="10"/>

用户自定义类型注入

<bean id="userDAO" class="com.yusael.dao.UserDAOImpl"></bean>

<bean id="userService" class="com.yusael.service.UserServiceImpl">
    <property name="userDAO">
        <ref bean="userDAO"/>
    </property>
</bean>

用户自定义类型注入 - 基于p命名空间的简化

<bean id="userDAO" class="com.yusael.dao.UserDAOImpl"></bean>

<bean id="userService" class="com.yusael.service.UserServiceImpl" 
p:userDAO-ref="userDAO"/>
构造注入
  • 注入:通过 Spring 的配置文件,为成员变量赋值;
  • Set注入:Spring 调用 Set 方法 通过 配置文件 为成员变量赋值;
  • 构造注入:Spring 调用 构造方法 通过 配置文件 为成员变量赋值
具体实现
  • 提供有参构造方法

    public class Customer {
        private String name;
        private int age;
    
        public Customer(String name, int age) {
            this.name = name;
            this.age = age;
        }
    
        @Override
        public String toString() {
            return "Customer{" +
                    "name='" + name + '\'' +
                    ", age=" + age +
                    '}';
        }
    }
    

spring配置文件

<bean id="customer" class="com.yusael.constructor.Customer">
    <constructor-arg>
        <value>zhenyu</value>
    </constructor-arg>
    <constructor-arg>
        <value>21</value>
    </constructor-arg>
</bean>
构造方法重载 参数个数不同

参数个数不同时,通过控制 <constructor-arg> 标签的数量进行区分;

如果只有一个参数的话,只需要一对 <constructor-arg> 标签:

<bean id="customer" class="com.yusael.constructor.Customer">
    <constructor-arg>
        <value>zhenyu</value>
    </constructor-arg>
</bean>
参数相同
<bean id="customer" class="com.yusael.constructor.Customer">
	<constructor-arg type="int">
	    <value>20</value>
	</constructor-arg>
</bean>
总结

未来的实战中,应用 set注入 还是 构造注入

答:set 注入更多。

  • 构造注入麻烦(重载)
  • Spring 框架底层大量应用了 set注入。
反转控制与依赖注入 反转控制(IOC Inverse of Control)
  • 控制:对于成员变量赋值的控制权;
  • 反转控制:把对于成员变量赋值的控制权,从代码中转移(反转)到 Spring 工厂和配置文件中完成。
  • 好处:解耦合;
  • 底层实现:工厂设计模式;

3.png

依赖注入
  • 注入:通过 Spring 的工厂及配置文件,为对象(bean,组件)的成员变量赋值;
  • 依赖注入:当⼀个类需要另⼀个类时,就意味着依赖,⼀旦出现依赖,就可以把另⼀个类作为本类的成员变量,最终通过 Spring 配置文件进行注入(赋值)。
  • 好处:解耦合;
创建复杂对象

复杂对象就是不能通过new构造方法创建的对象

例如Connection SqlSessionFactory

FactoryBean接口
  • 实现 FactoryBean 接口:实现 getObject,getObjectType,isSingleton 方法;
  • getObject():用于书写创建复杂对象时的代码。
  • getObjectType():返回创建的复杂对象的类型。
  • isSingleton:用于决定是否单例。
public class ConnectionFactoryBean implements FactoryBean<Connection> {
    // 用于书写创建复杂对象时的代码
    @Override
    public Connection getObject() throws Exception {
        Class.forName("com.mysql.jdbc.Driver");
        Connection conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/spring", "root", "1234");
        return conn;
    }

    // 返回创建的复杂对象的类型
    @Override
    public Class<Connection> getObjectType() {
        return Connection.class;
    }

    // 是否单例
    @Override
    public boolean isSingleton() {
        return false; // 每一次都创建新的复杂对象
        // return true; // 只创建一次这种类型的复杂对象
    }
}
Spring配置文件的配置

如果class中指定的类型是FactoryBean接口的实现类,那么通过id值获取的就是这个类所创建的复杂对象

比如下面 class 指定的是 ConnectionFactoryBean,获得的是 Connection 对象。

<!--class 指定了 ConnectionFactoryBean, 获得的是该类创建的复杂对象 Connection -->
<bean id="conn" class="com.2hu0.factorybean.ConnectionFactoryBean"/>
FactoryBean细节

如果你就想要获得FactoryBean类型的对象,加个 &ctx.getBean("&conn")

ConnectionFactoryBean cfb = (ConnectionFactoryBean) ctx.getBean("&conn");

isSingleton 方法返回 true 只会创建⼀个复杂对象,返回 false 每⼀次都会创建新的对象;
需要根据这个对象的特点 ,决定是返回 true(SqlSessionFactory) 还是 false(Connection);

依赖注入(DI):把 ConnectionFactoryBean 中依赖的 4 个字符串信息 ,通过配置文件进行注入

@Getter@Setter // 提供 get set 方法
public class ConnectionFactoryBean implements FactoryBean<Connection> {
	// 将依赖的字符串信息变为成员变量, 利用配置文件进行注入。
    private String driverClassName;
    private String url;
    private String username;
    private String password;
    @Override
    public Connection getObject() throws Exception {
        Class.forName(driverClassName);
        Connection conn = DriverManager.getConnection(url, username, password);
        return conn;
    }
    @Override
    public Class<Connection> getObjectType() {
        return Connection.class;
    }
    @Override
    public boolean isSingleton() {
  		return false;
    }
}
!--体会依赖注入, 好处: 解耦合, 今后要修改连接数据库的信息只需要修改配置文件, 无需改动代码-->
<bean id="conn" class="com.yusael.factorybean.ConnectionFactoryBean">
    <property name="driverClassName" value="com.mysql.jdbc.Driver"/>
    <property name="url" value="jdbc:mysql://localhost:3306/spring?useSSL=false"/>
    <property name="username" value="root"/>
    <property name="password" value="1234"/>
</bean>
Factory实现原理简易版

原理:接口回调。

问题:

  1. 为什么 Spring 规定 FactoryBean 接口实现 getObject()?
    2 .为什么 ctx.getBean("conn") 获得的是复杂对象 Connection ⽽非 ConnectionFactoryBean?
    Spring 内部运行流程:

  2. 配置文件中通过 id conn 获得 ConnectionFactoryBean 类的对象 ,进而通过 instanceof 判断出是 FactoryBean 接口的实现类;

  3. Spring 按照规定 getObject() —> Connection;
    返回 Connection;

4.png

Factory总结

Spring中用于创建复杂对象的一种方式,也是Spring原生提供的,后续Spring整合其他框架的时候会大量的应用FactoryBean

实例工厂
  1. 避免Spring框架的侵入
  2. 整合遗留系统
  • ConnectionFactory
public class ConnectionFactory {
    public Connection getConnection() {
        Connection conn = null;
        try {
            Class.forName("com.mysql.jdbc.Driver");
            conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/spring?useSSL=false", "root", "1234");
        } catch (ClassNotFoundException | SQLException e) {
            e.printStackTrace();
        }
        return conn;
    }
}
  • 配置文件
<!--实例工厂-->
<!-- 先创建出工厂实例 -->
<bean id="connFactory" class="com.yusael.factorybean.ConnectionFactory"/>
 <!-- 通过工厂实例里的方法创建复杂对象 -->
<bean id="conn" factory-bean="connFactory" factory-method="getConnection"/>
静态工厂
  • StaticConnectionFactory类
public class StaticFactoryBean {
	// 静态方法
    public static Connection getConnection() {
        Connection conn = null;
        try {
            Class.forName("com.mysql.jdbc.Driver");
            conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/spring?useSSL=false", "root", "1234");
        } catch (ClassNotFoundException | SQLException e) {
            e.printStackTrace();
        }
        return conn;
    }
}
  • 配置文件
<!--静态工厂-->
<bean id="conn" class="com.yusael.factorybean.StaticFactoryBean" factory-method="getConnection"/>

控制 Spring 工厂创建对象的次数 控制简单对象的创建次数 - scope

配置文件中进行配置:
singleton:每一个 IoC 容器只会创建⼀次简单对象,默认值;
prototype:每⼀次都会创建新的对象;

<!--控制简单对象创建次数-->
<bean id="scope" scope="singleton" class="com.yusael.scope.Scope"/>
控制复杂对象的创建次数

如果是 FactoryBean 方式创建的复杂对象:

public class xxxFactoryBean implements FactoryBean {
	public boolean isSingleton() {
		return true; // 只会创建⼀次
		// return false; // 每⼀次都会创建新的
	}
	// 省略其余实现方法......
}

如果是实例工厂或者静态工厂,没有 isSingleton ⽅法,与简单对象一样通过 scope 控制。

为什么要控制对象的创建次数?

好处:节省不必要的内存浪费。

什么样的对象只创建⼀次?(service DAO)

  • 重量级的、可以被共用的、线程安全的

什么样的对象每⼀次都要创建新的?

  • 不能被共用的,线程不安全的…(session Connecton)
网友评论