spring5笔记

导读:本篇文章讲解 spring5笔记,希望对大家有帮助,欢迎收藏,转发!站点地址:www.bmabk.com

第一章 引言

EJB(Enterprise Java Bean)存在的问题:EJB 是重量级的框架。

  • 运行环境苛刻
  • 代码移植性差

1.1 什么是 Spring

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

1.2 什么是轻量级?

  • 对于运行环境是没有额外要求的; 开源:tomcat、resion、jetty 收费:weblogic、websphere
  • 代码移植性高:不需要实现额外接口

整合设计模式:

  • 工厂
  • 代理
  • 模板
  • 策略

1.3 工厂设计模式

  • 什么是工厂设计模式?

概念:通过工厂类,创建对象;

User user = new User();
UserDAO userDAO = new UserDAOImpl();

好处:解耦合。 耦合:指定是代码间的强关联关系,⼀方的改变会影响到另⼀方; 问题:不利于代码维护; 简单:把接口的实现类,硬编码在程序中;

1.3.1 简单工厂的设计

package com.baizhiedu.basic;

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.baizhiedu.basic.UserServiceImpl");
	       UserService userService = (UserService)clazz.newInstance();
     */

    public static UserService getUserService() {
        UserService userService = null;
        try {
            //com.baizhiedu.basic.UserServiceImpl
            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;
    }

}

配置文件 applicationContext.properties:

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

问题:简单工厂会存在大量的代码冗余。 在这里插入图片描述 通用工厂的代码:

package com.baizhiedu.basic;

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;
     }
}

配置文件 applicationContext.properties:

# 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

通用工厂的使用方式

  • 定义类型 (类)
  • 通过配置文件的配置告知工厂 applicationContext.properties 中 key = value;
  • 通过工厂获得类的对象 Object ret = BeanFactory.getBean(“key”);

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

第二章 第一个spring程序

2.1 软件版本

  • JDK1.8+
  • Maven3.5+
  • IDEA2018+
  • SpringFramework 5.1.4 官方网站 依赖查询网站:

2.2 环境搭建

  • spring的jar包
#设置pom 依赖
<!-- 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的配置文件
  1. 配置文件的存放位置:任意位置,没有硬性要求
  2. 配置文件的命名:没有硬性要求,建议:applicationContext.xml
  • IDEA中新建spring配置文件的方式 在这里插入图片描述

2.3 Spring的核心API

  • ApplicationContext
  1. 作用: spring提供的ApplicationContext这个工厂,用于对象的创建.
  2. 好处 :解耦合.
  • Application接口类型

接口: 屏蔽实现的差异. 非web环境实现ApplicationContext此接口: ClassPathXmlApplicationContext (主要使用此实现) web环境: XmlWebApplicationContext

在这里插入图片描述

  • 重量级资源
  1. ApplicationContext工厂的对象占用大量内存.
  2. 不会频繁的创建对象: 一个应用只会创建一个工厂对象.
  3. ApplicationContext工厂: 一定是线程安全的(多线程并发可以访问).

2.4 程序开发

  1. 创建类型:Person.java
public class Person {}
  1. 配置文件的配置:
<?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>
  1. 通过工厂类,获得对象
/**
 * 用于测试Spring的第一个程序
 */
@Test
public void test() {
    // 1、获取spring的工厂
    ApplicationContext ctx = new ClassPathXmlApplicationContext("/applicationContext.xml");
    // 2、通过工厂类获得对象
    Person person = (Person)ctx.getBean("person");
    System.out.println(person);
}

2.5 细节分析

  • 名词解释

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
if (ctx.containsBeanDefinition("a")) {
  System.out.println("true = " + true);
}else{
  System.out.println("false = " + false);
}

containsBean:用于判断是否存在指定 id 值的 bean,也可以判断 name 值

//用于判断是否存在指定id值得bean
if (ctx.containsBean("person")) {
  System.out.println("true = " + true);
}else{
  System.out.println("false = " + false);
}
  • 配置文件中需要注意的细节

如果 bean 只配置 class 属性:

只配置class属性
<bean  class="com.baizhiedu.basic.Person"/>
  • 会自动生成一个 id,com.yusael.basic.Person#1 可以使用 getBeanNamesForType 验证。
  • 应用场景: 如果这个 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也可以。

2.6 Spring工厂的底层实现原理(简易图)

Spring工厂是可以调用对象私有的构造方法创建对象的

在这里插入图片描述

思考: 问题: 未来在开发过程中,是不是所有的对象,都会交给Spring工厂来创建对象呢? 回答: 理论上,是的,但是有特列: 实体对象(entity)是不会交给Spring来创建的,它是由持久层框架进行创建的.

第三章 Spring5.x与日志框架的整合

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

3.1 Spring如何整合日志框架

1. 默认
  	Spring1.2.3早期都是于commons-logging.jar
  	Spring5.x默认整合的日志框架 logback log4j2
2. Spring5.x整合log4j 
  1. 引入log4j jar包
  2. 引入log4.properties配置文件

3.2 Spring5.x 整合 log4j

  • 引⼊ log4j.jar 包
<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>
  • 引⼊ log4.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>

4.1 Spring注入的原理分析(简易版)

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

在这里插入图片描述

4.2 Set方式注入详解

Set注入的变量类型:

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

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

<property> xxxxx </property>

在这里插入图片描述

4.2.1 JDK内置类型

Person实体类对象如下:

package com.chentf.entity;
import java.io.Serializable;
import java.util.*;
/**
 * @author chentengfei
 * @desc 通用工程
 * @date 2020-12-26 16:12:45
 */
public class Person implements Serializable {
    private Integer id;
    private String name;
    private String[] emails;
    private Set<String> tels;
    private List<String> addresses;
    private Properties p;
    private Map<String,String> map;
    public Person() {
        System.out.println("Person.Person");
    }
	//get/set方法
	//toString()方法
}

4.2.1.1 String+8种基本类型

applicationContext.xml配置文件中的配置:

<bean id="person" class="com.chentf.entity.Person">
    <property name="id">
        <value>123456</value>
    </property>
    <!-- <property name="id" value="456789"/>--> 这个配置方式和上面的是一样的
    <property name="name">
        <value>chentf</value>
    </property>
</bean>
/**
  *  用于测试:用于测试注入
  */
@Test
public void test7() {
    ApplicationContext ctx = new ClassPathXmlApplicationContext("/applicationContext.xml");
    Person person = (Person) ctx.getBean("person");
    /*person.setId(1);
      person.setName("suns");*/ 可以想象set/get方法赋值和上面配置文件赋值是一样的意思
    System.out.println("person = " + person);
}

4.2.1.2 数组

<!--数组的赋值方式-->
<property name="emails">
    <list>
        <value>1216006658@qq.com</value>
        <value>ctflq115417@163.com</value>
        <value>793304740@qq.com</value>
    </list>
</property>

4.2.1.3 Set集合

<!--Set集合的赋值方式-->
<property name="tels">
    <set>
        <value>111111111</value>
        <value>222222222</value>
        <value>333333333</value>
        <value>444444444</value>
    </set>
</property>

4.2.1.4 List集合

<!--list集合的赋值方式-->
<property name="addresses">
    <list>
        <value>张三</value>
        <value>李四</value>
        <value>王五</value>
        <value>赵六</value>
    </list>
</property>

4.2.1.5 Map

<!--Map集合的赋值方式 注意: map -> entry  -> key有特定的标签  <key></key>值根据对应类型选择对应类型的标签-->
<property name="map">
    <map>
        <entry>
            <key><value>chentf</value></key>
            <value>13269239911</value>
        </entry>
        <entry>
            <key><value>chenty</value></key>
            <value>wdbb</value>
        </entry>
    </map>
</property>

4.2.1.6 Properites

<!--Properites属性的赋值方式-->
<property name="p">
    <props>
        <prop key="key1">sunwukong</prop>
        <prop key="key2">zhubajie</prop>
    </props>
</property>

注意:这几种赋值方式中,还有一种可以使用 <ref bean>引用标签,并且<value>标签和<value>中的值都是和实体类中对应成员变量设置的数据类型有关系.

整体的测试用例代码如下:

/**
  *  用于测试:JDK类型成员变量的赋值
  */
 @Test
public void test08(){
    ApplicationContext act = new ClassPathXmlApplicationContext("/applicationContext.xml");
    Person person = (Person) act.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> maps = person.getMap();
    Set<String> keys = maps.keySet();
    for (String key : keys) {
        System.out.println("key = "+key + " value = "+maps.get(key));
    }
    System.out.println("-----------------------");
    Properties pro = person.getP();
    System.out.println("key is key1" + " value is " + pro.getProperty("key1"));
    System.out.println("key is key2" + " value is " + pro.getProperty("key2"));
}

4.2.1.7 复杂的JDK类型(Date)

需要程序员自定义类型转换器,处理.

4.2.2 用户自定义类型

4.2.2.1 第一种方式

  • 为成员变量提供 set get 方法
  • 配置文件中进行注入(赋值)
//UserServiceImpl中配置UserDAO的成员变量,并提供set/get方法
public class UserServiceImpl implements UserService {

    //传统方式,直接new接口实现类对象,耦合度较高
    //private UserDAO userDAO = new UserDAOImpl();
    
    public UserDAO userDAO;

    public UserDAO getUserDAO() {
        return userDAO;
    }

    public void setUserDAO(UserDAO userDAO) {
        this.userDAO = userDAO;
    }

    @Override
    public void register(User user) {
        userDAO.save(user);
    }

    @Override
    public void login(String name, String password) {
        userDAO.queryUserByNameAndPassWord(name,password);
    }
}
<!-- 将UserDAOImpl注入到UserServiceImpl中-->
<bean id="userService" class="com.chentf.service.impl.UserServiceImpl">
    <property name="userDAO">
        <bean class="com.chentf.dao.impl.UserDAOImpl"></bean>
    </property>
</bean>
/**
  *  用于测试:用户自定义类型成员变量的赋值
  */
@Test
public void test09(){
    ApplicationContext act = new ClassPathXmlApplicationContext("applicationContext.xml");
    UserService userService = (UserService) act.getBean("userService");
    userService.register(new User("chentf","123456789"));
    userService.login("zhangsan","abcd");
}

4.2.2.2 第二种方式

  • 第一种赋值方式的问题如下:
    • 配置文件代码冗余
    • 被注入的对象(UserDAO),多次创建,浪费(JVM)内存资源
  • 为成员变量提供set/get方法
  • 配置文件中进行配置
<!--第二种方式是单独定义userDAO,然后将userDAO通过ref引入userService中,避免重复创建userDAO对象-->
<bean id="userDAO" class="com.chentf.dao.impl.UserDAOImpl"></bean>
<bean id="userService" class="com.chentf.service.impl.UserServiceImpl">
    <property name="userDAO">
        <ref bean="userDAO"></ref>
    </property>
</bean>

Spring4.x 废除了 <ref local=""/> 基本等效 <ref bean=""/>

4.2.3 Set注入方式的简化写法

4.2.3.1 基于属性简化

JDK类型注入

<property name="name">
   <value>suns</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>

4.2.3.2 基于p命名空间的简化

p标签在IDEA工具的xml配置文件中通过alt+enter快捷键导入

在这里插入图片描述 JDK类型注入:

<bean id="person" class="com.chentf.entity.Person">
    <property name="id">
        <value>123456</value>
    </property>
    <property name="name">
        <value>chentf</value>
    </property>
</bean>

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

<bean id="person" class="com.chentf.entity.Person" p:name="chentf" p:id="100"/>

用户自定义类型注入:

<bean id="userDAO" class="com.chentf.dao.impl.UserDAOImpl"></bean>
<bean id="userService" class="com.chentf.service.impl.UserServiceImpl">
    <property name="userDAO">
        <ref bean="userDAO"></ref>
    </property>
</bean>

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

<bean id="userService" class="com.chentf.service.impl.UserServiceImpl" p:userDAO-ref="userDAO"></bean>

4.3 构造方法注入详解

  • 注入: 通过Srping的配置文件,为成员变量赋值
  • Set注入: Spring调用Set方法,通过配置文件,为成员变量赋值
  • 构造注入: Spring调用构造方法,通过配置文件,为成员变量赋值

4.3.1 开发步骤

提供有参的构造方法

public class Customer implements Serializable {
    private String name;
    private int age;

    public Customer(String name) {
        this.name = name;
    }

    public Customer(int age) {
        this.age = age;
    }

    public Customer(String name, int age) {
        this.name = name;
        this.age = age;
    }
}

Spring的配置文件配置如下:

<bean id="customer" class="com.chentf.entity.Customer">
    <constructor-arg>
        <value>chentf</value>
    </constructor-arg>
    <constructor-arg>
        <value>31</value>
    </constructor-arg>
</bean>

4.3.2 构造方法重载

构造参数个数不同时

参数个数不同时,通过控制 <constructor-arg> 标签的数量进行区分; 如果只有一个参数的话,只需要一对 <constructor-arg> 标签:

<bean id="customer" class="com.yusael.constructor.Customer">
    <constructor-arg>
        <value>zhenyu</value>
    </constructor-arg>
</bean>

如果有两个参数的话,用两对 <constructor-arg> 标签,以此类推。

<bean id="customer" class="com.yusael.constructor.Customer">
    <constructor-arg>
        <value>zhenyu</value>
    </constructor-arg>
    <constructor-arg>
        <value>22</value>
    </constructor-arg>
</bean>

构造参数个数相同时

构造参数个数相同时,通过在标签引入 type 属性 进行类型的区分 <constructor-arg type="">

<bean id="customer" class="com.yusael.constructor.Customer">
	<constructor-arg type="int">
	    <value>20</value>
	</constructor-arg>
</bean>

注入总结

日常使用中,应用Set注入还是构造注入呢? 答案: Set注入更多

  • 构造注入麻烦(需要考虑重载)
  • Spring框架底层,大量应用了Set方法注入

在这里插入图片描述

第五章 反转控制与依赖注入

5.1 反转(转移)控制(IOC Inverse of Control)

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

在这里插入图片描述

5.2 依赖注入(Dependency Injection DI)

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

在这里插入图片描述

5.3 Spring工程创建复杂对象

在这里插入图片描述 什么是复杂对象

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

Srping工厂创建复杂对象的3种方式

5.3.1 FactoryBean 接口

开发步骤

  • 实现 FactoryBean 接口:实现 getObject,getObjectType,isSingleton 方法;
  • getObject():用于书写创建复杂对象时的代码。
  • getObjectType():返回创建的复杂对象的类型。
  • isSingleton:用于决定是否单例。

在这里插入图片描述

public class ConnectionFactory implements FactoryBean<Connection> {

    @Override
    public Connection getObject() throws Exception {
        Class.forName("com.MySQL.jdbc.Driver");
        Connection connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/chentf", "root", "admin");
        return connection;
    }

    @Override
    public Class<?> getObjectType() {
        return Connection.class;
    }

    @Override
    public boolean isSingleton() {
        return false;
    }
}

Spring配置文件的配置:

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

<!--如果Class中指定的类型,是FactoryBean接口的实现类,那么通过id值获得的是这个类所创建的复杂对象  Connection-->
<bean id="conn" class="com.chentf.factorybean.ConnectionFactory"></bean>
测试用例如下:
/**
 *  用于测试:用于测试FactoryBean接口
 */
@Test
public void test11(){
    ApplicationContext act = new ClassPathXmlApplicationContext("/applicationContext.xml");
    Connection conn = (Connection) act.getBean("conn");
    System.out.println("conn = "+ conn);
}

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

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

isSingleton 方法返回 true 只会创建⼀个复杂对象,返回 false 每⼀次都会创建新的对象; 需要根据这个对象的特点 ,决定是返回 true(SqlSessionFactory) 还是 false(Connection); mysql 高版本连接创建时,需要制定 SSL 证书,否则会警告;

Sat May 23 23:18:04 CST 2020 WARN: Establishing SSL connection without server's identity verification is not recommended. According to MySQL 5.5.45+, 5.6.26+ and 5.7.6+ requirements SSL connection must be established by default if explicit option isn't set. For compliance with existing applications not using SSL the verifyServerCertificate property is set to 'false'. You need either to explicitly disable SSL by setting useSSL=false, or set useSSL=true and provide truststore for server certificate verification.

解决方案:url = jdbc:mysql://localhost:3306/spring?useSSL=false

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

//自定义工程实例实现FactoryBean,并覆写FactoryBean接口的三个方法
public class ConnectionFactoryBean implements FactoryBean<Connection> {

    //将连接属性定义成成员变量,并提供set/get方法
    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 connection = DriverManager.getConnection(url,username,password);
        return connection;
    }

    //返回连接对象
    @Override
    public Class<?> getObjectType() {
        return Connection.class;
    }

    //设在建瓯时候单例还是多例
    @Override
    public boolean isSingleton() {
        return false;
    }
}
把ConnectionFactoryBean中依赖的4个字符串信息 ,进行配置文件的注入 
好处:解耦合
<bean id="conn" class="com.baizhiedu.factorybean.ConnectionFactoryBean">
  <property name="driverClassName" value="com.mysql.jdbc.Driver"/>
  <property name="url" value="jdbc:mysql://localhost:3306/suns?useSSL=false"/>
  <property name="username" value="root"/>
  <property name="password" value="123456"/>
</bean>
/**
  *  用于测试:用于测试实例工厂
  */
@Test
public void test13() {
    ApplicationContext ctx = new ClassPathXmlApplicationContext("/applicationContext.xml");
    Connection conn = (Connection) ctx.getBean("conn");
    System.out.println("conn = " + conn);
}

FactoryBean的实现原理

接口回调

  1. 为什么 Spring 规定 FactoryBean 接口实现 getObject()
  2. act.getBean("conn")获得的是复杂对象Connection而没有获得ConnectionFactoryBean对象,获取ConnectionFactoryBean对象需要通过getBean("&conn"),重点注意加&

Spring内部运行流畅

  1. 通过conn获取ConnectionFactoryBean类的独享,进而通过instanceof判断出是FactoryBean接口的实现类.
  2. Spring安装规定,通过getObject()获取Connection类.
  3. 返回Connection对象.

在这里插入图片描述 FactoryBean总结

Spring中用于创建复杂对象的一种方式,也是Spring原生提供的,后续讲解Spring整合其他框架,大量应用了FactoryBean接口.

5.3.2 实例工厂

  1. 避免Spring框架的侵入
  2. 整合遗留系统一般会用到实例工厂

ConnectionFactory 类

public class ConnectionFactory {
    public Connection getConnection(){
        Connection connection = null;
        try {
            Class.forName("com.mysql.jdbc.Driver");
            connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/spring5?useSSL=false","root","admin");
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (SQLException e) {
            e.printStackTrace();
        }
        return  connection;
    }
}

配置文件:

<bean id="connFactory" class="com.chentf.factorybean.ConnectionFactory"></bean>
<bean id="conn" factory-bean="connFactory" factory-method="getConnection"></bean>
@Test
public void test14() {
    ApplicationContext ctx = new ClassPathXmlApplicationContext("/applicationContext.xml");
    ConnectionFactory conn = (ConnectionFactory) ctx.getBean("connFactory");
    System.out.println("conn = " + conn);
}

5.3.3 静态工厂

StaticConnectionFactory 类

public class StaticConnectionFactory {
    public static Connection getConnection(){
        Connection conn = null;
        try {
            Class.forName("com.mysql.jdbc.Driver");
            conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/spring5?useSSL=false", "root", "admin");
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (SQLException e) {
            e.printStackTrace();
        }
        return conn;
    }
}

配置文件:

<bean id="conn" class="com.chentf.factorybean.StaticConnectionFactory" factory-method="getConnection"></bean>
@Test
public void test15(){
    ApplicationContext ctx = new ClassPathXmlApplicationContext("/applicationContext.xml");
    Connection conn = (Connection) ctx.getBean("conn");
    System.out.println("conn = "+conn);
}

5.3.4 Spring工厂创建对象的总结

在这里插入图片描述

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

5.4.1 如何控制简单对象的创建次数

  • 创建实体类如下:
public class Account {}
  • 通过配置文件实例化对象
<bean id="account" class="com.chentf.scope.Account" scope="singleton | prototype"></bean>
singleton: 只会创建一次简单的对象,spring默认配置式singleton.
prototype: 每次都会创建新的对象.
  • 测试用例如下:
@Test
public void test16(){
    ApplicationContext ctx = new ClassPathXmlApplicationContext("/applicationContext.xml");
    Account account = (Account) ctx.getBean("account");
    Account account2 = (Account) ctx.getBean("account");
    System.out.println("account = "+account);
    System.out.println("account2 = "+account2);
}

5.4.2 如何控制复杂对象的创建次数

//复杂对象通过实现spring的FactoryBean接口,覆写isSingleton方法,返回ture表示singleton,返回false表示prototype,如果没有isSingleton方法,还是通过scope属性,进行对象创建次数的控制
 @Override
public boolean isSingleton() {
    return false;
}

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

好处: 节省不必要的内存浪费. 什么样的对象只创建一次?

  • 重量级的、可以被共用的、线程安全的… 比如: SqlSessionFactory,DAO,Service

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

  • 不能被共用的,线程不安全的… 比如: Connection,SqlSession,Session,Struts2 Action

第六章 对象的生命周期

##

6.1 什么是对象的生命周期

指的是一个对象创建,存活,消亡的一个完整的过程.

6.2 为什么要学习对象的生命周期

由Spring负责对象的创建,存活,销毁,了解Spring的生命周期,有利于我们使用好Spring为我们创建对象.

6.3 生命周期的三个阶段

创建阶段 —> 初始化阶段 —> 销毁阶段

  • 创建阶段

Spring 工厂何时创建对象?

  • scope=“prototype”:Spring 工厂在获取对象 ctx.getBean(“xxx”) 的同时,创建对象。
  • scope=“singleton”:Spring 工厂(IoC 容器)创建的同时,创建对象。 通过配置 也可以实现工厂获取对象的同时,创建对象。

scope = "singleton"

理解此处构造方法是在Spring工厂创建的同时,对象就创建了
public class Product {
    public Product(){
        System.out.println("Product.Product");
    }
}
<bean id="product" scope="singleton" class="com.chentf.life.Product"></bean>
测试结果如下:可以发现配置文件中配置singleton,测试代码中还没有调用ctx.getBean("product")方法,构造方法就已经调用了,这就是工厂创建的同时,对象就创建了.
@Test
public void test17(){
    ApplicationContext ctx = new ClassPathXmlApplicationContext("/applicationContext.xml");
}

scope = "prototype"

<bean id="product" scope="prototype" class="com.chentf.life.Product"></bean>
测试结果如下: Spring工厂会在获取对象的同时,创建对象.当配置文件中配置prototype,必须调用了getBean方法,Prodct类中的构造方法才会被调用.
@Test
public void test17(){
    ApplicationContext ctx = new ClassPathXmlApplicationContext("/applicationContext.xml");
    Product product = (Product) ctx.getBean("product");
}

注意,当配置了scope = “singleton”这种情况,同时又希望只是在获取对象的时候创建对象,可以加<bean lazy-init="true"/>

  • 初始化阶段

Spring工厂在创建完对象后,调用对象的初始化方法,完成对应的初始化操作

  1. 初始化方法提供: 程序员提供初始化方法,最终完成初始化操作.
  2. 初始化方法调用: Srping工厂进行调用.

InitializingBean接口:

public class Product implements InitializingBean {
    public Product(){
        System.out.println("Product.Product");
    }
    //这个就是InitializingBean接口的初始化方法,做一些初始化操作,Spring会自动调用
    @Override
    public void afterPropertiesSet() throws Exception {
        System.out.println("Porduct.afterPropertiesSet");
    }
}

对象中提供一个普通的初始化方法,配置文件种配置 init-method:

//自定义初始化方法,方法名任意
public void myInit(){
    System.out.println("Product.myInit");
}
自定义的初始化方法,通过配置init-method="myInit",Spring会自动调用
<bean id="product" scope="prototype" class="com.chentf.life.Product" init-method="myInit"></bean>

细节分析

  1. 如果一个对象即实现了InitializingBean接口的初始化方法,同时有提供了普通的初始化方法,那么他们的执行顺序是怎么样呢? 答案: 先执行InitializingBean中的初始化方法,在执行自定义的普通初始化方法.
  1. 如果配置文件中注入了实体类中的属性,如下:
  <bean id="product" scope="prototype" class="com.chentf.life.Product" init-method="myInit">
      <property name="name" value="chentf"></property>
  </bean>

这种情况下则注入方法一定发生在初始化操作之前

  1. 什么叫做初始化操作 比如: 资源的初始化,如数据库,IO,网络等…
  • 销毁阶段

Spring销毁对象前,会调用对象的销毁方法,完成销毁操作

  1. Spring什么时候销毁所创建的对象? 答案: 调用ctx.close();
  2. 销毁方法:程序员根据自己的需求,定义销毁方法,完成销毁操作 调用:Spring工厂完成调用
  1. DisposableBean接口
public class Product implements InitializingBean, DisposableBean {
    //销毁方法:销毁操作(资源释放的操作 )
    @Override
    public void destroy() throws Exception {
        System.out.println("Product.destroy");
    }
  1. 对象中提供一个普通的销毁方法,配置文件种配置 destroy-method
public void myDestroy()throws Exception{
    System.out.println("Product.myDestroy");
}
<bean id="product" scope="prototype" class="com.chentf.life.Product" init-method="myInit" destroy-method="myDestroy">
    <property name="name" value="chentf"></property>
</bean>

细节分析

  1. 销毁方法的操作只使用于scope="singleton",初始化操作都适用。
  2. 什么叫做销毁操作 主要指的是资源的释放操作,比如:io流的关闭,数据库连接的关闭

对象的生命周期总结

public class Product implements InitializingBean, DisposableBean {
    private String name;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        System.out.println("Product.setName");
        this.name = name;
    }

    Product() { // 创建
        System.out.println("Product.Product");
    }

    // 程序员根据需求实现的方法, 完成初始化操作
    public void myInit() {
        System.out.println("Product.myInit");
    }

    // 程序员根据需求实现的方法, 完成初始化操作
    @Override
    public void afterPropertiesSet() throws Exception {
        System.out.println("Product.afterPropertiesSet");
    }

    public void myDestory() {
        System.out.println("Product.myDestory");
    }

    // 程序员根据自己的需求, 定义销毁方法, 完成销毁操作
    @Override
    public void destroy() throws Exception {
        System.out.println("Product.destroy");
    }
}
<bean id="product" class="com.yusael.life.Product" init-method="myInit" destroy-method="myDestory">
	<property name="name" value="yusael"/>
</bean>

6.4 配置文件参数化

把Spring配置文件中需要经常修改的字符串信息,转移到一个更小的配置文件中.

  1. Spring的配置文件中存放需要经常修改的字符串? 答案: 存在,以数据库连接相关配置为代表.
  2. 经常变化的字符串,在Spring的配置文件中,直接修改,不方便项目的维护.
  3. 转移到一个小的配置文件,一般是properties文件,利于维护.
  4. 配置文件参数化: 利于Spring配置文件的维护.

1. 配置文件参数化的开发步骤

  • 提供一个小的配置文件(一般是properties文件),文件名与放置位置没有要求
jdbc.driverClassName = com.mysql.jdbc.Driver
jdbc.url = jdbc:myslq://localhost:3306/spring5?useSSL=false
jdbc.username = root
jdbc.password = admin
  • spring的配置文件与小配置文件进行整合
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 connection = DriverManager.getConnection(url,username,password);
        return connection;
    }

    @Override
    public Class<?> getObjectType() {
        return Connection.class;
    }

    @Override
    public boolean isSingleton() {
        return false;
    }
}
<!--Spring配置文件与小配置文件进行整合-->
<!--classpath:/代表的就是classs的根路径,因为maven打包后java文件和resouce文件其实在同一个目录下面-->
<context:property-placeholder location="classpath:/db.properties"/>

<bean id="conn" class="com.chentf.factorybean.ConnectionFactoryBean">
    <!--其中name的配置对应ConnectionFactory定义的成员变量,并且需要听get/set方法,value的配置对应db.properties中的key-->
    <property name="driverClassName" value="${jdbc.driverClassName}"/>
    <property name="url" value="${jdbc.url}"/>
    <property name="username" value="${jdbc.username}"/>
    <property name="password" value="${jdbc.password}"/>
</bean>
/**
  * 用于测试:配置文件参数化
  */
@Test
public void test18(){
    ApplicationContext ctx = new ClassPathXmlApplicationContext("/applicationContext1.xml");
    Connection conn = (Connection) ctx.getBean("conn");
    System.out.println("conn = "+conn);
}

在这里插入图片描述

6.5 自定义类型转换器

6.5.1 类型转换器

作用: Spring通过类型转换器把配置文件中字符串类型的数据,转换成了对象中成员变量对象类型的数据,进而完成了注入

在这里插入图片描述

6.5.2 自定义类型转换器

原因: 当Spring内部没有提供特点的类型转换器时,而程序员在应用过程中还需要使用,那么就需要程序员自己自定义类型转换器.

  • 实体类
@Data
public class Person {
    private String name;
    private Date birthday;
}
  • 自定义类实现Converter接口
public class MyDateConverter implements Converter<String, Date> {

    /*
        convert方法的作用:将配置文件中注入的String类型数据转换成为Date类型的数据
                    传统方式: SimpleDateFormat sdf = new SimpleDateFormat("yyyy-mm-dd);
        参数source: 代表的是配置文件中日期字符串<vlaue>2021-01-12</value>
        return: 当把转换好的Date作为convert方法的返回值后,Spring自动的为Person类中的birthday属性进行注入(赋值)
     */
    @Override
    public Date convert(String source) {
        Date date = null;
        try {
            SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
            date = sdf.parse(source);
        } catch (ParseException e) {
            e.printStackTrace();
        }
        return date;
    }
}
  • 在Spring的配置文件中进行配置
<!--Spring创建MyDateConverter类型对象-->
<bean id="myDateConverter" class="com.chentf.converter.MyDateConverter"></bean>

<!--类型转换器的注册-->
<!--将上面创建的的对象引入注册类型转换器中-->
<!--下面是用于注册Spring的类型转换器,注意:id和properties中的name值都是固定的-->
<bean id="conversionService" class="org.springframework.context.support.ConversionServiceFactoryBean">
    <property name="converters">
        <!--set注入,可以注入多个-->
        <set>
            <ref bean="myDateConverter"/>
        </set>
    </property>
</bean>

<bean id="person" class="com.chentf.converter.Person">
    <property name="name" value="chentf"/>
    <property name="birthday" value="1990-01-15"/>
</bean>

6.5.3 细节点

  • MyDateConverter中的日期格式,可以不写死,通过依赖注入的方式,由配置文件完成赋值
@Setter
@Getter
public class MyDateConverter implements Converter<String, Date> {
	//定义一个成员变量,用来作为传入日期格式yyyy-MM-dd的参数
    private String pattern;
    
    @Override
    public Date convert(String source) {
        Date date = null;
        try {
            SimpleDateFormat sdf = new SimpleDateFormat(pattern);
            date = sdf.parse(source);
        } catch (ParseException e) {
            e.printStackTrace();
        }
        return date;
    }
}
<!--Spring创建MyDateConverter类型对象-->
    <bean id="myDateConverter" class="com.chentf.converter.MyDateConverter">
        <!--注解通过set注入方式给成员变量赋值-->
        <property name="pattern" value="yyyy-MM-dd"/>
    </bean>

    <!--将上面创建的的对象引入注册类型转换器中-->
    <!--下面是用于注册Spring的类型转换器,注意:id和properties中的name值都是固定的-->
    <bean id="conversionService" class="org.springframework.context.support.ConversionServiceFactoryBean">
        <property name="converters">
            <!--set注入,可以注入多个-->
            <set>
                <ref bean="myDateConverter"/>
            </set>
        </property>
    </bean>

    <bean id="person" class="com.chentf.converter.Person">
        <property name="name" value="chentf"/>
        <property name="birthday" value="1990-01-15"/>
    </bean>
  • ConversionServiceFactoryBean定义的di属性值必须是conversionService
  • Spring框架内置了日期类型的转换器,但是要求日期格式必须是1990/01-15(不支持2020-01-15)

6.6 后置处理Bean

BeanPostProcessor作用: 对Spring工厂所创建的对象,进行再加工. AOP底层实现: 通过实现BeanPostProcessor接口,覆写接口的两个方法.

  • 后置处理Bean的运行原理分析: - 程序员实现 BeanPostProcessor 接口中规定的两个方法:
public Object postProcessBeforeInitialization(Object bean, String beanName) {
   return bean;
}

作用:Spring创建完对象,并进行注入之后,可以运行Before方法进行再加工

  • 获得Spring创建好的对象,通过方法参数Bean获得最终通过返回值交给Spring框架
public Object postProcessAfterInitialization(Object bean, String beanName) {
    return bean;
}

作用: Spring执行完对象的初始化操作后,运行After方法进行再加工

  • 获得Spring创建好的对象: 通过方法参数Bean获得最终通过返回值交给Spring框架

实战中: 很少处理Spring的初始化操作: 没有必要区分Before,After.只需要实现其中的After方法即可. 注意: 一般覆写postProcessBeforeInitiallization,对对象进行再加工后,直接return bean对象.

  • BeanPostProcessor接口开发步骤 1.自定义类实现BeanPostprocessor接口
//编写实体类,实现get/set方法
@Data
public class Categroy {
    private int id;
    private String name;
}
//自定义后置处理bean
public class MyBeanPostProcessor implements BeanPostProcessor {
    /*
        前置方法一般不做处理,但是Spring创建好的对象会先传个该方法,所以该方法需要返回bean对象给容器,
        以便after方法接收Bean对象
     */
    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        return bean;
    }

    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        //此处的判断很有必要,因为BeanPostProcessor会对配置文件中的所有对象进行加工,此处可以过滤不需要加工的对象
        if(bean instanceof Categroy){
            Categroy categroy = (Categroy) bean;
            categroy.setName("sunwukong");
        }
        return bean;
    }
}
<!--注入bena对象-->
<bean id="categroy" class="com.chentf.beanpost.Categroy">
    <property name="id" value="30"/>
    <property name="name" value="chentf"/>
</bean>
<bean id="user" class="com.chentf.converter.Person"/>
<bean id="myBeanPostProcessor" class="com.chentf.beanpost.MyBeanPostProcessor"/>
//测试用例
/**
  *  用于测试:BeanPostProcessor
  */
@Test
public void test20() {
    ApplicationContext ctx = new ClassPathXmlApplicationContext("/applicationContext3.xml");
    Categroy categroy = (Categroy) ctx.getBean("categroy");
    System.out.println("categroy = " + categroy.getName());
}

BeanPostPrcessor细节:BeanPostProcessor 会对 Spring 工厂创建的所有对象进行加工。如果工厂创建了多个不同的对象,要注意区别传入的对象

@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
    if (bean instanceof  Category) {
        Category category = (Category) bean;
        category.setName("yusael");
        return category;
    }
    return bean;
}

第七章 动态代理模式

7.1 为什么需要代理设计模式

在 JavaEE 分层开发开发中,哪个层次对于我们来讲最重要? DAO —> Service –> Controller JavaEE分层开发中,最为重要的是Service

Service层中包含了哪些代码? 核心功能(代码量较多):业务运算,DAO 调用 额外功能(附加功能,不属于业务,可有可无,代码量小):事务、日志、性能

额外功能书写在 Service 层好不好? Service 层的调用者的角度(Controller):需要在 Service 层书写额外功能。 软件设计者:Service 层不需要额外功能。

  • 拿现实生活中的例子来做对比,解决方案是 引入一个代理 -

7.2 代理模式

  • 概念

概念:通过代理类,为原始类(目标类)增加额外的功能 好处:利于原始类(目标类)的维护

  • 名词解释

目标类 / 原始类:指的是 业务类 (核心功能 –> 业务运算、DAO调用) 目标方法 / 原始方法:目标类(原始类)中的方法就是目标方法(原始方法) 额外功能 / 附加功能:日志、事务、性能 …

 

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/1423.html

(0)
小半的头像小半

相关推荐

极客之音——专业性很强的中文编程技术网站,欢迎收藏到浏览器,订阅我们!