Spring知识点,这篇搞定(一)

本文涉及的产品
云数据库 RDS MySQL,集群系列 2核4GB
推荐场景:
搭建个人博客
RDS MySQL Serverless 基础系列,0.5-2RCU 50GB
云数据库 RDS MySQL,高可用系列 2核4GB
简介: Spring知识点,这篇搞定(一)

一、工厂设计模式

1.1、传统的容器——EJB的缺点

   EJB(Enterprise Java Beans),被称为企业Java Beans。他是上一代使用的容器。我们来看看传统的J2EE的体系。


 EJB具有的缺点是很致命的:

  1. 运行环境苛刻。
  2. 代码移植性很差。
  3. EJB是重量级框架。

1.2、什么是Spring

   Spring是一个轻量级的JavaEE解决方案,整合众多优秀的设计模式,其中最重要的设计模式是——工厂设计模式。他还包含其他的设计模式,比如说:代理设计模式、模板设计模式、策略设计模式等等。

1.3、什么是工厂设计模式

   在传统的创建对象的时候,我们都是调用无参构造函数来创建对象的即new的方式来创建,这样创建对象的方式的耦合程度(指定是代码间的强关联关系,一方的改变会影响到另一方)就十分高。、


   一旦我们需要修改类型,就需要代码中修改,并且重新编译和部署。

1.4、工厂设计模式的实现

package com.factory;
 
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.service.impl.UserServiceImpl
            env.load(inputStream);
            inputStream.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
 
    }
 
 
    /*
        对象的创建的两种方式:
          1. 直接调用构造方法创建对象  UserService userService = new UserServiceImpl();
          2. 通过反射的形式创建对象可以解耦合
               Class clazz = Class.forName("com.service.impl.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;
 
    }
}
userDAO = com.dao.userDAO
 
userService = com.service.userService

1.5、简单工厂的代码修改

我们可以发现,上面一个工厂设计模式的代码是又臭又长,我们每创建一个新的对象,都需要重新写一个工厂类,并且代码很大一部分都是相同的,仅仅只是创建的对象不同,于是我们可以抽取出共同的代码,组成一个通用的工厂类。

public class BeanFactory{
  
    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;
     }
 
}

1.6、总结

   Spring本质上就是一个工厂,只不过我们在日常开发的时候使用的不是自己写的工厂,因为这个工厂的功能很少,性能很低下,Spring帮我们写好了一个大型工厂(ApplicationContext),我们只需要在一个固定的配置文件(applicationContext.xml)中进行配置即可。

二、Spring入门

2.1、Spring简介

   Spring 是于 2003 年兴起的一个轻量级的 Java 开发框架,它是为了解决企业应用开发的复杂性而创建的。Spring 的核心是控制反转(IoC)和面向切面编程(AOP)。Spring 是可以在 Java SE/EE 中使用的轻量级开源框架。


  Spring 的主要作用就是为代码“解耦”,降低代码间的耦合度。就是让对象和对象(模块和模块)之间关系不是使用代码关联,而是通过配置来说明。即在 Spring 中说明对象(模块)的关系。


  Spring 根据代码的功能特点,使用 Ioc 降低业务对象之间耦合度。IoC 使得主业务在相互调用过程中,不用再自己维护关系了,即不用再自己创建要使用的对象了。而是由 Spring容器统一管理,自动“注入”,注入即赋值。 而 AOP 使得系统级服务得到了最大复用,且不用再由程序员手工将系统级服务“混杂”到主业务逻辑中了,而是由 Spring 容器统一完成“织入”。

2.2、Spring的优点

  Spring 是一个框架,是一个半成品的软件。由 20 个模块组成。它是一个容器管理对象,容器是装东西的,Spring 容器不装文本,数字。装的是对象。Spring 是存储对象的容器。他的优点主要是以下几个方面。

  1. 轻量。
  2. 面向接口编程。
  3. 面向切面编程
  4. 可以轻易集成其他优秀的框架。

2.2.1、轻量

   Spring 框架使用的 jar 都比较小,一般在 1M 以下或者几百 kb。Spring 核心功能的所需的 jar 总共在 3M 左右。

   Spring 框架运行占用的资源少,运行效率高。不依赖其他 jar。

2.2.2、面向接口编程

   Spring 提供了 Ioc 控制反转,由容器管理对象,对象的依赖关系。原来在程序代码中的对象创建方式,现在由容器完成。对象之间的依赖解耦合。

2.2.3、面向切面编程(AOP)

   通过 Spring 提供的 AOP 功能,方便进行面向切面的编程,许多不容易用传统 OOP 实现的功能可以通过 AOP 轻松应付。

   在 Spring 中,开发人员可以从繁杂的事务管理代码中解脱出来,通过声明式方式灵活地进行事务的管理,提高开发效率和质量。

2.2.4、集成其他优秀的框架

   Spring 不排斥各种优秀的开源框架,相反 Spring 可以降低各种框架的使用难度,Spring提供了对各种优秀框架(如 Shiro、MyBatis)等的直接支持。简化框架的使用。

   Spring 像插线板一样,其他框架是插头,可以容易的组合到一起。需要使用哪个框架,就把这个插头放入插线板。不需要可以轻易的移除。

2.3、Spring的体系结构

   Spring 由 20 多个模块组成,它们可以分为数据访问/集成(Data Access/Integration)、Web、面向切面编程(AOP, Aspects)、提供 JVM的代理(Instrumentation)、消息发送(Messaging)、核心容器(Core Container)和测试(Test)。

2.4、Spring的核心API

   Spring的核心就是一个大工厂:ApplicationContext,他的作用是用于对象的创建,且可以解除耦合,他是一个接口,但是ApplicationContext是一个重量级的工厂对象占用大量的内存,所以我们不会频繁得去创建对象,一般一个应用只会创建一个工厂对象。ApplicationContext是线程安全的,可以被多线程并发访问。


   他有两种实现方式:


适用于非WEB环境的:ClassPathXmlApplication


适用于WEB环境的:XmlApplicationContext


2.5、Spring的案例

2.5.1、引入依赖

    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-context</artifactId>
      <version>5.1.5.RELEASE</version>
    </dependency>

2.5.3、创建类型

package com.domain;
 
/**
 * @author Xiao_Lin
 * @date 2021/2/4 15:57
 */
public class Person {
 
}
 

2.5.4、修改配置文件

   在applicationContext.xml的配置文件中更改配置

<!--  id属性:名字-->
<!--  class属性:需要创建对象的全限定名-->
  <bean id="person" class="com.domain.Person"/>

2.5.5、创建对象

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

2.6、细节分析

2.6.1、名词解释

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

2.6.2、相关方法

//通过这种方式获得对象,就不需要强制类型转换
Person person = ctx.getBean("person", Person.class);
System.out.println("person = " + person);
        
 
//当前Spring的配置文件中 只能有一个<bean>标签的class是Person类型
Person person = ctx.getBean(Person.class);
System.out.println("person = " + person);
        
 
//获取的是 Spring工厂配置文件中所有bean标签的id值  person person1
String[] beanDefinitionNames = ctx.getBeanDefinitionNames();
for (String beanDefinitionName : beanDefinitionNames) {
  System.out.println("beanDefinitionName = " + beanDefinitionName);
}
        
 
//根据类型获得Spring配置文件中对应的id值
String[] beanNamesForType = ctx.getBeanNamesForType(Person.class);
for (String id : beanNamesForType) {
  System.out.println("id = " + id);
}
        
 
//用于判断是否存在指定id值的bean
if (ctx.containsBeanDefinition("a")) {
  System.out.println("true = " + true);
}else{
  System.out.println("false = " + false);
}
      
 
//用于判断是否存在指定id(name)值的bean
if (ctx.containsBean("person")) {
  System.out.println("true = " + true);
}else{
  System.out.println("false = " + false);
}

2.7、Spring创建对象的简易原理图


注意:反射底层调用的是无参构造函数来进行实例化对象的,即使构造方法私有了,依然可以调用进行实例化对象。

2.8、注意

   在未来开发的过程中,理论上所有的对象都是交给Spring工厂来创建,但是有一类特殊的对象——实体对象是不会交给Spring来创建的,它是交给持久层来创建的。

三、注入

3.1、什么是注入

   注入是指 Spring 创建对象的过程中,将对象依赖属性通过配置设值给该对象。

3.2、为什么需要注入

   通过编码的方式(setXxx),为成员变量进行赋值,存在耦合。

3.3、注入的方式

  1. set注入:其类必须提供对应 setter 方法。
  2. 构造器注入:利用构造器进行注入。

3.4、set注入

package com.domain;
 
/**
 * @author Xiao_Lin
 * @date 2021/2/4 15:57
 */
public class Person {
  private String username;
  private Integer password;
 
 
  @Override
  public String toString() {
    return "Person{" +
        "username='" + username + '\'' +
        ", password=" + password +
        '}';
  }
 
  public Person(String username, Integer password) {
    this.username = username;
    this.password = password;
  }
 
  public Person() {
  }
 
  public String getUsername() {
    return username;
  }
 
  public void setUsername(String username) {
    this.username = username;
  }
 
  public Integer getPassword() {
    return password;
  }
 
  public void setPassword(Integer password) {
    this.password = password;
  }
}
 
<bean id="person" class="com.domain.Person">
    <property name="username">
      <value>Xiao_Lin</value>
    </property>
    <property name="password">
      <value>123456</value>
    </property>
  </bean>
/**
  * 用于测试注入
  */
  @Test
  public void testDI(){
    ClassPathXmlApplicationContext application = new ClassPathXmlApplicationContext("applicationContext.xml");
    Person person = application.getBean("person", Person.class);
    System.out.println(person);
  }

3.4.1、set注入的原理图

   Spring通过底层调用对象属性对应的set方法完成对成员变量的赋值操作。


3.4.2、set注入详解

   针对不同的不同类型的成员变量,我们不可能一直是使用value标签,我们需要嵌套其他的标签,我们将成员变量可能的类型分类两大类:

  1. JDK内置类型。
  2. 用户自定义类型。

3.4.2.1、JDK内置类型
3.4.2.1.1、String+8种基本数据类型

   都直接使用value标签即可

<property name="password">
<value>123456</value>
</property>
3.4.2.1.2、数组类型

   对于数组类型,我们需要在配置文件中,使用list标签,表明是数组类型,嵌套value标签来进行赋值。

@Data
@AllArgsConstructor
@NoArgsConstructor
public class Person {
  private String[] emails;
}
 
 <property name="emails">
      <list>
        <value>124@qq.com</value>
        <value>456@163.com</value>
      </list>
    </property>
3.4.2.1.3、Set集合

   对于set集合类型,我们需要在配置文件中,使用set标签,表明是set集合类型,嵌套Set泛型中对应的标签来进行赋值。

@Data
@AllArgsConstructor
@NoArgsConstructor
public class Person {
 private Set<String> tels;
}
 
<property name="tels">
      <set>
        <value>123456</value>
        <value>456789</value>
        <value>13579</value>
      </set>
    </property>

  对于set集合由于我们规范了泛型为String,她是8种基本数据类型,所以在set标签中才嵌套value标签。如果没有规定泛型或者说是规定了其他的泛型,set嵌套的标签需要根据具体的情况来具体分析。

3.4.2.1.4、List集合

   对于List集合类型,我们需要在配置文件中,使用list标签,表明是List集合类型,嵌套List泛型中对应的标签来进行赋值。

   list便签中嵌套什么标签,取决于List集合中的泛型。

@Data
@AllArgsConstructor
@NoArgsConstructor
public class Person {
 private List<String> address;
}
<property name="address">
      <list>
        <value>sz</value>
        <value>sz</value>
        <value>gz</value>
      </list>
    </property>

3.4.2.1.5、Map集合

   对于Map集合,有一个内部类——Entry,所以我们在配置文件中需要使用的标签是用map标签来嵌套entry标签,里面是封装了一对键值对。我们使用key标签来表示键,里面嵌套键对应的标签,值要根据对应的类型来选择对应的标签。

@Data
@AllArgsConstructor
@NoArgsConstructor
public class Person {
 private Map<String,String> qq;
}
    <property name="qq">
      <map>
        <entry>
          <key><value>zs</value></key>
          <value>123456</value>
        </entry>
        <entry>
          <key><value>lisi</value></key>
          <value>456789</value>
        </entry>
      </map>
    </property>
3.4.2.1.6、Properties集合

   Properties类似是特殊的Map,他的keyvalue都必须是String类型。

   在配置文件中,我们使用props标签,里面嵌套prop标签,一个prop就是一个键值对,键写在key属性中,值写在标签内部。

@Data
@AllArgsConstructor
@NoArgsConstructor
public class Person {
 private Properties properties;
}
   <property name="properties">
      <props>
        <prop key="username">admin</prop>
        <prop key="password">123456</prop>
      </props>
    </property>
3.4.2.2、自定义类型
3.4.2.2.1、第一种注入方式
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Hobby {
  private String name;
}
    <property name="hobby">
      <bean class="com.domain.Hobby"/>
    </property>

   我们可以发现第一种注入方式其实就是在property标签里面写一个bean标签,他的劣势也很明显:


配置文件代码冗余,我有一万个类需要引用同一个对象的时候,我同一段代码需要写一万次。

被注入的对象,被多次创建,浪费(JVM)内存资源,因为我每写一个bean标签意味着就创建一个新的对象。

3.4.2.2.2、第二种注入方式

   鉴于第一种注入方式的缺点很明显,我们就需要改进,于是就有了第二种注入方式,这种方式是将我们需要注入的对象提前先创建一份出来,谁需要谁去引用即可。

 <bean> 
<property name="hobby">
      <ref bean="hobby"/>
    </property>
 </bean>
  <bean id="hobby" class="com.domain.Hobby">
    <property name="name">
      <value>admin</value>
    </property>
  </bean>

3.4.3、set注入的简化写法

3.4.3.1、基于属性的简化

JDK类型注入

3.4.3、set注入的简化写法

3.4.3.1、基于属性的简化

JDK类型注入


   我们可以使用value属性来简化value标签的值,但是只可以简化8种基本数据类型➕Stirng类型的值。

<!--以前的方式-->
<property name="name">
<value>Xiao_Lin</value>
</property>
 
<!--简化后的方式-->
<property name="name" value="Xiao_Lin"/>

用户自定义类型的注入

   我们可以使用ref属性来简化ref标签的值.

<!--以前的方式-->
<property name="hobby">
<ref bean="hobby"/>
</property>
 
<!--简化后的方式-->
<property name="hobby" ref="hobby"/>
3.4.3.2、基于p命名空间的简化

   我们可以发现,bean标签的很多值都是重复且冗余的,于是可以使用p命名空间来进行简化。

<!--内置数据类型-->
<bean id="person" class="com.domain.Person" p:username="zs" p:password="123456" />
 
<!--用户自定义类型-->
<bean id="hobbyBean" class="com.domain.Hobby"></bean>
 
<bean id="hobby" class="com.domain.Person" p:hobby-ref="hobbyBean"

3.5、构造注入

   Spring调用构造方法,通过配置文件为成员变量赋值。如果要使用构造注入,必须提供有参的构造方法。


  构造注入使用的标签是constructor-arg标签,一个构造参数就是一对constructor-arg标签。顺序和个数都必须和构造参数一样。


  当出现构造方法重载的时候,我们可以通过控制constructor-arg的个数来进行控制。如果出现构造参数个数相同的重载的时候(如第一个构造方法是给name赋值,第二个构造方法给type赋值),我们需要用type属性来指定类型。

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

4.1、控制反转(IOC)

   控制反转(IoC,Inversion of Control),是一个概念,是一种思想。指将传统上由程序代码直接操控的对象调用权交给容器,通过容器来实现对象的装配和管理。控制反转就是对对象控制权的转移,从程序代码本身反转到了外部容器。通过容器实现对象的创建,属性赋值,依赖的管理。


   简单来说控制反转就是把对于成员变量赋值的控制权,从代码中反转(转移)到Spring工厂和配置文件中完成。


   IoC 是一个概念,是一种思想,其实现方式多种多样。Spring 框架使用依赖注入(DI)实现 IoC。

4.2、依赖注入(DI)

   依赖:classA 类中含有 classB 的实例,在 classA 中调用 classB 的方法完成功能,即 classA对 classB 有依赖。


   依赖注入(Dependency Injection):当一个类需要另一个类时,就可以把另一个类作为本类的成员变量,最终通过Spring的配置文件进行注入(赋值)。简单来说就是指程序运行过程中,若需要调用另一个对象协助时,无须在代码中创建被调用者,而是依赖于外部容器,由外部容器创建后传递给程序。


   Spring 的依赖注入对调用者与被调用者几乎没有任何要求,完全支持对象之间依赖关系的管理。

4.3、总结

  Spring 容器是一个超级大工厂,负责创建、管理所有的 Java 对象,这些 Java 对象被称为 Bean。Spring 容器管理着容器中 Bean 之间的依赖关系,Spring 使用“依赖注入”的方式来管理 Bean 之间的依赖关系。使用 IoC 实现对象之间的解耦和。

五、Spring工厂

5.1、简单对象和复杂对象

5.1.1、简单对象

   简单对象指的就是可以直接通过调用构造方法(new)创建出来的对象。

5.1.2、复杂对象

   复杂对象指的就是不可以直接通过调用构造方法(new)创建出来的对象。比如JDBC的Connection对象、Mybatis的SqlSessionFactory对象。

5.2、Spring创建复杂对象的三种方式

5.2.1、FactoryBean

5.2.1.1、FactoryBean接口

   如果在applicationContext.xml配置文件中配置的class属性是FactoryBean接口的实现类,那么通过id属性获得的是这个类所创建的复杂对象(底层会调用重写的getObject()方法)。

public class MyFactoryBean implements FactoryBean<Connection> {
 
  // 用于书写创建复杂对象的代码,并且把复杂对象作为方法的返回值返回
  @Override
  public Connection getObject() throws Exception {
    Class.forName("com.mysql.jdbc.Driver");
    Connection connection = DriverManager.getConnection("jdbc:mysql:///javaweb?characterEncoding=utf-8&useSSL=false","root","123456");
    return connection;
  }
 
  // 返回所创建的复杂对象的Class对象
  @Override
  public Class<?> getObjectType() {
    return Connection.class;
  }
 
  // 配置是否是单例模式
  @Override
  public boolean isSingleton() {
    return false;
  }
 
  <bean id="factoryBean" class="com.test.MyFactoryBean">
  /**
  * 用于测试factoryBean
  */
  @Test
  public void testFactoryBean(){
    ClassPathXmlApplicationContext ctr = new ClassPathXmlApplicationContext("/applicationContext.xml");
    Connection conn  = (Connection) ctr.getBean("factoryBean");
    System.out.println(conn);
  }
5.2.1.2、FactoryBean接口的细节
  1. 如果我不想获得创建的复杂对象(Connection),想获得普通的简单对象(FactoryBean),我们仅仅只需在getBean(id)的前面加一个&即可。
import java.sql.Connection;
import java.sql.DriverManager;
import org.springframework.beans.factory.FactoryBean;
 
/**
 * @Description
 * @Author XiaoLin
 * @Date 2021/2/24 19:47
 */
public class MyFactoryBean implements FactoryBean<Connection> {
 
  @Override
  public Connection getObject() throws Exception {
    Class.forName("com.mysql.jdbc.Driver");
    Connection connection = DriverManager
        .getConnection("jdbc:mysql:///javaweb?characterEncoding=utf-8&useSSL=false", "root",
            "1101121833");
    return connection;
  }
 
  @Override
  public Class<?> getObjectType() {
    return Connection.class;
  }
 
  @Override
  public boolean isSingleton() {
    return true;
  }
}
<?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="factoryBean" class="MyFactoryBean">
 
  </bean>
</beans>
import java.sql.Connection;
import org.junit.Test;
import org.springframework.beans.factory.FactoryBean;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
 
/**
 * @Description
 * @Author XiaoLin
 * @Date 2021/2/24 19:50
 */
public class MyFactoryBeanTest {
 
  /**
  * 用于测试复杂类型对象的创建
  */
  @Test
  public void testMyFactoryBeanTest(){
    ApplicationContext applicationContext = new ClassPathXmlApplicationContext("/applicationContext.xml");
    MyFactoryBean connection = (MyFactoryBean)applicationContext.getBean("&factoryBean");// 获取普通的简单对象FactoryBean,不获取复杂的Connection对象
    System.out.println(connection);
  }
}
  1. isSingleton()方法,如果返回值为true时,他只会创建一个对象,返回false时会创建多个对象,一般根据对象的特点来判断返回true(SqlSessionFactory)还是false(Connection)。
5.2.1.3、BeanFactory实现原理图


5.2.1.4、FactoryBean总结

   FactoryBean是Spring中用于创建复杂对象的一种方式 也是Spring原生提供的,后面框架整合会大量运用。

5.2.2、实例工厂

5.2.2.1、FactoryBean的弊端

   使用FactoryBean的话有Spring的侵入,实现了FactoryBean接口,一旦离开了Spring,整个类都无法使用。

5.2.2.2、实例工厂的使用
// 实例工厂
public class ConnectionFactory {
  public Connection getConnection(){
    Connection conn = null;
    try {
      Class.forName("com.mysql.jdbc.Driver");
      conn = DriverManager.getConnection("jdbc:mysql:///javaweb?characterEncoding=utf-8&useSSL=false","root","1101121833");
    } catch (ClassNotFoundException | SQLException e) {
      e.printStackTrace();
    }
   return conn;
  }
}
  <bean id="connFactory" class="com.factory.ConnectionFactory"/>
  <bean id="conn" factory-bean="connFactory" factory-method="getConnection"/>

5.2.3、静态工厂

   前面我们学了实例工厂,由于实例工厂的getConnection()方法是实例方法,需要由对象来调用,所以需要先创建对象然后再通过对象来调用方法。


   而静态工厂由于getConnection()方法是静态方法,不需要由对象来调用,直接通过类进行调用。这就是实例工厂与静态工厂最大的区别。

public class ConnectionStaticBeanFactory {
  public static Connection getConnection(){
    Connection conn = null;
    try {
      Class.forName("com.mysql.jdbc.Driver");
      conn = DriverManager.getConnection("jdbc:mysql:///javaweb?characterEncoding=utf-8&useSSL=false","root","1101121833");
    } catch (ClassNotFoundException | SQLException e) {
      e.printStackTrace();
    }
    return conn;
  }
}
 <bean id="staticBeanFactory" class="com.factory.ConnectionStaticBeanFactory" factory-method="getConnection"/>

5.3、创建对象的细节

5.3.1、控制简单对象的创建次数

  控制简单对象的创建次数我们只需要配置bean标签的scope属性值即可。他常用的有两个值:

  1. singleton:默认为单例模式,只会创建一个简单对象。
  2. prototype:每次都会创建一个新的对象。
<bean id="person" scope="singleton(prototype)"  class="com.doamin.Person"/>

5.3.2、控制复杂对象的创建次数

  FactoryBean接口的isSingleton()方法的返回值来进行控制(如果没有isSingleton()方法,那么还是通过scope属性来进行控制):

  1. 返回true:只会创建一次。
  2. 返回false:每一次都会创建一个新的对象。

5.3.3、控制对象创建次数的原因

 可以被大家共享的对象(SqlSessionFactory、各种Dao、Service)可以只创建一次,不可以被大家共享的对象(Connection、SqlSession、Controller)可以创建多次,控制对象创建次数的最大好处是可以节省不必要的内存浪费。

5.4、对象的生命周期

   生命周期指的是一个对象的创建、存活、消亡的一个完整过程。由Spring来负责对象的创建、存活、销毁。了解生命周期,有利于我们使用好Spring为我们创建的对象。

   Spring帮我们创建的对象有三个阶段:

  1. 创建阶段
  2. 初始化阶段
  3. 销毁阶段

5.4.1、创建阶段

当 scope = “singleton” 时,Spring工厂创建的同时,对象会随之创建。如果我们不想在Spring工厂创建的同时创建,想在获取对象的时候创建,只需在配置文件的bean标签添加一个lazy-init = true即可。


当 scope = “prototype” 时,Spring工厂会在获取对象的同时创建对象。

5.4.2、初始化阶段

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


 初始化方法是由程序员根据需求提供初始化方法,由Spring工厂调用,最终完成初始化操作。他有两种调用的方式:


实现InitializingBean接口(有Spring侵入的问题)。

提供一个普通方法并修改配置文件。

5.4.2.1、InitializingBean接口
// 这个就是初始化方法,做一些初始化操作,Spring会进行调用 
@Override
  public void afterPropertiesSet() throws Exception {
// 初始化操作
  }
5.4.2.2、提供普通方法

   由于实现InitializingBean接口存在Spring侵入的问题,所以Spring提供了另一个方法给我们进行初始化操作,那就是提供一个普通的方法,然后去配置文件中增加init-method="方法名"熟悉的配置即可。

  public void init(){
    System.out.println("我是初始化方法");
  }
<bean id="product" class="com.domain.Product" init-method="init"/>
5.4.2.3、注意

   如果一个对象既实现了InitializingBean接口同时又提供了普通的初始化方法,那么两个初始化方法都会执行,先执行的是InitializingBean接口的方法,再执行普通的初始化方法。


   在执行初始化操作之前,会先进行属性的注入,注入在前,初始化在后。


   初始化需要做的操作一般是数据库、IO、网络操作。

5.4.3、销毁阶段

   在工厂关闭之前,Spring会在销毁对象前,会调用对象的销毁方法,完成销毁操作。

  销毁方法是程序员根据需求定义销毁方法,由Spring工厂调用销毁方法,完成销毁操作。他也有两种方法:

  1. 实现DisposableBean接口。
  2. 定义普通的销毁方法在配置文件中配置。
5.4.3.1、实现DisposableBean接口
public class Product implements InitializingBean, DisposableBean {
      @Override
  public void destroy() throws Exception {
    System.out.println("销毁操作,资源释放");
  }
}
5.4.3.2、定义普通方法
public class Product implements InitializingBean, DisposableBean { 
public void MyDestory(){
    System.out.println("自己定义的销毁方法");
  }
}
 <bean id="product" class="com.domain.Product" destroy-method="MyDestory"/>
5.4.3.3、注意
  1. 销毁方法的操作只适用于scope=“singleton”。
  2. 销毁操作主要指的是一些资源的释放操作。

5.5、Spring整合配置文件

   一般来说像数据库的一些配置信息我们都不会直接写在代码里面,会将他们抽取出来成一个配置文件,再利用Spring进行注入。我们只需要加入一个标签即可完成。

<!--告诉Spring你的db.properties在哪里-->  
<context:property-placeholder location="classpath:/db.properties"/>
 
<!--用$(db.properties中的key)来进行取值-->
<bean id="conn" class="com.factory.BeanFactory">
<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>

六、自定义类型转换器

6.1、类型转换器

   我们写在Spring配置文件中赋值的值都是String类型的,但是我们的实体类是Interger类型的值,按照语法来说,String类型的值是不可以直接赋值给Integer类型的,但是为什么能直接赋值呢?


  因为Spring内部帮我们进行了自动的类型转换,Spring通过类型转换器将配置文件中字符串类型的数据,转换成了对象中成员变量对应类型的数据,从而完成注入。

6.2、自定义类型转换器

6.2.1、问题引入

@Data
@AllArgsConstructor
@NoArgsConstructor
public class People {
  private String name;
  private Date birthday;
 
}
  <bean id="people" class="com.domain.People">
    <property name="name" value="XiaoLin"/>
    <property name="birthday" value="2021-2-6"/>
  </bean>

   我们运行代码之后发现报错了,说String类型的值不可以转化为Date类型的值,说明Spring内部没有这个转换器。

Caused by: java.lang.IllegalStateException: Cannot convert value of type 'java.lang.String' to required type 'java.util.Date' for property 'birthday': no matching editors or conversion strategy found
at org.springframework.beans.TypeConverterDelegate.convertIfNecessary(TypeConverterDelegate.java:262)
at org.springframework.beans.AbstractNestablePropertyAccessor.convertIfNecessary(AbstractNestablePropertyAccessor.java:585)
... 39 more

  Spring内部没有提供特定类型转换器时,而程序员在应用的过程中又需要使用,所以需要程序员自己定义类型转换器。

6.2.2、代码实现

 自定义类型转换器我们分为两步实现:

  1. 实现Converter<转换前的类型, 转换后的类型>接口,并且重写里面的方法。
  2. 在配置文件中进行转换器的注册
public class MyConverter implements Converter<String, Date> {// 他有两个泛型,一个是转换前的类型,另一个是转换后的类型
 
  /*
  convert方法的作用是将String->Date
  parm:source代表的是配置文件中需要转换的内容
  return:把转换好的值作为返回值,Spring会自动为属性赋值
   */
  @Override
  public Date convert(String source) {
    SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
    Date parse = null;
    try {
      parse = sdf.parse(source);
    } catch (ParseException e) {
      e.printStackTrace();
    }
    return parse;
  }
}
 
<!--  类型转换器的注册,告诉Spring我们所创建的MyConverter类是类型转换器类,
      Spring提供了一个类ConversionServiceFactoryBean来完成类型转换器的注册-->
  <bean id="conversionService" class="org.springframework.context.support.ConversionServiceFactoryBean">
    <property name="converters" >
      <set>
<!--        注册类型转换器-->
        <ref bean="myConvert"/>
      </set>
    </property>
  </bean>
 

6.2.3、注意细节

创建ConversionServiceFactoryBean标签的id必须为conversionService,不然不生效。

其实Spring已经内置了日期类型的转换器,但是他只支持以/作为分隔符的字符串的格式:2021/2/6,不支持其他的格式,如果你的字符串格式已经是这种,就无需再写自定义类型转换器。

七、BeanPostProcessor

7.1、概述

   BeanPostProcessor称为后置处理Bean,他的作用是对Spring工厂所创建的对象进行二次加工,他是AOP的底层实现,他本质上是一个接口。

7.2、BeanPostProcessor分析

   他是一个接口,要实现他的两个方法:


Object postProcessBeforeInitialization(Object bean,String beanName):他的作用是在Spring创建完对象后,在进行初始化方法之前,

通过参数获取到Spring创建好的对象,执行postProcessBeforeInitialization方法进行加工,最终通过返回值返回这个加工好的对象给Spring。

Object postProcessAfterInitialization(Object bean,Stirng beanName):Spring执行完对象的初始化操作之后,运行postProcessAfterInitialization方法进行加工,通过参数获取Spring创建好的对象,最终通过返回值返回给Spring。

   在日常的开发中,我们很少去处理Spring的初始化操作,所以没有必要区分前后,所以一般只需要实现其中一个方法即可,且BeanPostProcessor会对Spring工厂中的所有对象进行加工。


Spring知识点,这篇搞定(二)

https://developer.aliyun.com/article/1583471?spm=a2c6h.13148508.setting.28.3cca4f0eq8AZ11


相关实践学习
如何在云端创建MySQL数据库
开始实验后,系统会自动创建一台自建MySQL的 源数据库 ECS 实例和一台 目标数据库 RDS。
全面了解阿里云能为你做什么
阿里云在全球各地部署高效节能的绿色数据中心,利用清洁计算为万物互联的新世界提供源源不断的能源动力,目前开服的区域包括中国(华北、华东、华南、香港)、新加坡、美国(美东、美西)、欧洲、中东、澳大利亚、日本。目前阿里云的产品涵盖弹性计算、数据库、存储与CDN、分析与搜索、云通信、网络、管理与监控、应用服务、互联网中间件、移动服务、视频服务等。通过本课程,来了解阿里云能够为你的业务带来哪些帮助 &nbsp; &nbsp; 相关的阿里云产品:云服务器ECS 云服务器 ECS(Elastic Compute Service)是一种弹性可伸缩的计算服务,助您降低 IT 成本,提升运维效率,使您更专注于核心业务创新。产品详情: https://www.aliyun.com/product/ecs
相关文章
|
XML 缓存 前端开发
Spring入门系列:浅析知识点
本文介绍了学习Spring源码前需要掌握的核心知识点,包括IOC、AOP、Bean生命周期、初始化和Transaction事务。通过Hello World示例,讲解了如何使用Spring,并指出了深入了解Spring内部机制的方向。
|
6月前
|
监控 Java 应用服务中间件
Spring Boot 源码面试知识点
【5月更文挑战第12天】Spring Boot 是一个强大且广泛使用的框架,旨在简化 Spring 应用程序的开发过程。深入了解 Spring Boot 的源码,有助于开发者更好地使用和定制这个框架。以下是一些关键的知识点:
93 6
|
1月前
|
XML Java 数据格式
spring复习
spring复习
11 0
|
3月前
|
前端开发 Java 数据库连接
|
5月前
|
XML 安全 Java
Spring 基础知识学习
Spring 基础知识学习
|
XML 存储 缓存
史上最全Spring相关面试题(三)
史上最全Spring相关面试题(三)
|
XML Java 关系型数据库
史上最全Spring相关面试题(五)
史上最全Spring相关面试题(五)
|
6月前
|
XML Java 数据格式
Spring学习?这一篇文章就够,史上最全!
Spring学习?这一篇文章就够,史上最全!
|
设计模式 SQL 前端开发
史上最全Spring相关面试题(一)
史上最全Spring相关面试题(一)
|
设计模式 供应链 安全
史上最全Spring相关面试题(二)
史上最全Spring相关面试题(二)
下一篇
无影云桌面