Spring学习笔记(1)中IOC概念如何理解?

2026-05-19 12:151阅读0评论SEO资源
  • 内容介绍
  • 文章标签
  • 相关推荐

本文共计5223个文字,预计阅读时间需要21分钟。

Spring学习笔记(1)中IOC概念如何理解?

Spring 工厂是什么?Spring 是一个轻量级的JavaEE解决方案,整合了众多优秀的设计模式。轻量级意味着对运行环境要求低;对运行环境无额外要求;开源:Tomcat、Resin、Jetty;收费:WebLogic、WebSphere。

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 通用工厂的设计

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

通用工厂代码

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包:

<!-- 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="www.springframework.org/schema/beans" xmlns:xsi="www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="www.springframework.org/schema/beans 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学习笔记(1)中IOC概念如何理解?

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注入

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 工厂和配置文件中完成。
  • 好处:解耦合;
  • 底层实现:工厂设计模式;

依赖注入
  • 注入:通过 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;

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)

本文共计5223个文字,预计阅读时间需要21分钟。

Spring学习笔记(1)中IOC概念如何理解?

Spring 工厂是什么?Spring 是一个轻量级的JavaEE解决方案,整合了众多优秀的设计模式。轻量级意味着对运行环境要求低;对运行环境无额外要求;开源:Tomcat、Resin、Jetty;收费:WebLogic、WebSphere。

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 通用工厂的设计

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

通用工厂代码

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包:

<!-- 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="www.springframework.org/schema/beans" xmlns:xsi="www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="www.springframework.org/schema/beans 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学习笔记(1)中IOC概念如何理解?

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注入

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 工厂和配置文件中完成。
  • 好处:解耦合;
  • 底层实现:工厂设计模式;

依赖注入
  • 注入:通过 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;

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)