Spring01 - 工厂篇

news/2024/12/26 20:43:07 标签: java, 框架, spring

Spring入门(上)-工厂篇

文章目录

  • Spring入门(上)-工厂篇
    • 一:引言
      • 1:EJB存在的问题
      • 2:什么是Spring
      • 3:设计模式和反射工厂
    • 二:第一个spring程序
      • 1:环境搭建
      • 2:核心API - ApplicationContext
        • 2.1:核心API和两大实现类
        • 2.2:低级容器和高级容器
      • 3:程序开发和使用
        • 3.1:使用流程
        • 3.2:常见方法
        • 3.3:配置文件中的注意事项
      • 4:底层实现原理【简易版本】
      • 5:Spring和日志框架整合
    • 三:注入Injection
      • 1:注入概述
      • 2:set注入
        • 2.1:JDK内置类型
        • 2.2:自定义类型
        • 2.3:简化写法
      • 3:构造器注入
        • 3.1:使用方法
        • 3.2:构造方法重载问题
      • 4:注入总结
    • 四:控制反转(IOC)和依赖注入(DI)
      • 1:控制反转IOC
      • 2:依赖注入DI
    • 五:复杂对象的创建
      • 1:什么是复杂对象
      • 2:创建复杂对象的三种方式
        • 2.1:FactoryBean接口
        • 2.2:实例工厂
        • 2.3:静态工厂
    • 六:控制对象的创建次数
      • 1:如何控制对象的创建次数
        • 1.1:简单对象的创建次数
        • 1.2:复杂对象的创建次数
      • 2:为什么要控制对象的创建次数
    • 七:对象的生命周期
      • 1:创建阶段
      • 2:初始化阶段
      • 3:销毁阶段
      • 4:总结一下(面试重点)
    • 八:配置文件参数化
      • 1:配置文件参数化
      • 2:开发步骤
    • 九:自定义类型转换器
      • 1:类型转换器
      • 2:自定义类型转换器
    • 十:后置处理Bean
      • 1:BeanPostProcessor概述和原理
      • 2:开发步骤

一:引言

1:EJB存在的问题

运行环境苛刻 + 代码移植性性差 = 重量级框架

在这里插入图片描述

2:什么是Spring

spring = 轻量级的 + JavaEE解决方案的框架,没有引入新的技术,但是整合了众多的优秀设计模式

  • 所谓轻量级,是针对EJB而言:对于运行环境是没有要求的【开源tomcat, jetty等】,同时代码移植性高【 不用实现额外的移植接口】

  • 所谓JAVAEE的通用解决方案:是Spring的整合内容包含了JAVA分层的各个阶段:而不像struts2只整合Controller层,Mybatis只整合DAO层

在这里插入图片描述

3:设计模式和反射工厂

什么是设计模式

  • 狭义:GOF提出的23种设计模式:创建型 + 结构型 + 行为型
  • 广义:面向对象设计中,解决特定问题的经典代码

spring中常用的四个设计模式是:工厂模式【工厂 + 反射 = spring核心】,模板方法模式【可扩展性】,代理模式【AOP】,策略模式【子类的选择】

什么是工厂模式

通过工厂类创建对象,而不是通过显式的new()方式进行对象的创建,这样可以做到解耦合【解除代码之间的强关联关系】

因为一旦代码中出现强耦合,就会不利于代码的维护

java">UserService userSerivce = new UserServiceImpl(); // 强耦合

而如何解耦合呢,这时候就要想到对象的创建方式除了使用显式的new()创建还有什么方式了 -> 反射,所以,可以通过反射工厂来进行对象的创建实现解耦

java">public class MyBeanFactory {

    /**
     * 创建person对象
     * <p>
     *     对象的创建方式:
     *     (1)显式创建:Person person = new Person()
     *     (2) 隐式创建:反射 -> class.forName(全限定类型名) + newInstance()
     * </p>
     * @return person对象实例
     */
    public static Person getPersonInstance() {
        
        Person person = null;
        
        try {
            Class clazz = Class.forName("com.cui.commonboot.suns_spring.entity.Person");
            person = (Person) clazz.newInstance();
        } catch (Exception e) {
            e.printStackTrace();
        }
        
        return person;
    }
}

但是上述方式显然还是有两个问题:

(1)如果有多个类要进行工厂创建,是不是要写很多个这样的getXXXInstance()?

(2)这里面的Class.forName("com.cui.commonboot.suns_spring.entity.Person");并没有实现解耦,如果要改对应的类还是要到代码里去改

针对问题一:可以发现所有的类的工厂创建流程都是一样的,所以可以使用一个通用的方式进行创建【返回Object】

针对问题二:可以使用外部配置文件进行加载,然后使用java.util.Properties获取外部配置文件信息,这样就可以做到解耦了

java">// ================== 通用反射工厂 =================
package com.cui.commonboot.suns_spring.config;

import java.io.IOException;
import java.io.InputStream;
import java.util.Properties;

/**
 * <p>
 * 功能描述:my bean factory
 * </p>
 *
 * @author cui haida
 * @date 2024/01/06/9:32
 */
public class MyBeanFactory {
    // 加载配置
    // 将properties配置文件中的内容读取到Properties中
    private static Properties env = null;
    // 因为io比较耗时,所以放到静态代码块中
    static {
        try {
            // 加载配置文件
            InputStream in = MyBeanFactory.class.getResourceAsStream("/applicationContext.properties");
            // 装载到env中
            env.load(in);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    /**
     * get bean from factory
     * @param name bean name
     * @return bean instance
     */
    public static Object getBean(String name) {
        Object bean = null;
        try {
            // 获取指定的类
            String property = env.getProperty(name);
            Class clazz = Class.forName(property);
            bean = clazz.newInstance();
        } catch (Exception e) {
            e.printStackTrace();
        }
        return bean;
    }
}
# ==================== applicationContext.properties ===============
person=com.cui.commonboot.suns_spring.entity.Person

spring_138">二:第一个spring程序

1:环境搭建

maven依赖

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

配置文件

Spring对于配置文件的放置位置和命名没有任何的要求,一般放在resource下

日后在应用spring框架时,需要进行配置文件的路径的设置

在这里插入图片描述

2:核心API - ApplicationContext

2.1:核心API和两大实现类

作用是:Spring提供的这个工厂可以用于对象的创建

好处:解耦合

特点:是一个接口,这样可以完美的屏蔽实现的差异

  • web环境:XmlWebApplicationContext
  • 非web环境:ClassPathXmlApplicationContext(main, Junit)

在这里插入图片描述

ApplicationContext是一个重量级资源【工厂的对象会占用大量的内存空间】

所以spring不会频繁的创建对象,一个应用只会创建一个工厂对象避免占用大量内存空间

同时ApplicationContext内部含有大量的独占锁,所以是线程安全的,支持多线程并发访问

2.2:低级容器和高级容器

在这里插入图片描述

在 Spring 中,有两种 IoC 容器:BeanFactoryApplicationContext。(ApplicationContext是BeanFactory的子类)

在这里插入图片描述

  • BeanFactory:Spring 实例化、配置和管理对象的最基本接口。

在这里插入图片描述

java">public interface BeanFactory {
    /**
     * 用来引用一个实例,或把它和工厂产生的Bean区分开
     * 如果一个FactoryBean的名字为a,那么,&a会得到那个Factory
     */
    String FACTORY_BEAN_PREFIX = "&";
    /*
     * 四个不同形式的getBean方法,获取实例
     */
    Object getBean(String name) throws BeansException;
    <T> T getBean(String name, Class<T> requiredType) throws BeansException;
    <T> T getBean(Class<T> requiredType) throws BeansException;
    Object getBean(String name, Object... args) throws BeansException;
    
 	// 根据名称判断bean是否存在
    boolean containsBean(String name);
	// 是否为单实例Bean
    boolean isSingleton(String name) throws NoSuchBeanDefinitionException;
	// 是否为原型(多实例)
    boolean isPrototype(String name) throws NoSuchBeanDefinitionException;
	// 名称、类型是否匹配
    boolean isTypeMatch(String name, Class<?> targetType) throws NoSuchBeanDefinitionException;
	// 获取类型
    Class<?> getType(String name) throws NoSuchBeanDefinitionException;
	// 根据实例的名字获取实例的别名
    String[] getAliases(String name);
}
  • ApplicationContext以更加面向框架的风格增强了BeanFactory,并提供了一些适用于企业应用程序的特性。
    • 默认初始化所有的Singleton,也可以通过配置取消预初始化。

    • 继承MessageSource,因此支持国际化。

    • 资源访问,比如访问URL和文件。

    • 事件传播特性,即支持aop特性。

    • 同时加载多个配置文件。

    • 以声明式方式启动并创建Spring容器。

3:程序开发和使用

3.1:使用流程

只有三个步骤:创建类型 -> 配置文件的配置【xxx.xml】-> 通过工厂类【ApplicationContext的两个实现类】,获得对象

<?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">

    <!--
        id:名字 - 唯一性
        class属性:配置全限定类名
    -->
    <bean id="person" class="org.example.entity.Person"/>

</beans>
java">@org.junit.Test
    public void test() {
    // 通过工厂读取配置文件,因为这里是非web环境,所以是ClassPathXmlApplicationContext实现类工厂
    ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("/bean.xml");
    // 通过工厂加载bean后获取对象实例,参数是配置文件中的id值
    Person person = (Person) ctx.getBean("person");

    System.out.println("person is: " + person);
}
3.2:常见方法
java">// getBean1: 通过工厂加载bean后获取对象实例,参数是配置文件中的id值
Person person = (Person) ctx.getBean("person");
// getBean2: 可以即指定id, 又指定返回值类型,这样可以不用强转
Person person0 = ctx.getBean("person", Person.class);
// getBean3:当前的配置文件中,如果只有一个类型是person的,可以只使用class作为参数,不指定id
Person person1 = ctx.getBean(Person.class);

// 获取所有的bean的id值列表
String[] allBeanNames = ctx.getBeanDefinitionNames();
for (String beanName : allBeanNames) {
    System.out.println(beanName);
}

// 获取指定类型的所有的bean的id值列表
String[] pointTypeAllBeanNames = ctx.getBeanNamesForType(Person.class);
for (String beanName : pointTypeAllBeanNames) {
    System.out.println(beanName);
}

// 判断是否存在指定id值的bean, name属性不生效
boolean flag = ctx.containsBeanDefinition("person");
System.out.println("flag is: " + flag);

// 判断是否存在指定id值的bean, name属性也生效
boolean flagWithName = ctx.containsBean("p");
System.out.println("flag is: " + flagWithName);
3.3:配置文件中的注意事项

只配置class属性

<bean class="org.example.entity.Person"></bean>

如果这个bean主需要使用一次,那么就可以省略id值,反之如果bean会使用多次或者会被其他bean引用需要设置id值

name属性:别名,可重复【逗号分割多个别名】

<bean id="person" name="p, p1, p2" class="org.example.entity.Person"/>

别名可以指定多个,但是id属性只能指定一个【你可以有多个小名,但是只能有一个大名】

ctx.containsBeanDefinition(String beanName)传入别名的时候识别不了,ctx.containsBean(name)可以识别

4:底层实现原理【简易版本】

这个是简易版本,不代表底层具体实现逻辑

在这里插入图片描述

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

🎉 理论上程序的所有的对象都会交给spring工厂创建,但是实际上实体对象一般不会交给Spring管理创建,因为要操作数据库,所以一般会由持久层框架进行创建

5:Spring和日志框架整合

spring和日志框架进行整合,可以十分方便的在控制台输出运行过程中的重要的信息

<!-- spring整合日志 -->
<dependency>
    <groupId>log4j</groupId>
    <artifactId>log4j</artifactId>
    <version>1.2.17</version>
</dependency>
<!-- 日志门面:将默认的logback和log4j2干掉,进而使得spring5可以整合支持log4j -->
<dependency>
    <groupId>org.slf4j</groupId>
    <artifactId>slf4j-log4j12</artifactId>
    <version>1.7.25</version>
</dependency>

然后引入log4j.properties

# 配置根
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

1:注入概述

所谓注入就是通过spring工厂和配置文件对创建的对象的成员变量赋值的操作

而常规的赋值操作是通过硬编码的方式,存在耦合

java">// 常规的变量赋值方法是通过set方法
Person person = (Person) ctx.getBean("person");
person.setId(1);
person.setName("cui");

spring提供的注入方式可以消除这种硬编码,避免耦合

<bean id="person" name="p, p1, p2" class="org.example.entity.Person">
    <property name="id">
        <value>2</value>
    </property>
    <property name="name">
        <value>cui</value>
    </property>
</bean>

在这里插入图片描述

2:set注入

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

在这里插入图片描述

2.1:JDK内置类型

8种基本类型 + String

  • <value>

  • <property name="id">
        <value>2</value>
    </property>
    <property name="name">
        <value>cui</value>
    </property>
    

数组

  • <list> - <value> , 里面也可以是其他类型的

  • <property name="emails">
        <list>
            <value>111111@qq.com</value>
            <value>123456@123.com</value>
        </list>
    </property>
    

set集合

  • 无序性,唯一性

  • <set> - <value>/<ref>.....

  • <property name="emails">
        <set>
            <value>111111@qq.com</value>
            <value>123456@123.com</value>
        </set>
    </property>
    

list集合

  • 有序性,可重复
  • <list> - <value>/<ref>.....

map

  • <map> - <entry> - <key> & <value>

  • <property name="emails">
        <map>
            <entry>
                <key>张三</key>
                <value>111111@qq.com</value>
            </entry>
            <entry>
                <key>李四</key>
                <value>111111@qq.com</value>
            </entry>
        </map>
    </property>
    

properties

  • 是一种特殊的map[key = string, value = string]

  • <property name="p">
        <props>
            <prop key="张三">value1</prop>
            <prop key="李四">value2</prop>
        </props>
    </property>
    
2.2:自定义类型

第一种方式

为成员变量提供get/set方法

配置文件中进行注入赋值【bean】

<bean id="userService" class="org.example.service.impl.UserServiceImpl">
    <property name="userDao">
        <bean class="org.example.Dao.impl.UserDaoImpl"/>
    </property>
</bean>

但是这种方式会导致:配置文件代码冗余 & 被注入的对象会被多次创建,导致大大的浪费内存资源

第二种方式

<bean id="userDao" class="org.example.Dao.impl.UserDaoImpl"></bean>

<bean id="userService" class="org.example.service.impl.UserServiceImpl">
    <property name="userDao">
        <ref bean="userDao"/>
    </property>
</bean>
2.3:简化写法

基于属性的简化

<bean id="student" class="springProject.Student">
    <!-- 通过property标签给各个成员变量赋值。name:成员变量名。value:数值类型的值。ref:引用类型的值。 -->
    <property name="stuNo" value="2"></property>
    <property name="userDao" ref="userDao"></property>
</bean>

p方式简化

需要引入p命名空间xmlns:p="http://www.springframework.org/schema/p"

然后在bean标签内使用p:成员变量名这种方式来赋值

<!-- 
简单类型:p:属性名="属性值"
引用类型(除了String外):p:属性名-ref="引用的id"
注意多个 p赋值的时候 要有空格。 -->
<bean id="student" class="springProject.Student" p:userDao-ref="userDao" p:stuNo="123"></bean>

3:构造器注入

3.1:使用方法

Spring调用构造方法,通过配置文件,为成员变量赋值

  • 使用这种方式的前提是需要bean类提供有参构造的方法
  • 如果不写index,将使用默认顺序
<bean id="student" class="springProject.Student">
    <!-- index:构造方法中成员变量的顺序,从0开始。value、ref的含义同property。 -->
    <constructor-arg value="3" index="0" ></constructor-arg>
    <constructor-arg value="2" index="2" ></constructor-arg>
    <constructor-arg value="zs" index="1"></constructor-arg>
</bean>
3.2:构造方法重载问题
  • 参数个数不同的时候,可以通过控制constructor-arg标签个数进行区分
  • 参数个数相同的时候,可以指定type属性,然后根据type属性进行区分
<bean id="person2" class="org.example.entity.Person">
    <constructor-arg type="int" value="15"/>
</bean>

<bean id="person3" class="org.example.entity.Person">
    <constructor-arg value="haha"/>
</bean>

4:注入总结

在这里插入图片描述

在应用中大多数用的都是set注入,因为构造注入有重载问题需要注意和解决,同时Spring底层大量应用了set注入

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

1:控制反转IOC

  • IOC是Spring核心特性之一,另一个核心特性是AOP
  • IOC(Inverse of Controll) -> 解耦合,底层是工厂设计模式
  • IoC 意味着将你设计好的对象交给容器控制

谁控制谁,控制什么

传统 JavaSE 程序设计,我们直接在对象内部通过 new 进行创建对象,是程序主动去创建依赖对象;

而 IoC 是有专门一个容器来创建这些对象,即由 IoC 容器来控制对象的创建;

  • 谁控制谁?当然是 IoC 容器控制了对象
  • 控制什么?那就是主要控制了外部资源获取(对于成员变量赋值的控制权,不只是对象包括比如文件等)。

为何是反转,哪些方面反转了

传统应用程序是由我们自己在对象中主动控制去直接获取依赖对象,也就是正转;而反转则是由容器来帮忙创建及注入依赖对象;

  • 为何是反转?因为由容器帮我们查找及注入依赖对象,对象只是被动的接受依赖对象,所以是反转;
  • 哪些方面反转了?依赖对象的获取被反转了

在这里插入图片描述

2:依赖注入DI

DI(Dependency Injection) -> 依赖注入是 IoC 的最常见形式。

注入:通过Spring的工厂和配置文件,为对象(bean,组件)的成员变量进行复制

依赖注入:当一个类需要另一个类的时候,就意味着依赖,依赖出现依赖,就可以将另一个类作为本类的成员变量,最终通过Spring配置文件进行注入

在这里插入图片描述

五:复杂对象的创建

1:什么是复杂对象

所谓复杂对象,就是指不能直接通过new方法创建的对象

在这里插入图片描述

2:创建复杂对象的三种方式

2.1:FactoryBean接口

实现FactoryBean接口,然后Spring配置文件配置即可完成

在后续学习Spring整合其他框架的时候,底层会大量的运用到这个接口

FactoryBean介绍

FactroyBean接口共有三个需要实现的方法来辅助我们创建复杂的对象,三个方法分别是:

java">public interface FactoryBean<T> {
    
    // 用于书写创建复杂对象的代码,并将复杂对象作为方法的返回值返回
    @Nullable
    T getObject() throws Exception;

    // 用于返回所创建的复杂的对象的class对象
    @Nullable
    Class<?> getObjectType();

    // 如果该对象只需要创建一次,将返回true[单例为true]
    // 多例为false
    // 默认是单例
    default boolean isSingleton() {
        return true;
    }
}

在这里插入图片描述

<bean id="conn" class="com.xxx.xxx.ConnectionFactoryBean"></bean>

FactoryBean获取对象

虽然上面的Spring配置文件中创建复杂对象的配置方法和简单对象的配置方法一致,但是本质却有很大的不同

在这里插入图片描述

其他细节

  1. 如果就想获得FactoryBean类型的对象, 可以使用getBean("&conn")【&标记】,此时获得的就是ConnectionFactoryBean对象
  2. DI的体会和运用
<bean id="conn" class="com.xxx.xxx.ConnectionFactoryBean">
    <!-- 作为属性,DI -->
    <property name="driverClassName" value="com.mysql.jdbc.Driver"/>
    <property name="url" value="jdbc:mysql://localhost:3306/cui?useSSL=false"/>
    <property name="userName" value="root"/>
    <property name="password" value="314159"/>
</bean>

实现原理【思想层面】

接口 + 反射,什么都能做 -> FactoryBean的核心原理就是接口回调

在这里插入图片描述

  1. 根据conn获得<bean>标签相关信息,并判断后面指定的全限定类名的类是不是属于FactoryBean的子类?
    1. 如果是:进行下一步
    2. 如果不是:说明是简单对象,直接底层new()
  2. 发现是子类,就要实现FactoryBean的getObject()方法,这个方法返回的内容就是ctx.getBean(xxx)返回的内容
2.2:实例工厂

在有些老的项目中,有一些已经写好的复杂对象的创建方法没有整合到Spring容器中,此时就需要实例工厂和Spring的整合了

java">// 已经写好的复杂对象的创建方法,没有实现FactoryBean接口
public class ConnectionFactory {
    // 数据库连接
    public Connection getConnection() {
        Connection conn = null;
        try {
            Class.forName("com.mysql.jdbc.Driver");
            conn = DriverMannger.getConnection("jdbc:mysql://localhost:3306/cui?useSSL=false", "root", "314259");
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

此时就可以通过配置文件将其加入到Spring容器的控制范围

<bean id="connFactory" class="com.xxx.xxx.ConnectionFactory"></bean>

<!-- factory-bean目的是注册connFactory为FactoryBean结构  -->
<!-- factory-method指明对标getObject的是那个方法  -->
<bean id="conn" factory-bean="connFactory" factory-method="getConnection"></bean>
2.3:静态工厂

静态工厂和实例工厂的唯一不同之处就是创建复杂对象的那个方法是静态的

java">// 已经写好的复杂对象的创建方法,没有实现FactoryBean接口
public class ConnectionFactory {
    // 静态的
    public static Connection getConnection() {
        Connection conn = null;
        try {
            Class.forName("com.mysql.jdbc.Driver");
            conn = DriverMannger.getConnection("jdbc:mysql://localhost:3306/cui?useSSL=false", "root", "314259");
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
<!-- 类名.静态方法名() -->
<bean id="conn" class="com.xxx.xxx.ConnectionFactory" factory-method="getConnection"></bean>

六:控制对象的创建次数

1:如何控制对象的创建次数

1.1:简单对象的创建次数
<bean id="person" scope="singleton|prototype" class="xxx.Person"></bean>

singleton:只会创建一次简单对象【默认就是单例的】

prototype:每一次都会创建新的对象

1.2:复杂对象的创建次数

在FactoryBean中的isSingleton()方法中指定

java">public class xxxFactoryBean implements FactoryBean {
    // 如果该对象只需要创建一次,将返回true[单例为true, 默认]
    // 多例为false默认是单例
    boolean isSingleton() {
        return true;
    }
}

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

因为要节省不必要的内存浪费

  • 创建一次的对象常见的有:DAO,Service,Controller,SqlSessionFactory
  • 每次都要新创建的对象常见的有:Connection,SqlSession,Session,Struts2 Action

七:对象的生命周期

对象的生命周期是指一个对象创建,使用到销毁的完整过程,熟悉对象的生命周期有利于更好的使用Spring管理创建对象

在这里插入图片描述

1:创建阶段

Spring工厂创建对象的时机取决于对象的创建次数:

  • 如果是单例的,将在Spring工厂创建的同时,创建对象;
  • 如果是多例的,将在获取对象的同时创建对象(ctx.getBean(xxx)

🎉 如果想要在获取对象的时候创建单例对象,需要在<bean>标签中加入lazy-init="true"属性

2:初始化阶段

Spring工厂在创建完成对象[完成依赖注入]之后,调用对象的初始化方法,完成对应的初始化操作:

  • 初始化方法的提供:由程序员根据需求提供初始化方法,最终完成初始化操作
  • 初始化方法的调用:由Spring工厂进行调用

定义初始化方法有两种途径:

第一种方法是实现InitializingBean这个接口

这个接口提供了一个afterPropertiesSet()方法,这个方法允许属性赋值之后做一些额外操作

这种方式耦合了Spring框架,需要实现InitializingBean接口

java">public class Product implements InitializingBean {

    public Product() {
        System.out.println("this is no args structure");
    }

    /**
     * 在这里可以做一些初始化操作 spring 会调用
     */
    @Override
    public void afterPropertiesSet() throws Exception {
        System.out.println("Product.after properties set");
    }
}

第二种方法是配置文件中配置普通方法

java">public void myInit() {
    // 自己根据自己的需求,随便写初始化方法逻辑
}
<bean id="product" class="xxx.Product" init-method="myInit"></bean>

🎉 如果既实现了InitializingBean接口,又指定了init-method方法那么接口的顺序 > init-method方法的顺序

🎉 先进行注入才会执行初始化[先DI,再init]

🎉 初始化操作的主要是数据库,IO,网络流…

3:销毁阶段

Spring在调用ctx.close()之后便会进行对象的销毁,而对象的销毁也是有两种方式

第一种方式是通过实现DisposableBean接口中的destory()方法

java">public class Product implements InitializingBean, DisposableBean {

    public Product() {
        System.out.println("this is no args structure");
    }

    /**
     * 在这里可以做一些初始化操作 spring 会调用
     */
    @Override
    public void afterPropertiesSet() throws Exception {
        System.out.println("Product.after properties set");
    }

    @Override
    public void destroy() throws Exception {
        System.out.println("this is destroy method");
    }
}

第二种方法是配置文件中配置普通方法为destroy-method属性

java">public myDestroy() throws Exception {
    // 自定义销毁方法
}
<bean id="product" class="xxx.Product" init-method="xxx" destory-method="myDestroy"></bean>

🎉 销毁方法只适用于单例模式的bean

🎉 所谓的销毁操作就是资源的释放操作io.close();connection.close()

4:总结一下(面试重点)

Spring Bean 生命周期简单概括为4个阶段:

  1. 实例化,创建一个Bean对象
  2. 填充属性,为属性赋值
  3. 初始化
    1. 如果实现了xxxAware接口,通过不同类型的Aware接口拿到Spring容器的资源
    2. 如果实现了BeanPostProcessor接口,则会回调该接口的postProcessBeforeInitialzationpostProcessAfterInitialization方法
    3. 如果配置了init-method方法,则会执行init-method配置的方法
  4. 销毁
    1. 容器关闭后,如果Bean实现了DisposableBean接口,则会回调该接口的destroy方法
    2. 如果配置了destroy-method方法,则会执行destroy-method配置的方法

在这里插入图片描述

八:配置文件参数化

1:配置文件参数化

所谓配置文件参数化就是:将Spring配置文件中需要经常修改的字符串信息转移到一个更小的配置文件中 -> 利于维护性

在这里插入图片描述

2:开发步骤

  1. 提供一个小的配置文件(.properties) -> 名字随便,放置位置随便
# 假设叫做db.properties,位于resource根路径下
jdbc.driverName = com.mysql.jdbc.Driver
jdbc.url = jdbc:mysql://localhost:3306/cui?useSSL=false
jdbc.username = root
jdbc.password = 314159
  1. Spring配置文件和小的配置文件进行整合
<context:property-placeholder location="classpath:/db.propertied"/>
  1. 通过${key}获取对应的值

在这里插入图片描述

九:自定义类型转换器

1:类型转换器

所谓类型转换器就是Spring通过类型转换器将配置文件中的字符串类型的数据转换成为对象中对应类型的数据,进而完成注入工作

在这里插入图片描述

2:自定义类型转换器

当Spring内部没有提供特定类型的转换器的时候【例如String -> Date】,就需要在开发过程中自定义类型转换器

而如果需要自己实现类型转换器,就要实现Convert接口,然后在Spring配置文件中进行注册

在这里插入图片描述

java">package org.example.config;

import org.springframework.core.convert.converter.Converter;

import java.text.SimpleDateFormat;
import java.util.Date;

/**
 * <p>
 * 功能描述:自定义类型转换器
 * Converter<原始类型, 自定义类型>
 * </p>
 *
 * @author cui haida
 * @date 2024/01/06/17:21
 */
public class MyDateConverter implements Converter<String, Date> {

    // converter方法作用:String -> Date
    // SimpleDateFormat sdf = new SimpleDateFormat(format);
    // sdf.parse(String) --> Date

    @Override
    public Date convert(String s) {
        Date date = null;
        try {
            SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
            date = sdf.parse(s);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return date;
    }
}

告知Spring框架,我们创建的是一个类型转换器,所以要在Spring中注册

<bean id="myDateConverter" class="xxx.MyDateConverter"></bean>

同时还要在Spring中声明用于注册类型转换器的服务类

<!-- 创建MyDateConverter对象 -->
<bean id="myDateConverter" class="org.example.config.MyDateConverter"/>

<!-- 用于注册类型转换器 -->
<bean id="conversionService" class="org.springframework.context.support.ConversionServiceFactoryBean">
    <property name="converters">
        <!-- converters是一个set类型的 -->
        <set>
            <ref bean="myDateConverter"/> <!-- 注册刚才创建的MyDateConverter对象 -->
        </set>
    </property>
</bean>

🎉 Spring支持一种日期的类型转换器:2024/1/6这种格式的,其他格式的都不支持,需要自定义

十:后置处理Bean

1:BeanPostProcessor概述和原理

BeanPostProcessor的作用就是对Spring工厂所创建的对象进行再加工操作,AOP的底层原理大量的就是用的这个

在这里插入图片描述

再看一遍生命周期这张图,回顾下BeanPostProcessor所处的位置

在这里插入图片描述

而对于BeanPostProcessor规定的接口中有两个要实现的方法

java">public interface BeanPostProcessor {
    
    
    /**
     * 作用:Spring创建完成对象,并进行注入之后【完成属性赋值和容器赋值】,可以运行Before方法进行加工
     * 获得Spring创建好的对象,通过方法的参数
     * 最终通过获取返回值交给Spring框架
     */
    @Nullable
    default Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        return bean;
    }

    /**
     * 作用:Spring执行完成对象的初始化之后【init-method or InitalzingBean中的setAfterProperties】,可以运行After方法进行加工
     * 获得Spring创建好的对象,通过方法的参数
     * 最终通过获取返回值交给Spring框架
     */
    @Nullable
    default Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        return bean;
    }
}

🎉 因为在实战中很少去处理Spring的初始化操作,所以没有必要去区分Before和After,此时二者只需要实现一个即可

⚠️ 一定要返回bean对象在before方法中

2:开发步骤

1:类要实现BeanPostProcessor接口

java">package org.example.beanpost;

import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.lang.Nullable;

/**
 * <p>
 * 功能描述:
 * </p>
 *
 * @author cui haida
 * @date 2024/01/07/8:19
 */
public class MyBeanPostProcessor implements BeanPostProcessor {

    /**
     * 前置方法
     * @param bean bean
     * @param beanName bean id
     */
    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        return bean;
    }

    /**
     * 后置方法
     * @param bean bean
     * @param beanName bean id
     */
    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        Category category = (Category) bean;
        category.setName("zhangsan");
        return category;
    }
}

2:Spring的配置文件中进行配置

<bean id="c" class="org.example.beanpost.Category">
    <property name="id" value="10"/>
    <property name="name" value="cui"/>
</bean>

<bean id="myBeanPostProcessor" class="org.example.beanpost.MyBeanPostProcessor"/>

因为BeanPostProcessor会对工厂中所有的bean进行监控,所以在重写前置方法或者后置方法的时候可能要做特殊的处理

java">/**
 * 后置方法
 * @param bean bean
 * @param beanName bean id
 */
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
    if (bean instanceof Category) { // or beanName.equals("xxxbeanId")
        Category category = (Category) bean;
        category.setName("zhangsan");
    }
    return bean;
}

http://www.niftyadmin.cn/n/5800921.html

相关文章

springboot整合Elasticsearch介绍

上一篇博客介绍了elasticsearch及其安装部署&#xff08;https://chengpei.top/archives/elasticsearch-jieshao&#xff09;&#xff0c;这次就介绍了一下如何将ES和我们的springboot项目整合使用 连接工具 整合之前我们先介绍一款工具用于连接elasticsearch查询工具&#x…

嵌入式驱动开发详解21(网络驱动开发)

文章目录 前言以太网框架ENET 接口简介MAC接口MII \ RMII 接口MDIO 接口RJ45 接口 PHY芯片以太网驱动驱动挂载wifi模块挂载后续 前言 linux驱动主要是字符设备驱动、块设备驱动还有网络设备驱动、字符设备驱动在本专栏前面已经详细将解了&#xff0c;网络设备驱动本文会做简要…

IMX芯片启动方式

一、启动方式选择 a)概述 BOOT 的处理过程是发生在 I.MX6U 芯片上电以后,芯片会根据 BOOT_MODE[1:0]的设置 来选择 BOOT 方式。 BOOT_MODE[1:0]的值是可以改变的,有两种方式,一种是改写 eFUSE(熔 丝),一种是修改相应的 GPIO 高低电平。第一种修改 eFUSE 的方式只能修改一次…

默认接口实现”在 C# 7.3 中不可用。请使用 8.0 或更高的语言版本报错问题

问题 开发环境&#xff1a; C# .netframework4.5.2, Visualstudio2019&#xff0c;定义了如下接口&#xff0c;在接口中定义了一个委托和事件报错&#xff1a;错误 CS8370 功能“默认接口实现”在 C# 7.3 中不可用。请使用 8.0 或更高的语言版本。 public interface I…

使用Python获取PDF文本和图片的精确位置

在处理和分析PDF文档时&#xff0c;获取文本和图片在页面上的精确位置是一个重要的操作。通过确定这些元素的具体坐标&#xff0c;我们可以实现对PDF内容的更精细控制和理解&#xff0c;这对于自动化文档处理、信息提取以及内容重组等工作流程尤为关键。通过Python编程语言&…

Let‘s encrypt 免费 SSL 证书安装

Let’s Encrypt 是一家免费、开放、自动化的证书颁发机构&#xff0c;由非营利组织互联网安全研究组&#xff08;ISRG&#xff09;运作。Let’s encrypt 证书是 3 个月有效期&#xff0c;可以通过脚本自动续期。 yum install certbot -y测试一下 Certbot 是否安装成功。 cert…

CAN201 Introduction to Networking(计算机网络)Pt.2 传输层

文章目录 3. Transport Layer&#xff08;传输层&#xff09;3.1 Multiplexing and demultiplexing&#xff08;多路复用和多路分解&#xff09;3.2 Connectionless transport&#xff1a;UDP3.3 Principles of reliable data transfer3.4 Pipelined communication3.5 TCP: con…

JVM简介—1.Java内存区域

大纲 1.运行时数据区的介绍 2.运行时数据区各区域的作用 3.各个版本内存区域的变化 4.直接内存的使用和作用 5.站在线程的角度看Java内存区域 6.深入分析堆和栈的区别 7.方法的出入栈和栈上分配、逃逸分析及TLAB 8.虚拟机中的对象创建步骤 9.对象的内存布局 10.对象的…