页面加载中...

Spring中bean初始化时的扩展接口

| Java | 0 条评论 | 700浏览

Preface

先回顾下bean的生命周期: 图片from:http://www.iocoder.cn 如上,容器启动后,从配置文件或者注解读取某个bean的配置,先开始进行bean的实例化,而后根据配置为bean注入属性。接下来就是bean的初始化,主要就是三件事情:

  1. 激活Aware系列接口的方法;
  2. BeanPostProcessor(前后置处理器)的处理操作;
  3. 自定义的初始化方法。 初始化完成,就可以供我们正常使用了。

作为一个优秀的框架,Spring提供了许多扩展接口,可以供我们在bean初始化过程中定制自己的逻辑。所以,本文将介绍在bean初始化的过程中,这些扩展接口的简单使用以及基本原理。

Aware

Aware接口是一个标识接口,接口本身没有任何方法。

/**
 * A marker superinterface indicating that a bean is eligible to be notified by the
 * Spring container of a particular framework object through a callback-style method.
 * The actual method signature is determined by individual subinterfaces but should
 * typically consist of just one void-returning method that accepts a single argument.
 */
public interface Aware {

}

主要是看其各种子接口,这些子接口可以视为回调接口,回调的方法就是接口里面的setXXX方法。

来看下其主要的子类接口: ApplicaitonContextAware、BeanNameAware、BeanFactoryAware、BeanClassLoaderAware (其实可以顾名思义,根据接口名就知道是要给bean set什么样的属性了)

测试Demo

比如,写个测试的bean。(本文的测试样例均通过注解配置bean,下同)

@Component
public class MyApplicationAware implements BeanNameAware,BeanFactoryAware,BeanClassLoaderAware,ApplicationContextAware {

    private String beanName;
    private BeanFactory beanFactory;
    private ClassLoader classLoader;
    private ApplicationContext applicationContext;

    @Override
    public void setBeanClassLoader(ClassLoader classLoader) {
        System.out.println("调用了 BeanClassLoaderAware 的 setBeanClassLoader 方法");
        this.classLoader = classLoader;
    }

    @Override
    public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
        System.out.println("调用了 BeanFactoryAware 的 setBeanFactory 方法");
        this.beanFactory = beanFactory;
    }

    @Override
    public void setBeanName(String name) {
        System.out.println("调用了 BeanNameAware 的 setBeanName 方法");
        this.beanName = name;
    }

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        System.out.println("调用了 ApplicationContextAware 的 setApplicationContext 方法");
        this.applicationContext = applicationContext;
    }

    public void display(){
        System.out.println("beanName:" + beanName);
        System.out.println("classloader:" + classLoader);
        System.out.println("is singleton:" + beanFactory.isSingleton(beanName));
        System.out.println("application context :" + applicationContext.getEnvironment());
    }
}

再写个测试方法:

public static void main(String args[]){
    ApplicationContext context = new AnnotationConfigApplicationContext(MyApplicationAware.class);
    MyApplicationAware bean = context.getBean(MyApplicationAware.class);
    bean.display();
}

结果如下:

调用了 BeanNameAware 的 setBeanName 方法
调用了 BeanClassLoaderAware 的 setBeanClassLoader 方法
调用了 BeanFactoryAware 的 setBeanFactory 方法
调用了 ApplicationContextAware 的 setApplicationContext 方法
beanName:myApplicationAware
classloader:sun.misc.Launcher$AppClassLoader@18b4aac2
is singleton:true
application context :StandardEnvironment {activeProfiles=[], defaultProfiles=[default], propertySources=[PropertiesPropertySource {name='systemProperties'}, SystemEnvironmentPropertySource {name='systemEnvironment'}]}

可以看出,在我们的bean(myApplicationAware)初始化时,我们可以通过实现这些Aware接口回调方法来获取当前的容器的各种属性。

原理

那么Aware是在bean创建的什么时候开始调用回调方法呢? 可以在setXXX回调方法里面断点看看堆栈信息: 可以看出,实在初始化bean(initializeBean)时调用invokeAwareMethods方法,看看invokeAwareMethods方法的源码:

//AbstractAutowireCapableBeanFactory.java
private void invokeAwareMethods(final String beanName, final Object bean) {
		if (bean instanceof Aware) {
			if (bean instanceof BeanNameAware) {
				((BeanNameAware) bean).setBeanName(beanName);
			}
			if (bean instanceof BeanClassLoaderAware) {
				ClassLoader bcl = getBeanClassLoader();
				if (bcl != null) {
					((BeanClassLoaderAware) bean).setBeanClassLoader(bcl);
				}
			}
			if (bean instanceof BeanFactoryAware) {
				((BeanFactoryAware) bean).setBeanFactory(AbstractAutowireCapableBeanFactory.this);
			}
		}
	}

可以看见,如果bean是实现了Aware的某个子接口,就会调用其setXXX方法,将对应属性注入到该bean里面去。

BeanPostProcessor

bean的前后置处理器。它的作用是当bean完成实例化、属性注入后,可以通过该接口再对bean进行一些处理逻辑。它是针对所有容器里的bean。

它有两个方法:

public interface BeanPostProcessor {
	/*
	* 在完成bean初始化之前的操作。也就是在InitializingBean接口之前的操作。
	* */
	@Nullable
	default Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
		return bean;
	}
	/*
	*在完成bean初始化之后的操作
	*/
	@Nullable
	default Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
		return bean;
	}
}

比如我们用将其来检测bean实现的接口,或者对bean再进行一层包装。

测试Demo

比如我们定义两个bean,分别实现不同的接口:

// OtherService.java
@Component
public class OtherService implements Serializable {
}

// UserService.java
@Component
public class UserService implements Runnable{
    @Override
    public void run() {
        System.out.println("UserService is running");
    }
}

接下来自定义我们的BeanPostProcessor:

@Component
public class MyBeanPostProcessor implements BeanPostProcessor  {
	//bean实例化之前进行定义的逻辑处理:针对不同接口进行不同处理
    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        System.out.println(beanName+" begin init");
        if (bean instanceof Runnable){
            ((Runnable)bean).run();
        }
        if (bean instanceof Serializable){
            System.out.println(beanName + " can be serialized");
        }
        return bean;
    }
	//bean实例化之后的逻辑处理
    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        System.out.println(beanName+" init complete");
        return bean;
    }
}

如上,我们在bean实例化之前根据bean所实现的接口的不同分别进行不同的逻辑处理。 写个测试方法:

public static void main(String args[]){
    ApplicationContext context = new AnnotationConfigApplicationContext("com.abreaking.master.spring.ipit.bpp");
    System.out.println("容器启动后,bean初始化后就会执行定义的BeanPostProcessor的方法");
}

启动容器,结果如下:

otherService begin init
otherService can be serialized
otherService init complete
userService begin init
UserService is running
userService init complete
容器启动后,bean初始化后就会执行定义的BeanPostProcessor的方法

原理

同样断点,可以看出 applyBeanPostProcessorsBeforeInitialization的源码如下:

public Object applyBeanPostProcessorsBeforeInitialization(Object existingBean, String beanName)
			throws BeansException {
	Object result = existingBean;
	for (BeanPostProcessor processor : getBeanPostProcessors()) {
		Object current = processor.postProcessBeforeInitialization(result, beanName);
		if (current == null) {
			return result;
		}
		result = current;
	}
	return result;
}

getBeanPostProcessors就是一个List。可以发现,会先获取所有的BeanPostProcessor,而后根据顺序执行这些BeanPostProcessor。

那么问题来了,我要是定义了多个BeanPostProcessor,那是怎么一个顺序呢?或者说我怎么自定义BeanPostProcessor的先后顺序呢? 在ApplicationContext进行refresh时,发现进行了这么一个操作: 可以看到在容器refresh时,会先注册所有的BeanPostProcessor。 registerBeanPostProcessors(beanFactory)源码如下:

//AbstractApplicationContext.java
protected void registerBeanPostProcessors(ConfigurableListableBeanFactory beanFactory) {
	PostProcessorRegistrationDelegate.registerBeanPostProcessors(beanFactory, this);
}
//PostProcessorRegistrationDelegate.java
public static void registerBeanPostProcessors(
			ConfigurableListableBeanFactory beanFactory, AbstractApplicationContext applicationContext) {
	// 先获取到所有的BeanPostProcessor
	String[] postProcessorNames = beanFactory.getBeanNamesForType(BeanPostProcessor.class, true, false);
	
	int beanProcessorTargetCount = beanFactory.getBeanPostProcessorCount() + 1 + postProcessorNames.length;
	beanFactory.addBeanPostProcessor(new BeanPostProcessorChecker(beanFactory, beanProcessorTargetCount));

	// Separate between BeanPostProcessors that implement `PriorityOrdered`,
	// Ordered, and the rest.
	List<BeanPostProcessor> priorityOrderedPostProcessors = new ArrayList<>();
	List<BeanPostProcessor> internalPostProcessors = new ArrayList<>();
	List<String> orderedPostProcessorNames = new ArrayList<>();
	List<String> nonOrderedPostProcessorNames = new ArrayList<>();
	for (String ppName : postProcessorNames) {
		if (beanFactory.isTypeMatch(ppName, PriorityOrdered.class)) {
			BeanPostProcessor pp = beanFactory.getBean(ppName, BeanPostProcessor.class);
			priorityOrderedPostProcessors.add(pp);
			if (pp instanceof MergedBeanDefinitionPostProcessor) {
				internalPostProcessors.add(pp);
			}
		}
		else if (beanFactory.isTypeMatch(ppName, Ordered.class)) {
			orderedPostProcessorNames.add(ppName);
		}
		else {
			nonOrderedPostProcessorNames.add(ppName);
		}
	}

	// First, register the BeanPostProcessors that implement PriorityOrdered.
	sortPostProcessors(priorityOrderedPostProcessors, beanFactory);
	registerBeanPostProcessors(beanFactory, priorityOrderedPostProcessors);

	// Next, register the BeanPostProcessors that implement Ordered.
	List<BeanPostProcessor> orderedPostProcessors = new ArrayList<>(orderedPostProcessorNames.size());
	for (String ppName : orderedPostProcessorNames) {
		BeanPostProcessor pp = beanFactory.getBean(ppName, BeanPostProcessor.class);
		orderedPostProcessors.add(pp);
		if (pp instanceof MergedBeanDefinitionPostProcessor) {
			internalPostProcessors.add(pp);
		}
	}
	sortPostProcessors(orderedPostProcessors, beanFactory);
	registerBeanPostProcessors(beanFactory, orderedPostProcessors);

	// Now, register all regular BeanPostProcessors.
	List<BeanPostProcessor> nonOrderedPostProcessors = new ArrayList<>(nonOrderedPostProcessorNames.size());
	for (String ppName : nonOrderedPostProcessorNames) {
		BeanPostProcessor pp = beanFactory.getBean(ppName, BeanPostProcessor.class);
		nonOrderedPostProcessors.add(pp);
		if (pp instanceof MergedBeanDefinitionPostProcessor) {
			internalPostProcessors.add(pp);
		}
	}
	registerBeanPostProcessors(beanFactory, nonOrderedPostProcessors);

	// Finally, re-register all internal BeanPostProcessors.
	sortPostProcessors(internalPostProcessors, beanFactory);
	registerBeanPostProcessors(beanFactory, internalPostProcessors);

	// Re-register post-processor for detecting inner beans as ApplicationListeners,
	// moving it to the end of the processor chain (for picking up proxies etc).
	beanFactory.addBeanPostProcessor(new ApplicationListenerDetector(applicationContext));
}

可以看出,会先对BeanPostProcessor实现的接口进行判断,主要是两个接口:PriorityOrderedOrdered。根据实现的接口不同,而后分别装入Llist里面,然后再排序。排序的过程比较简单,就是根据接口的getOrder()的大小排序。 所以,我们可以让BeanPostProcessor实现c'v接口,进而定义BeanPostProcessor的执行顺序。

比如:先不去实现PriorityOrdered接口,看下效果:

@Configuration
public class MySecondBeanPostProcessor implements BeanPostProcessor{

    //bean实例化之前进行定义的逻辑处理:针对不同接口进行不同处理
    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        System.out.println(beanName+" before in the MySecondBeanPostProcessor");
        return bean;
    }
    //bean实例化之后的逻辑处理
    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        System.out.println(beanName+ " after in the MySecondBeanPostProcessor");
        return bean;
    }
	//先不实现PriorityOrdered接口,看下效果
    public int getOrder() {
        return Integer.MAX_VALUE;
    }
}

继续使用以上的测试方法,结果如下:

otherService begin init
otherService can be serialized
otherService before in the MySecondBeanPostProcessor
otherService init complete
otherService after in the MySecondBeanPostProcessor
userService begin init
UserService is running
userService before in the MySecondBeanPostProcessor
userService init complete
userService after in the MySecondBeanPostProcessor
容器启动后,bean初始化后就会执行定义的BeanPostProcessor的方法

可以看出,未实现PriorityOrdered接口的MySecondBeanPostProcessor 执行顺序是在MyBeanPostProcessor之后。 为其实现PriorityOrdered接口,再看看效果:

@Configuration
public class MySecondBeanPostProcessor implements BeanPostProcessor,PriorityOrdered {
	...同上
}

结果如下:

myBeanPostProcessor before in the MySecondBeanPostProcessor
myBeanPostProcessor after in the MySecondBeanPostProcessor
otherService before in the MySecondBeanPostProcessor
otherService begin init
otherService can be serialized
otherService after in the MySecondBeanPostProcessor
otherService init complete
userService before in the MySecondBeanPostProcessor
userService begin init
UserService is running
userService after in the MySecondBeanPostProcessor
userService init complete
容器启动后,bean初始化后就会执行定义的BeanPostProcessor的方法

这时MySecondBeanPostProcessor 的执行顺序在MyBeanPostProcessor之前。

InitializingBean

顾名思义,bean初始化的时候的扩展接口。它只有一个方法,表示在bean完成属性注入后,可进行的操作。在上面介绍的BeanPostProcessor,它就是在InitializingBean的方法前后执行的。

public interface InitializingBean {

	/**
	 * Invoked by the containing {@code BeanFactory} after it has set all bean properties
	 * and satisfied {@link BeanFactoryAware}, {@code ApplicationContextAware} etc.
	 * <p>This method allows the bean instance to perform validation of its overall
	 * configuration and final initialization when all bean properties have been set.
	 * @throws Exception in the event of misconfiguration (such as failure to set an
	 * essential property) or if initialization fails for any other reason
	 */
	void afterPropertiesSet() throws Exception;

}

当某个bean的属性设置完之后,我们可以再对该bean进行一些别的操作:比如对其他属性进行填充,对已经注入的属性进行判断等等。

测试Demo

一个简单的demo如下:

@Component
public class MyInitializingBean implements InitializingBean {

    @Value("zhangsan")
    String name;
    @Value("99")
    int age;

    final Map<String,String> cache = new ConcurrentHashMap<>();

    @Override
    public void afterPropertiesSet() throws Exception {
        System.out.println("afterPropertiesSet方法开始,先看看该bean已注入的属性:");
        System.out.println(this);

        System.out.println("进行一些自定义的操作,比如判断age大小,太大的age将其改变下");
        if (age>80){
            System.out.println("发现age大于80了,调整成默认的80");
            this.age = 80;
        }
        cache.put("name",name);
        cache.put("age",String.valueOf(age));
        System.out.println("afterPropertiesSet方法完毕!");
    }

    @Override
    @Override
    public String toString() {
        return "MyInitializingBean{name='" + name + '\'' + ", age=" + age +", cache=" + cache +'}';
    }
}

写个测试方法如下:

public static void main(String[] args) {
    ApplicationContext context = new AnnotationConfigApplicationContext(MyInitializingBean.class);
     MyInitializingBean bean = context.getBean(MyInitializingBean.class);
     System.out.println("bean初始化完成,该bean的属性如下:");
     System.out.println(bean);
 }

结果如下:

afterPropertiesSet方法开始,先看看该bean已注入的属性:
MyInitializingBean{name='zhangsan', age=99, cache={}}
进行一些自定义的操作,比如判断age大小,太大的age将其改变下
发现age大于80了,调整成默认的80
afterPropertiesSet方法完毕!
bean初始化完成,该bean的属性如下:
MyInitializingBean{name='zhangsan', age=80, cache={name=zhangsan, age=80}}

原理

同样在断点的堆栈处看到: 如上,可以看到,在初始化bean时,会先调用我们初始化的方法,invokeInitMethods方法源码如下:

protected void invokeInitMethods(String beanName, final Object bean, @Nullable RootBeanDefinition mbd)
			throws Throwable {
	//1、先判断bean是否有实现InitializingBean接口
	boolean isInitializingBean = (bean instanceof InitializingBean);
	if (isInitializingBean && (mbd == null || !mbd.isExternallyManagedInitMethod("afterPropertiesSet"))) {
		if (logger.isTraceEnabled()) {
			logger.trace("Invoking afterPropertiesSet() on bean with name '" + beanName + "'");
		}
		if (System.getSecurityManager() != null) {
			try {
				AccessController.doPrivileged((PrivilegedExceptionAction<Object>) () -> {
					((InitializingBean) bean).afterPropertiesSet();
					return null;
				}, getAccessControlContext());
			}
			catch (PrivilegedActionException pae) {
				throw pae.getException();
			}
		}
		else {
			((InitializingBean) bean).afterPropertiesSet();
		}
	}
	//2、如果有自指定的init-method方法
	if (mbd != null && bean.getClass() != NullBean.class) {
		String initMethodName = mbd.getInitMethodName();
		if (StringUtils.hasLength(initMethodName) &&
				!(isInitializingBean && "afterPropertiesSet".equals(initMethodName)) &&
				!mbd.isExternallyManagedInitMethod(initMethodName)) {
			invokeCustomInitMethod(beanName, bean, mbd);
		}
	}
}

上述的逻辑不是太复杂,在1处,判断bean是否有实现InitializingBean接口,如有,则调用其afterPropertiesSet方法。

BUT!我们发现在2处,貌似还有别的操作:如果即不是InitializingBean的子类也没得afterPropertiesSet方法,并且有自己initMethodName,则执行invokeCustomInitMethod方法。 先来看看invokeCustomInitMethod怎么做的操作,篇幅原因,贴下主要逻辑代码

/AbstractAutowireCapableBeanFactory.java
protected void invokeCustomInitMethod(String beanName, final Object bean, RootBeanDefinition mbd)
			throws Throwable {
		//bean的初始化方法名
		String initMethodName = mbd.getInitMethodName();
		...
		//方法名对应的实际方法
		Method initMethod = (mbd.isNonPublicAccessAllowed() ?
				BeanUtils.findMethod(bean.getClass(), initMethodName) :
				ClassUtils.getMethodIfAvailable(bean.getClass(), initMethodName));

		if (initMethod == null) {
			...
		}

		if (logger.isTraceEnabled()) {
			logger.trace("Invoking init method  '" + initMethodName + "' on bean with name '" + beanName + "'");
		}
		Method methodToInvoke = ClassUtils.getInterfaceMethodIfPossible(initMethod);

		if (System.getSecurityManager() != null) {
			...
		}
		else {
			try {
				//然后通过反射的方式手动调用该初始化方法
				ReflectionUtils.makeAccessible(methodToInvoke);
				methodToInvoke.invoke(bean);
			}
			catch (InvocationTargetException ex) {
				throw ex.getTargetException();
			}
		}
	}

上述逻辑比较简单,如果该bean有指定的initMethod,则会去调用该initMethod。

那什么是initMethod呢? 这是spring初始化bean的第二个机制,叫做init-method。我们可以在bean的配置中自指定初始化的方法,它的作用同InitializingBean接口。

通过在配置bean时指定init-method,一个好处就是可以让我们的业务代码跟spring解耦,不会对spring依赖太多。 比如:我们先创建一个简单的类:

public class MyInitializingBeanWithInitMethod {

    String name;
    Integer age;

    final Map<String,String> cache = new ConcurrentHashMap<>();

    /**
     * 自定义的初始化方法。
     * 最终效果同InitializingBean接口的afterPropertiesSet方法
     * @throws Exception
     */
    public void myInitMethod() throws Exception {
        System.out.println("afterPropertiesSet方法开始,先看看该bean已注入的属性:");
        System.out.println(this);
        System.out.println("应该都是空的,这时我们可以手动来注入下属性");
        this.name = "lisi";
        this.age = 19;

        cache.put("name",name);
        cache.put("age",String.valueOf(age));
        System.out.println("afterPropertiesSet方法完毕!");
    }
    @Override
    public String toString() {
        return "MyInitializingBeanWithInitMethod{name='" + name + '\'' + ", age=" + age +", cache=" + cache +'}';
    }
}

如上述业务代码,很明显没有依赖Spring的任何接口。我们再指定该对象的bean的创建方式以及初始化的方法。 tip:这里使用ConfigurationBean注解进行bean的实例化,bean注解中通过配置initMethod来指定初始化方法。

@Configuration
public class MyInitializingConfiguration {
    /**
     * bean的创建时指定init-method方法
     * @return 实例化后的bean
     */
    @Bean(initMethod = "myInitMethod")
    public MyInitializingBeanWithInitMethod bean(){
        return new MyInitializingBeanWithInitMethod();
    }
}

再写个测试方法看看:

public static void main(String[] args) {
    AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(MyInitializingConfiguration.class);
    MyInitializingBeanWithInitMethod bean = context.getBean(MyInitializingBeanWithInitMethod.class);
    System.out.println(bean);
}

同样达到InitializingBean 接口的效果:

myInitMethod方法开始,先看看该bean已注入的属性:
MyInitializingBeanWithInitMethod{name='null', age=null, cache={}}
应该都是空的,这时我们可以手动来注入下属性
myInitMethod方法完毕!
MyInitializingBeanWithInitMethod{name='lisi', age=19, cache={name=lisi, age=19}}

所以,针对bean初始化方法,可以根据实际业务情况进行选择。不过现在基本上都是spring的天下了,与spring解耦意义也不大。个人认为:接口的方式还是更加简单明了些。

参考文章

发表评论

最新评论

    来第一个评论吧!