Spring 使用指南 ~ 3、Spring 中 bean 的生命周期详解

本文详细解析了 Spring 中 bean 的生命周期,包括 bean 的创建阶段(BeanNameAware、BeanClassLoaderAware、ApplicationContextAware)、初始化阶段(@PostConstruct、InitializingBean、init-method)以及销毁阶段(@PreDestroy、DisposableBean、destroy-method)。此外,还介绍了如何使用关闭钩子、FactoryBean、factory-bean 和 factory-method 以及 PropertyEditor 的使用方法。
摘要由CSDN通过智能技术生成

Spring 中 bean 的生命周期详解

一、bean 的生命周期图解

在这里插入图片描述

二、bean 创建时使用 Spring 的资源

  • 实现 aware 类型接口的 bean,可以在 bean 实例化的时候获取到一些相对应的资源,如实现 BeanNameAware 的 bean,就可以获取到 beanName。
  • Spring 自带的 aware 类型的接口有很多,下面只列举了常用的三个(ApplicationContextAware 是用的最多的一个)

1、BeanNameAware

  • 使用 BeanNameAware 接口来获取 beanName

示例

package com.luo.spring.guides.beanlifecycle.aware.name;

import org.springframework.beans.factory.BeanNameAware;

/**
 * @author : archer
 * @date : Created in 2022/12/6 15:37
 * @description :
 */
public class NamedSinger  implements BeanNameAware {
    private String name;

    /** @Implements {@link BeanNameAware#setBeanName(String)} */
    public void setBeanName(String beanName) {
        this.name = beanName;
    }

    public void sing() {
        System.out.println("Singer [" + name + "] - sing()");
    }
}
<?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="namedSinger" class="com.luo.spring.guides.beanlifecycle.aware.name.NamedSinger"/>
</beans>

测试

package com.luo.spring.guides.beanlifecycle.aware.name;

import org.springframework.context.support.GenericXmlApplicationContext;

/**
 * @author : archer
 * @date : Created in 2022/12/6 15:38
 * @description :
 */
public class Main {
    public static void main(String[] args) {
        GenericXmlApplicationContext ctx = new GenericXmlApplicationContext();
        ctx.load("beanlifecycle/aware/name/app-context-name.xml");
        ctx.refresh();

        NamedSinger bean = (NamedSinger) ctx.getBean("namedSinger");
        bean.sing();

        ctx.close();
    }
}

输出

Singer [namedSinger] - sing()

2、BeanClassLoaderAware

package com.luo.spring.guides.beanlifecycle.aware.classloader;

import org.springframework.beans.factory.BeanClassLoaderAware;
import org.springframework.stereotype.Component;

/**
 * @author : archer
 * @date : Created in 2022/12/6 15:51
 * @description :
 */
@Component
public class SingerClassLoaderBean implements BeanClassLoaderAware {

    private ClassLoader classLoader;

    @Override
    public void setBeanClassLoader(ClassLoader classLoader) {
        this.classLoader = classLoader;
    }

    public void print(){
        System.out.println(classLoader);
    }
}

测试

package com.luo.spring.guides.beanlifecycle.aware.classloader;

import com.luo.spring.guides.iocdi.annotation.field.Singer;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;

/**
 * @author : archer
 * @date : Created in 2022/12/6 15:52
 * @description :
 */
public class Main {

    @Configuration
    @ComponentScan(basePackages = {"com.luo.spring.guides.beanlifecycle.aware.classloader"})
    public static class ClassloaderConfiguration{}

    public static void main(String... args) {
        ApplicationContext ctx = new AnnotationConfigApplicationContext
                (ClassloaderConfiguration.class);
        SingerClassLoaderBean singerClassLoaderBean = ctx.getBean("singerClassLoaderBean", SingerClassLoaderBean.class);


        singerClassLoaderBean.print();
    }
}

输出

jdk.internal.loader.ClassLoaders$AppClassLoader@4e25154f

3、ApplicationContextAware

package com.luo.spring.guides.beanlifecycle.aware.applicationcontext;

import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Component;

/**
 * Created by iuliana.cosmina on 2/22/17.
 */
@Component
public class Singer implements ApplicationContextAware {

    private ApplicationContext applicationContext;

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.applicationContext = applicationContext;
    }


    public void sing() {
		System.out.println(applicationContext.getId());
    }
}

测试

package com.luo.spring.guides.beanlifecycle.aware.applicationcontext;

import com.luo.spring.guides.beanlifecycle.aware.classloader.SingerClassLoaderBean;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;

/**
 * @author : archer
 * @date : Created in 2022/12/8 14:20
 * @description :
 */
public class Main {

    @Configuration
    @ComponentScan(basePackages = {"com.luo.spring.guides.beanlifecycle.aware.applicationcontext"})
    public static class MConfiguration {
    }

    public static void main(String... args) {
        ApplicationContext ctx = new AnnotationConfigApplicationContext(MConfiguration.class);
        Singer singer = (Singer) ctx.getBean("singer");
        singer.sing();
    }
}

输出

org.springframework.context.annotation.AnnotationConfigApplicationContext@45820e51

三、bean 创建时调用特定方法

1、@PostConstruct

package com.luo.spring.guides.beanlifecycle.init.postconstruct;

import org.springframework.beans.factory.BeanCreationException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.GenericXmlApplicationContext;

import javax.annotation.PostConstruct;

public class SingerWithJSR250 {
    private static final String DEFAULT_NAME = "Eric Clapton";

    private String name;
    private int age = Integer.MIN_VALUE;

    public void setName(String name) {
        this.name = name;
    }

    public void setAge(int age) {
        this.age = age;
    }

    @PostConstruct
    private void init() throws Exception {
        System.out.println("Initializing bean");

       if (name == null) {
            System.out.println("Using default name");
            name = DEFAULT_NAME;
        }

        if (age == Integer.MIN_VALUE) {
            throw new IllegalArgumentException(
                    "You must set the age property of any beans of type " + 
                    SingerWithJSR250.class);
        }
    }

    public String toString() {
        return "\tName: " + name + "\n\tAge: " + age;
    }
}
<?xml version="1.0" encoding="UTF-8"?>

<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:p="http://www.springframework.org/schema/p"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
          http://www.springframework.org/schema/beans/spring-beans.xsd
          http://www.springframework.org/schema/context 
          http://www.springframework.org/schema/context/spring-context.xsd"
       default-lazy-init="true">

    <context:annotation-config/>

    <bean id="singerOne"
          class="com.luo.spring.guides.beanlifecycle.init.postconstruct.SingerWithJSR250"
          p:name="John Mayer" p:age="39"/>

    <bean id="singerTwo"
          class="com.luo.spring.guides.beanlifecycle.init.postconstruct.SingerWithJSR250"
          p:age="72"/>

    <bean id="singerThree"
          class="com.luo.spring.guides.beanlifecycle.init.postconstruct.SingerWithJSR250"
          p:name="John Butler"/>
</beans>

测试

package com.luo.spring.guides.beanlifecycle.init.postconstruct;

import org.springframework.beans.factory.BeanCreationException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.GenericXmlApplicationContext;

/**
 * @author : archer
 * @date : Created in 2022/12/8 15:26
 * @description :
 */
public class Main {
    public static void main(String... args) {
        GenericXmlApplicationContext ctx = new GenericXmlApplicationContext();
        ctx.load("classpath:beanlifecycle/init/app-context-postconstruct.xml");
        ctx.refresh();

        getBean("singerOne", ctx);
        getBean("singerTwo", ctx);
        getBean("singerThree", ctx);

        ctx.close();
    }

    private static SingerWithJSR250 getBean(String beanName, ApplicationContext ctx) {
        try {
            SingerWithJSR250 bean = (SingerWithJSR250) ctx.getBean(beanName);
            System.out.println(bean);
            return bean;
        } catch (BeanCreationException ex) {
            System.out.println("An error occured in bean configuration: "
                    + ex.getMessage());
            return null;
        }
    }
}

输出

Initializing bean
Name: John Mayer
Age: 39
Initializing bean
Using default name
Name: Eric Clapton
Age: 72
Initializing bean
An error occured in bean configuration: Error creating bean with name ‘singerThree’: Invocation of init method failed; nested exception is java.lang.IllegalArgumentException: You must set the age property of any beans of type class com.luo.spring.guides.beanlifecycle.init.postconstruct.SingerWithJSR250

2、InitializingBean

package com.luo.spring.guides.beanlifecycle.init.initialzingbean;

import org.springframework.beans.factory.BeanCreationException;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.GenericXmlApplicationContext;

public class SingerWithInterface implements InitializingBean {
    private static final String DEFAULT_NAME = "Eric Clapton";

    private String name;
    private int age = Integer.MIN_VALUE;

    public void setName(String name) {
        this.name = name;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public void afterPropertiesSet() throws Exception {
        System.out.println("Initializing bean");

        if (name == null) {
            System.out.println("Using default name");
            name = DEFAULT_NAME;
        }

        if (age == Integer.MIN_VALUE) {
            throw new IllegalArgumentException(
                    "You must set the age property of any beans of type " 
                    + SingerWithInterface.class);
        }
    }

    public String toString() {
        return "\tName: " + name + "\n\tAge: " + age;
    }
}
<?xml version="1.0" encoding="UTF-8"?>

<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:p="http://www.springframework.org/schema/p"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd"
       default-lazy-init="true">

    <bean id="singerOne"
          class="com.luo.spring.guides.beanlifecycle.init.initialzingbean.SingerWithInterface"
          p:name="John Mayer" p:age="39"/>

    <bean id="singerTwo"
          class="com.luo.spring.guides.beanlifecycle.init.initialzingbean.SingerWithInterface"
          p:age="72"/>

    <bean id="singerThree"
          class="com.luo.spring.guides.beanlifecycle.init.initialzingbean.SingerWithInterface"
          p:name="John Butler"/>
</beans>

测试

package com.luo.spring.guides.beanlifecycle.init.initialzingbean;

import org.springframework.beans.factory.BeanCreationException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.GenericXmlApplicationContext;

/**
 * @author : archer
 * @date : Created in 2022/12/8 15:33
 * @description :
 */
public class Main {

    public static void main(String... args) {
        GenericXmlApplicationContext ctx = new GenericXmlApplicationContext();
        ctx.load("beanlifecycle/init/app-context-initializingbean.xml");
        ctx.refresh();

        getBean("singerOne", ctx);
        getBean("singerTwo", ctx);
        getBean("singerThree", ctx);

        ctx.close();
    }

    private static SingerWithInterface getBean(String beanName,
                                               ApplicationContext ctx) {
        try {
            SingerWithInterface bean = (SingerWithInterface) ctx.getBean(beanName);
            System.out.println(bean);
            return bean;
        } catch (BeanCreationException ex) {
            System.out.println("An error occurred in bean configuration: "
                    + ex.getMessage());
            return null;
        }
    }
}

输出

Initializing bean
Name: John Mayer
Age: 39
Initializing bean
Using default name
Name: Eric Clapton
Age: 72
Initializing bean
An error occurred in bean configuration: Error creating bean with name ‘singerThree’ defined in class path resource [beanlifecycle/init/app-context-initializingbean.xml]: Invocation of init method failed; nested exception is java.lang.IllegalArgumentException: You must set the age property of any beans of type class com.luo.spring.guides.beanlifecycle.init.initialzingbean.SingerWithInterface

3、init-method

package com.luo.spring.guides.beanlifecycle.init.initmethod.domain;

import org.springframework.beans.factory.BeanCreationException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.GenericXmlApplicationContext;

public class Singer {
    private static final String DEFAULT_NAME = "Eric Clapton";

    private String name;
    private int age = Integer.MIN_VALUE;

    public void setName(String name) {
        this.name = name;
    }

    public void setAge(int age) {
        this.age = age;
    }

    private void init() {
        System.out.println("Initializing bean");

       if (name == null) {
            System.out.println("Using default name");
            name = DEFAULT_NAME;
        }

        if (age == Integer.MIN_VALUE) {
            throw new IllegalArgumentException(
                    "You must set the age property of any beans of type " + Singer.class);
        }
    }

    public String toString() {
        return "\tName: " + name + "\n\tAge: " + age;
    }
}
1)、xml 形式
<?xml version="1.0" encoding="UTF-8"?>

<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:p="http://www.springframework.org/schema/p"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd"
    default-lazy-init="true" default-init-method="init">

    <bean id="singerOne"
        class="com.luo.spring.guides.beanlifecycle.init.initmethod.domain.Singer"
          p:name="John Mayer" p:age="39"/>

    <bean id="singerTwo"
        class="com.luo.spring.guides.beanlifecycle.init.initmethod.domain.Singer"
         p:age="72"/>

    <bean id="singerThree"
        class="com.luo.spring.guides.beanlifecycle.init.initmethod.domain.Singer"
         p:name="John Butler"/>
</beans>

测试

package com.luo.spring.guides.beanlifecycle.init.initmethod.xml;

import com.luo.spring.guides.beanlifecycle.init.initmethod.domain.Singer;
import org.springframework.beans.factory.BeanCreationException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.GenericXmlApplicationContext;

/**
 * @author : archer
 * @date : Created in 2022/12/8 15:41
 * @description :
 */
public class Main {
    public static void main(String... args) {
        GenericXmlApplicationContext ctx = new GenericXmlApplicationContext();
        ctx.load("classpath:beanlifecycle/init/app-context-initmethod.xml");
        ctx.refresh();

        getBean("singerOne", ctx);
        getBean("singerTwo", ctx);
        getBean("singerThree", ctx);

        ctx.close();
    }

    public static Singer getBean(String beanName, ApplicationContext ctx) {
        try {
            Singer bean = (Singer) ctx.getBean(beanName);
            System.out.println(bean);
            return bean;
        } catch (BeanCreationException ex) {
            System.out.println("An error occurred in bean configuration: "
                    + ex.getMessage());
            return null;
        }
    }
}

输出

Initializing bean
Name: John Mayer
Age: 39
Initializing bean
Using default name
Name: Eric Clapton
Age: 72
Initializing bean
An error occurred in bean configuration: Error creating bean with name ‘singerThree’ defined in class path resource [beanlifecycle/init/app-context-initmethod.xml]: Invocation of init method failed; nested exception is java.lang.IllegalArgumentException: You must set the age property of any beans of type class com.luo.spring.guides.beanlifecycle.init.initmethod.domain.Singer

2)、注解形式

测试

package com.luo.spring.guides.beanlifecycle.init.initmethod.annotation;

import com.luo.spring.guides.beanlifecycle.init.initmethod.domain.Singer;
import org.springframework.beans.factory.BeanCreationException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Lazy;
import org.springframework.context.support.GenericApplicationContext;

/**
 * @author : archer
 * @date : Created in 2022/12/8 15:42
 * @description :
 */
public class Main {

    @Configuration
    static class SingerConfig{

        @Lazy
        @Bean(initMethod = "init")
        Singer singerOne() {
            Singer singerOne = 	new Singer();
            singerOne.setName("John Mayer");
            singerOne.setAge(39);
            return singerOne;
        }

        @Lazy
        @Bean(initMethod = "init")
        Singer singerTwo() {
            Singer singerTwo = 	new Singer();
            singerTwo.setAge(72);
            return singerTwo;
        }

        @Lazy
        @Bean(initMethod = "init")
        Singer singerThree() {
            Singer singerThree = 	new Singer();
            singerThree.setName("John Butler");
            return singerThree;
        }
    }

    public static void main(String... args) {
        GenericApplicationContext ctx = new AnnotationConfigApplicationContext(SingerConfig.class);

        getBean("singerOne", ctx);
        getBean("singerTwo", ctx);
        getBean("singerThree", ctx);

        ctx.close();
    }

    public static Singer getBean(String beanName, ApplicationContext ctx) {
        try {
            Singer bean = (Singer) ctx.getBean(beanName);
            System.out.println(bean);
            return bean;
        } catch (BeanCreationException ex) {
            System.out.println("An error occurred in bean configuration: "
                    + ex.getMessage());
            return null;
        }
    }
}

输出

Initializing bean
Name: John Mayer
Age: 39
Initializing bean
Using default name
Name: Eric Clapton
Age: 72
Initializing bean
An error occurred in bean configuration: Error creating bean with name ‘singerThree’ defined in com.luo.spring.guides.beanlifecycle.init.initmethod.annotation.Main$SingerConfig: Invocation of init method failed; nested exception is java.lang.IllegalArgumentException: You must set the age property of any beans of type class com.luo.spring.guides.beanlifecycle.init.initmethod.domain.Singer

四、bean 销毁时调用特定方法

1、@PreDestroy

package com.luo.spring.guides.beanlifecycle.destory.predestory.domain;

import org.springframework.context.support.GenericXmlApplicationContext;

import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import java.io.File;

public class DestructiveBeanWithJSR250 {
    private File file;
    private String filePath;
    
    @PostConstruct
    public void init() throws Exception {
        System.out.println("Initializing Bean");

        if (filePath == null) {
            throw new IllegalArgumentException(
                    "You must specify the filePath property of " + 
                    DestructiveBeanWithJSR250.class);
        }

        this.file = new File(filePath);
        this.file.createNewFile();

        System.out.println("File exists: " + file.exists());
    }

    @PreDestroy
    public void destroy() {
        System.out.println("Destroying Bean");

        if(!file.delete()) {
            System.err.println("ERROR: failed to delete file.");
        }

        System.out.println("File exists: " + file.exists());
    }

    public void setFilePath(String filePath) {
        this.filePath = filePath;
    }


}
1)、xml 形式
<?xml version="1.0" encoding="UTF-8"?>

<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:p="http://www.springframework.org/schema/p"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context
        http://www.springframework.org/schema/context/spring-context.xsd">

    <context:annotation-config/>

    <bean id="destructiveBean"
          class="com.luo.spring.guides.beanlifecycle.destory.predestory.domain.DestructiveBeanWithJSR250"
          p:filePath=
                  "#{systemProperties['java.io.tmpdir']}#{systemProperties['file.separator']}test.txt"/>
</beans>

测试

package com.luo.spring.guides.beanlifecycle.destory.predestory.xml;

import com.luo.spring.guides.beanlifecycle.destory.predestory.domain.DestructiveBeanWithJSR250;
import org.springframework.context.support.GenericXmlApplicationContext;

/**
 * @author : archer
 * @date : Created in 2022/12/8 16:08
 * @description :
 */
public class Main {

    public static void main(String... args) throws Exception {
        GenericXmlApplicationContext ctx = new GenericXmlApplicationContext();
        ctx.load("classpath:beanlifecycle/destroy/app-context-predestory.xml");
        ctx.refresh();

        DestructiveBeanWithJSR250 bean =
                (DestructiveBeanWithJSR250) ctx.getBean("destructiveBean");

        System.out.println("Calling destroy()");
        ctx.close();
        System.out.println("Called destroy()");
    }
}

输出

Initializing Bean
File exists: true
Destroying Bean
File exists: false

2)、注解形式

测试

package com.luo.spring.guides.beanlifecycle.destory.predestory.annotation;

import com.luo.spring.guides.beanlifecycle.destory.predestory.domain.DestructiveBeanWithJSR250;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Lazy;
import org.springframework.context.support.GenericApplicationContext;

/**
 * Created by iuliana.cosmina on 2/27/17.
 */
public class Main {

	@Configuration
	static class DestructiveBeanConfig {

		@Lazy // 表示 getBean 时再创建 bean
		@Bean
		DestructiveBeanWithJSR250 destructiveBean() {
			DestructiveBeanWithJSR250 destructiveBean = new DestructiveBeanWithJSR250();
			destructiveBean.setFilePath(System.getProperty("java.io.tmpdir") +
					System.getProperty("file.separator") + "test.txt");
			return destructiveBean;
		}

	}

	public static void main(String... args) {
		GenericApplicationContext ctx =
				   new AnnotationConfigApplicationContext(DestructiveBeanConfig.class);

		ctx.getBean(DestructiveBeanWithJSR250.class);
		ctx.registerShutdownHook();
	}

}

输出

Initializing Bean
File exists: true
Destroying Bean
File exists: false

2、DisposableBean

package com.luo.spring.guides.beanlifecycle.destory.disposable;

import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.context.support.GenericXmlApplicationContext;

import java.io.File;

public class DestructiveBeanWithInterface implements InitializingBean, DisposableBean {
    private File file;
    private String filePath;
    
    @Override
    public void afterPropertiesSet() throws Exception {
        System.out.println("Initializing Bean");

        if (filePath == null) {
            throw new IllegalArgumentException(
                    "You must specify the filePath property of " + 
                    DestructiveBeanWithInterface.class);
        }

        this.file = new File(filePath);
        this.file.createNewFile();

        System.out.println("File exists: " + file.exists());
    }

    @Override
    public void destroy() {
        System.out.println("Destroying Bean");

        if(!file.delete()) {
            System.err.println("ERROR: failed to delete file.");
        }

        System.out.println("File exists: " + file.exists());
    }

    public void setFilePath(String filePath) {
        this.filePath = filePath;
    }

}

测试

package com.luo.spring.guides.beanlifecycle.destory.disposable;

import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Lazy;
import org.springframework.context.support.GenericApplicationContext;

/**
 * @author : archer
 * @date : Created in 2022/12/8 16:49
 * @description :
 */
public class Main {

    @Configuration
    static class DestructiveBeanConfig {

        @Lazy // 表示 getBean 时再创建 bean
        @Bean
        DestructiveBeanWithInterface destructiveBean() {
            DestructiveBeanWithInterface destructiveBean = new DestructiveBeanWithInterface();
            destructiveBean.setFilePath(System.getProperty("java.io.tmpdir") +
                    System.getProperty("file.separator") + "test.txt");
            return destructiveBean;
        }

    }

    public static void main(String... args) throws Exception {
        GenericApplicationContext ctx =
                new AnnotationConfigApplicationContext(DestructiveBeanConfig.class);

        ctx.getBean(DestructiveBeanWithInterface.class);
        ctx.close();
    }
}

输出

Initializing Bean
File exists: true
Destroying Bean
File exists: false

3、destroy-method

package com.luo.spring.guides.beanlifecycle.destory.destroymethod;

import org.springframework.beans.factory.InitializingBean;
import org.springframework.context.support.GenericXmlApplicationContext;

import java.io.File;

public class DestructiveBean {
    private File file;
    private String filePath;
    
    public void init() throws Exception {
        System.out.println("Initializing Bean");

        if (filePath == null) {
            throw new IllegalArgumentException(
                    "You must specify the filePath property of " + DestructiveBean.class);
        }

        this.file = new File(filePath);
        this.file.createNewFile();

        System.out.println("File exists: " + file.exists());
    }

    public void destroy() {
        System.out.println("Destroying Bean");

        if(!file.delete()) {
            System.err.println("ERROR: failed to delete file.");
        }

        System.out.println("File exists: " + file.exists());
    }

    public void setFilePath(String filePath) {
        this.filePath = filePath;
    }


}

测试

package com.luo.spring.guides.beanlifecycle.destory.destroymethod;

import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Lazy;
import org.springframework.context.support.GenericApplicationContext;

/**
 * @author : archer
 * @date : Created in 2022/12/8 16:55
 * @description :
 */
public class Main {

    @Configuration
    static class DestructiveBeanConfig {
        @Lazy // 表示 getBean 时再创建 bean
        @Bean(initMethod = "init", destroyMethod = "destroy")
        DestructiveBean destructiveBean() {
            DestructiveBean destructiveBean = new DestructiveBean();
            destructiveBean.setFilePath(System.getProperty("java.io.tmpdir") +
                    System.getProperty("file.separator") + "test.txt");
            return destructiveBean;
        }
    }

    public static void main(String... args) throws Exception {
        GenericApplicationContext ctx =
                new AnnotationConfigApplicationContext(DestructiveBeanConfig.class);

        ctx.getBean(DestructiveBean.class);
        ctx.registerShutdownHook();
    }
}

输出

Initializing Bean
File exists: true
Destroying Bean
File exists: false

五、使用关闭钩子

在 Spring 中销毁回调函数唯一缺点是,它们不会自动触发,需要应用程序关闭之前调用 AbstractApplicationContext.destroy()。那么怎么让它随着应用程序的关闭自动调用呢?

一个可行的方案是创建一个关闭钩子(Shutdown Hook),它是应用程序关闭之前执行的一个线程,这是调用 AbstractApplicationContext.destroy() 的一种理想的方式。

  • ctx.registerShutdownHook() 会与 ctx.close() 产生相同的结果
  • ctx.registerShutdownHook() 方法自动指示 Spring 注册底层 JVM 运行时的关闭钩子。

代码实现如下

package com.luo.spring.guides.beanlifecycle.shutdownhook;

import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.support.AbstractApplicationContext;
import org.springframework.context.support.GenericApplicationContext;

import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import java.io.File;

public class DestructiveBeanWithHook implements ApplicationContextAware {
    private File file;
    private String filePath;

    private ApplicationContext applicationContext;

    @PostConstruct
    public void init() throws Exception {
        System.out.println("Initializing Bean");

        if (filePath == null) {
            throw new IllegalArgumentException(
                    "You must specify the filePath property of " +
                            DestructiveBeanWithHook.class);
        }

        this.file = new File(filePath);
        this.file.createNewFile();

        System.out.println("File exists: " + file.exists());
    }

    @PreDestroy
    public void destroy() {
        System.out.println("Destroying Bean");

        if (!file.delete()) {
            System.err.println("ERROR: failed to delete file.");
        }

        System.out.println("File exists: " + file.exists());
    }

    public void setFilePath(String filePath) {
        this.filePath = filePath;
    }

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.applicationContext = applicationContext;
        if (applicationContext instanceof AbstractApplicationContext) {
            ((AbstractApplicationContext) applicationContext).registerShutdownHook();
        }
    }
}

测试

package com.luo.spring.guides.beanlifecycle.shutdownhook;

import com.luo.spring.guides.beanlifecycle.destory.destroymethod.DestructiveBean;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Lazy;
import org.springframework.context.support.GenericApplicationContext;
import org.springframework.context.support.GenericXmlApplicationContext;

/**
 * @author : archer
 * @date : Created in 2022/12/8 17:11
 * @description :
 */
public class Main {

    @Configuration
    static class DestructiveBeanConfig {
        @Lazy // 表示 getBean 时再创建 bean
        @Bean
        DestructiveBeanWithHook destructiveBean() {
            DestructiveBeanWithHook destructiveBean = new DestructiveBeanWithHook();
            destructiveBean.setFilePath(System.getProperty("java.io.tmpdir") +
                    System.getProperty("file.separator") + "test.txt");
            return destructiveBean;
        }
    }

    public static void main(String... args) throws Exception {
        GenericApplicationContext ctx =
                new AnnotationConfigApplicationContext(DestructiveBeanConfig.class);

        ctx.getBean(DestructiveBeanWithHook.class);
    }
}

输出

Initializing Bean
File exists: true

//若不注册 registerShutdownHook(),则没有下面两行,即 destroy() 方法不执行

Destroying Bean
File exists: false

六、使用 FactoryBean

package com.luo.spring.guides.factorybean;

import java.security.MessageDigest;

public class MessageDigester {
    private MessageDigest digest1;
    private MessageDigest digest2;

    public void setDigest1(MessageDigest digest1) {
        this.digest1 = digest1;
    }

    public void setDigest2(MessageDigest digest2) {
        this.digest2 = digest2;
    }

    public MessageDigest getDigest1() {
        return digest1;
    }

    public void digest(String msg) {
        System.out.println("Using digest1");
        digest(msg, digest1);

        System.out.println("Using digest2");
        digest(msg, digest2);
    }

    private void digest(String msg, MessageDigest digest) {
        System.out.println("Using alogrithm: " + digest.getAlgorithm());
        digest.reset();
        byte[] bytes = msg.getBytes();
        byte[] out = digest.digest(bytes);
        System.out.println(out);
    }
}
package com.luo.spring.guides.factorybean;

import org.springframework.beans.factory.FactoryBean;
import org.springframework.beans.factory.InitializingBean;

import java.security.MessageDigest;

public class MessageDigestFactoryBean implements 
      FactoryBean<MessageDigest>, InitializingBean {
    private String algorithmName = "MD5";

    private MessageDigest messageDigest = null;

    public MessageDigest getObject() throws Exception {
       return messageDigest;
    }

    public Class<MessageDigest> getObjectType() {
        return MessageDigest.class;
    }

    public boolean isSingleton() {
        return true;
    }

    public void afterPropertiesSet() throws Exception {
        messageDigest = MessageDigest.getInstance(algorithmName);
    }

    public void setAlgorithmName(String algorithmName) {
        this.algorithmName = algorithmName;
    }
}

测试

package com.luo.spring.guides.factorybean;

import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.support.GenericApplicationContext;

import java.security.MessageDigest;

/**
 * Created by iuliana.cosmina on 3/7/17.
 */
public class Main {

    @Configuration
    static class MessageDigesterConfig {

        @Bean
        public MessageDigestFactoryBean shaDigest() {
            MessageDigestFactoryBean factoryOne = new MessageDigestFactoryBean();
            factoryOne.setAlgorithmName("SHA1");
            return factoryOne;
        }

        @Bean
        public MessageDigestFactoryBean defaultDigest() {
            return new MessageDigestFactoryBean();
        }

        @Bean
        MessageDigester digester() throws Exception {
            MessageDigester messageDigester = new MessageDigester();
            messageDigester.setDigest1(shaDigest().getObject());
            messageDigester.setDigest2(defaultDigest().getObject());
            return messageDigester;
        }
    }

    public static void main(String... args) throws Exception {
        GenericApplicationContext ctx = new AnnotationConfigApplicationContext(MessageDigesterConfig.class);

        MessageDigester digester = (MessageDigester) ctx.getBean("digester");
        digester.digest("Hello World!");

        System.out.println("==========我是分割线=============");

        MessageDigest shaDigest1 = ctx.getBean("shaDigest", MessageDigest.class);
        System.out.println(shaDigest1.digest("Hello World".getBytes()).toString());

        MessageDigestFactoryBean factoryBean =
                (MessageDigestFactoryBean) ctx.getBean("&shaDigest");
        MessageDigest shaDigest2 = factoryBean.getObject();
        System.out.println(shaDigest2.digest("Hello World".getBytes()).toString());

        System.out.println("shaDigest1==shaDigest2:" + (shaDigest1 == shaDigest2));
        System.out.println("digester.getDigest1()==shaDigest2:" + (digester.getDigest1() == shaDigest2));

        ctx.close();
    }
}

输出

Using digest1
Using alogrithm: SHA1
[B@782859e
Using digest2
Using alogrithm: MD5
[B@34bde49d
我是分割线===
[B@1b1cfb87
[B@821330f
shaDigest1==shaDigest2:true
digester.getDigest1()==shaDigest2:true

七、factory-bean && factory-method

package com.luo.spring.guides.factorybean.xml;

import java.security.MessageDigest;

public class MessageDigestFactory {
    private String algorithmName = "MD5";

    public MessageDigest createInstance() throws Exception {
       return MessageDigest.getInstance(algorithmName);
    }

    public void setAlgorithmName(String algorithmName) {
        this.algorithmName = algorithmName;
    }
}
package com.luo.spring.guides.factorybean;

import java.security.MessageDigest;

public class MessageDigester {
    private MessageDigest digest1;
    private MessageDigest digest2;

    public void setDigest1(MessageDigest digest1) {
        this.digest1 = digest1;
    }

    public void setDigest2(MessageDigest digest2) {
        this.digest2 = digest2;
    }

    public MessageDigest getDigest1() {
        return digest1;
    }

    public void digest(String msg) {
        System.out.println("Using digest1");
        digest(msg, digest1);

        System.out.println("Using digest2");
        digest(msg, digest2);
    }

    private void digest(String msg, MessageDigest digest) {
        System.out.println("Using alogrithm: " + digest.getAlgorithm());
        digest.reset();
        byte[] bytes = msg.getBytes();
        byte[] out = digest.digest(bytes);
        System.out.println(out);
    }
}
<?xml version="1.0" encoding="UTF-8"?>

<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:p="http://www.springframework.org/schema/p"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd">

    <bean id="shaDigestFactory" 
        class="com.luo.spring.guides.factorybean.xml.MessageDigestFactory"
        p:algorithmName="SHA1"/>

    <bean id="defaultDigestFactory"
        class="com.luo.spring.guides.factorybean.xml.MessageDigestFactory"/>

    <bean id="shaDigest"
          factory-bean="shaDigestFactory"
          factory-method="createInstance">
    </bean>

    <bean id="defaultDigest"
          factory-bean="defaultDigestFactory"
          factory-method="createInstance"/>

    <bean id="digester" 
        class="com.luo.spring.guides.factorybean.MessageDigester"
        p:digest1-ref="shaDigest"
        p:digest2-ref="defaultDigest"/>
</beans>

测试

package com.luo.spring.guides.factorybean.xml;

import com.luo.spring.guides.factorybean.MessageDigester;
import org.springframework.context.support.GenericXmlApplicationContext;

import java.security.MessageDigest;

public class Main {
    public static void main(String... args) throws Exception {
        GenericXmlApplicationContext ctx = new GenericXmlApplicationContext();
        ctx.load("classpath:factorybean/app-context-xml.xml");
        ctx.refresh();

        MessageDigester digester = (MessageDigester) ctx.getBean("digester");
        digester.digest("Hello World!");

        System.out.println("==========我是分割线=============");

        MessageDigest shaDigest1 = ctx.getBean("shaDigest", MessageDigest.class);
        System.out.println(shaDigest1.digest("Hello World".getBytes()).toString());

        System.out.println("digester.getDigest1()==shaDigest2:" + (digester.getDigest1() == shaDigest1));

        ctx.close();
    }
}

输出

Using digest1
Using alogrithm: SHA1
[B@16a0ee18
Using digest2
Using alogrithm: MD5
[B@505fc5a4
我是分割线===
[B@5fbdfdcf
digester.getDigest1()==shaDigest2:true

八、PropertyEditor

1、使用内置的 PropertyEditor

  • 默认情况下,Spring 不会注册 CustomDateEditor 和 StringTrimmerEditor
  • 使用 Spring 内置的 PropertyEditor,可以将各种属性的 String 转换为正确的类型
package com.luo.spring.guides.propertyeditor.domain;

import org.springframework.beans.PropertyEditorRegistrar;
import org.springframework.beans.PropertyEditorRegistry;
import org.springframework.beans.propertyeditors.CustomDateEditor;
import org.springframework.beans.propertyeditors.StringTrimmerEditor;
import org.springframework.context.support.GenericXmlApplicationContext;

import java.io.File;
import java.io.InputStream;
import java.net.URL;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.regex.Pattern;

public class PropertyEditorBean {
    private byte[] bytes;                 // ByteArrayPropertyEditor
    private Character character;          //CharacterEditor
    private Class cls;                    // ClassEditor
    private Boolean trueOrFalse;          // CustomBooleanEditor
    private List<String> stringList;      // CustomCollectionEditor
    private Date date;                    // CustomDateEditor
    private Float floatValue;             // CustomNumberEditor
    private File file;                    // FileEditor
    private InputStream stream;           // InputStreamEditor
    private Locale locale;                // LocaleEditor
    private Pattern pattern;              // PatternEditor
    private Properties properties;        // PropertiesEditor
    private String trimString;            // StringTrimmerEditor
    private URL url;                      // URLEditor

    public void setCharacter(Character character) {
        System.out.println("Setting character: " + character);
        this.character = character;
    }

    public void setCls(Class cls) {
        System.out.println("Setting class: " + cls.getName());
        this.cls = cls;
    }

    public void setFile(File file) {
        System.out.println("Setting file: " + file.getName());
        this.file = file;
    }

    public void setLocale(Locale locale) {
        System.out.println("Setting locale: " + locale.getDisplayName());
        this.locale = locale;
    }

    public void setProperties(Properties properties) {
        System.out.println("Loaded " + properties.size() + " properties");
        this.properties = properties;
    }

    public void setUrl(URL url) {
        System.out.println("Setting URL: " + url.toExternalForm());
        this.url = url;
    }

    public void setBytes(byte... bytes) {
        System.out.println("Setting bytes: " + Arrays.toString(bytes));
        this.bytes = bytes;
    }

    public void setTrueOrFalse(Boolean trueOrFalse) {
        System.out.println("Setting Boolean: " + trueOrFalse);
        this.trueOrFalse = trueOrFalse;
    }

    public void setStringList(List<String> stringList) {
        System.out.println("Setting string list with size: "
            + stringList.size());

        this.stringList = stringList;

        for (String string: stringList) {
            System.out.println("String member: " + string);
        }
    }

    public void setDate(Date date) {
        System.out.println("Setting date: " + date);
        this.date = date;
    }

    public void setFloatValue(Float floatValue) {
        System.out.println("Setting float value: " + floatValue);
        this.floatValue = floatValue;
    }

    public void setStream(InputStream stream) {
        System.out.println("Setting stream: " + stream);
        this.stream = stream;
    }

    public void setPattern(Pattern pattern) {
        System.out.println("Setting pattern: " + pattern);
        this.pattern = pattern;
    }

    public void setTrimString(String trimString) {
        System.out.println("Setting trim string: " + trimString);
        this.trimString = trimString;
    }

    public static class CustomPropertyEditorRegistrar implements PropertyEditorRegistrar {
        @Override
        public void registerCustomEditors(PropertyEditorRegistry registry) {
            SimpleDateFormat dateFormatter = new SimpleDateFormat("MM/dd/yyyy");
            //默认情况下,Spring 不会注册 CustomDateEditor 和 StringTrimmerEditor
            registry.registerCustomEditor(Date.class,
                     new CustomDateEditor(dateFormatter, true));

            registry.registerCustomEditor(String.class, new StringTrimmerEditor(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"
    xmlns:util="http://www.springframework.org/schema/util"
    xmlns:p="http://www.springframework.org/schema/p"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/util    
        http://www.springframework.org/schema/util/spring-util.xsd">

    <bean id="customEditorConfigurer" 
        class="org.springframework.beans.factory.config.CustomEditorConfigurer"
        p:propertyEditorRegistrars-ref="propertyEditorRegistrarsList"/>
   
    <util:list id="propertyEditorRegistrarsList">
        <bean class="com.luo.spring.guides.propertyeditor.domain.PropertyEditorBean$CustomPropertyEditorRegistrar"/>
    </util:list>

    <bean id="builtInSample" class="com.luo.spring.guides.propertyeditor.domain.PropertyEditorBean"
          p:character="A"
        p:bytes="John Mayer"
        p:cls="java.lang.String"
        p:trueOrFalse="true"
        p:stringList-ref="stringList"
        p:stream="test.txt"
        p:floatValue="123.45678"
        p:date="05/03/13"
        p:file="#{systemProperties['java.io.tmpdir']}#{systemProperties['file.separator']}test.txt"
        p:locale="en_US"
        p:pattern="a*b"
        p:properties="name=Chris age=32"
        p:trimString="   String need trimming   "
        p:url="https://spring.io/"
    /> 

    <util:list id="stringList">
        <value>String member 1</value>
        <value>String member 2</value>
    </util:list>
</beans>

测试

package com.luo.spring.guides.propertyeditor.xml;

import com.luo.spring.guides.propertyeditor.domain.PropertyEditorBean;
import org.springframework.context.support.GenericXmlApplicationContext;

import java.io.File;

/**
 * @author : archer
 * @date : Created in 2022/12/8 19:02
 * @description :
 */
public class Main {

    public static void main(String... args) throws Exception {
        File file = File.createTempFile("test", "txt");
        file.deleteOnExit();

        GenericXmlApplicationContext ctx = new GenericXmlApplicationContext();
        ctx.load("classpath:propertyeditor/app-context-propertyeditor.xml");
        ctx.refresh();

        PropertyEditorBean bean =
                (PropertyEditorBean) ctx.getBean("builtInSample");

        ctx.close();
    }
}

输出

Setting bytes: [74, 111, 104, 110, 32, 77, 97, 121, 101, 114]
Setting character: A
Setting class: java.lang.String
Setting date: Wed May 03 00:00:00 CST 13
Setting file: test.txt
Setting float value: 123.45678
Setting locale: 英语 (美国)
Setting pattern: a*b
Loaded 1 properties
Setting stream: java.io.BufferedInputStream@4b14918a
Setting string list with size: 2
String member: String member 1
String member: String member 2
Setting trim string: String need trimming
Setting Boolean: true
Setting URL: https://spring.io/

2、自定义 PropertyEditor

package com.luo.spring.guides.propertyeditor.domain;

public class FullName {
    private String firstName;
    private String lastName;

    public FullName(String firstName, String lastName) {
        this.firstName = firstName;
        this.lastName = lastName;
    }

    public String getFirstName() {
        return firstName;
    }

    public void setFirstName(String firstName) {
        this.firstName = firstName;
    }

    public String getLastName() {
        return lastName;
    }

    public void setLastName(String lastName) {
        this.lastName = lastName;
    }

    public String toString() {
        return "First name: " + firstName + " - Last name: " + lastName;
    }
}
package com.luo.spring.guides.propertyeditor.domain;

import java.beans.PropertyEditorSupport;

public class NamePropertyEditor extends PropertyEditorSupport {
    @Override
    public void setAsText(String text) throws IllegalArgumentException {
        //匹配任意空白字符(空格,制表符等)
        String[] name = text.split("\\s");

        setValue(new FullName(name[0], name[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"
    xmlns:p="http://www.springframework.org/schema/p"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd">

    <bean name="customEditorConfigurer"
          class="org.springframework.beans.factory.config.CustomEditorConfigurer">
        <property name="customEditors">
            <map>
                <entry key="com.luo.spring.guides.propertyeditor.domain.FullName"
                       value="com.luo.spring.guides.propertyeditor.domain.NamePropertyEditor"/>
            </map>
        </property>
    </bean>

    <bean id="exampleBean" class="com.luo.spring.guides.propertyeditor.domain.CustomEditorExample"
      p:name="John Mayer"/>
</beans>

测试

package com.luo.spring.guides.propertyeditor.domain;

import org.springframework.context.support.GenericXmlApplicationContext;

/**
 * @author : archer
 * @date : Created in 2022/12/8 19:03
 * @description :
 */
public class CustomEditorExample {

    private FullName name;

    public FullName getName() {
        return name;
    }

    public void setName(FullName name) {
        this.name = name;
    }

    public static void main(String... args) {
        GenericXmlApplicationContext ctx = new GenericXmlApplicationContext();
        ctx.load("classpath:propertyeditor/app-context-02.xml");
        ctx.refresh();

        CustomEditorExample bean =
                (CustomEditorExample) ctx.getBean("exampleBean");

        System.out.println(bean.getName());

        ctx.close();
    }
}

输出

First name: John - Last name: Mayer

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值