Spring框架使用中的ioc操作

2018, Dec 05    

Spring框架使用中的IoC操作

场景1:Spring的bean如果要在实例化过程中修改其某一个成员变量

首先我们知道Bean的生命周期

1.实例化Bean对象:Spring容器根据配置中的bean定义中实例化bean。
2.设置对象属性:Spring 使用依赖注入填充所有属性
3.检查Aware相关接口并设置相关依赖:如果 bean 实现 BeanNameAware 接口,则工厂通过传递 bean 的 ID 来调用 setBeanName()递Bean的ID。
如果 bean 实现 BeanFactoryAware 接口,工厂通过传递自身的实例来调用 setBeanFactory()。
5.BeanPostProcessors前置处理:如果存在与 bean 关联的任何 BeanPostProcessors,则调用 preProcessBeforeInitialization() 方法。 6.调用Bean的初始化方法:如果为bean指定了 init 方法( 的 init-method 属性),那么将调用它。
7.BeanPostProcessors后置处理:如果存在与 bean 关联的任何 BeanPostProcessors,则将调用 postProcessAfterInitialization() 方法。
8.使用Bean

Spring的bean如果要在实例化过程中修改其某一个成员变量让类实现:

// postProcessBeforeInstantiation方法的作用在目标对象被实例化之前调用的方法,可以返回目标实例的一个代理用来代替目标实例
// beanClass参数表示目标对象的类型,beanName是目标实例在Spring容器中的name
// 返回值类型是Object,如果返回的是非null对象,接下来除了postProcessAfterInitialization方法会被执行以外,其它bean构造的那些方法都不再执行。否则那些过程以及postProcessAfterInitialization方法都会执行
Object postProcessBeforeInstantiation(Class<?> beanClass, String beanName) throws BeansException;

// postProcessAfterInstantiation方法的作用在目标对象被实例化之后并且在属性值被populate之前调用
// bean参数是目标实例(这个时候目标对象已经被实例化但是该实例的属性还没有被设置),beanName是目标实例在Spring容器中的name
// 返回值是boolean类型,如果返回true,目标实例内部的返回值会被populate,否则populate这个过程会被忽视
boolean postProcessAfterInstantiation(Object bean, String beanName) throws BeansException;

// postProcessPropertyValues方法的作用在属性中被设置到目标实例之前调用,可以修改属性的设置
// pvs参数表示参数属性值(从BeanDefinition中获取),pds代表参数的描述信息(比如参数名,类型等描述信息),bean参数是目标实例,beanName是目标实例在Spring容器中的name
// 返回值是PropertyValues,可以使用一个全新的PropertyValues代替原先的PropertyValues用来覆盖属性设置或者直接在参数pvs上修改。如果返回值是null,那么会忽略属性设置这个过程(所有属性不论使用什么注解,最后都是null)
PropertyValues postProcessPropertyValues(
    PropertyValues pvs, PropertyDescriptor[] pds, Object bean, String beanName)
    throws BeansException;


总结

    InstantiationAwareBeanPostProcessor接口继承BeanPostProcessor接口它内部提供了3个方法再加上BeanPostProcessor接口内部的2个方法所以实现这个接口需要实现5个方法InstantiationAwareBeanPostProcessor接口的主要作用在于目标对象的实例化过程中需要处理的事情包括实例化对象的前后过程以及实例的属性设置
    postProcessBeforeInstantiation方法是最先执行的方法它在目标对象实例化之前调用该方法的返回值类型是Object我们可以返回任何类型的值由于这个时候目标对象还未实例化所以这个返回值可以用来代替原本该生成的目标对象的实例(比如代理对象)如果该方法的返回值代替原本该生成的目标对象后续只有postProcessAfterInitialization方法会调用其它方法不再调用否则按照正常的流程走
    postProcessAfterInstantiation方法在目标对象实例化之后调用这个时候对象已经被实例化但是该实例的属性还未被设置都是null如果该方法返回false会忽略属性值的设置如果返回true会按照正常流程设置属性值
    postProcessPropertyValues方法对属性值进行修改(这个时候属性值还未被设置但是我们可以修改原本该设置进去的属性值)如果postProcessAfterInstantiation方法返回false该方法不会被调用可以在该方法内对属性值进行修改
    父接口BeanPostProcessor的2个方法postProcessBeforeInitialization和postProcessAfterInitialization都是在目标对象被实例化之后并且属性也被设置之后调用的
    Instantiation表示实例化Initialization表示初始化实例化的意思在对象还未生成初始化的意思在对象已经生成
场景2:在Bean创建后,和Bean销毁前添加逻辑处理
在java的实际开发过程中我们可能常常需要使用到init method和destroy method比如初始化一个对象bean后立即初始化加载一些数据在销毁一个对象之前进行垃圾回收等等 
发现实际上可以有两种方式来实现init method和destroy method

@Bean注解方式 
首先要创建一个至少拥有两个方法的类一个方法充当init method另一个充当destroy method
package springTest2;
public class Test1 {
    public void init() {
        System.out.println("this is init method1");
    }
    public Test1() {
        super();
        System.out.println("构造函数1");
    }
    public void destroy() {
        System.out.println("this is destroy method1");
    }
}
创建好了这个类我们就可以使用@Bean注解的方式指定两个方法以让他们生效
package springTest2;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;@Configuration@ComponentScan("springTest2") public class ConfigTest {@Bean(initMethod = "init", destroyMethod = "destroy") Test1 test1() {
        return new Test1();
    }
}
这里边的@Configguration注解是告诉spring这个类是一个配置类相当于我们的xml文件@ComponentScan则是指定需要spring来扫描的包相当于xml中的context:component-scan属性
@Bean后边的initMethod和destroyMethod就是在声明这是一个baen的同时指定了init和destroy方法方法名从功能实现上来说可以随意
到这里我们就已经用第一种方式写好了为了验证成功与否再写一个main方法验证一下
package springTest2;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
public class MainTest {
    public static void main(String[] args) {
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(ConfigTest.class);
        System.out.println("#################################");
        context.close();
    }
}


JSR-250注解的方式需要导入jsr250-api的jar包
首先依然是创建一个拥有构造方法在内的三个方法的java类
package springTest2;
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
public class Test2 {@PostConstruct public void init() {
        System.out.println("this is init method2");
    }
    public Test2() {
        super();
        System.out.println("构造函数2");
    }@PreDestroy public void destroy() {
        System.out.println("this is destroy method2");
    }
}
很显然这里和上一个类不同的是在init和destroy方法上加入了两个注解@PostConstruct和上边@Bean后的initMethod相同@PreDestroy则是和destroyMethod做用相同
既然这里有了区别已经指定了init method和destroy method那么后边声明bean的时候自然也会有不同也就不需要再指定一遍
package springTest2;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;@Configuration@ComponentScan("springTest2") public class ConfigTest {@Bean Test2 test2() {
        return new Test2();
    }
}
所以如上代码中只需要简单的声明这是一个bean就可以了类上边的两个注解和上一个例子中的意思相同
再测试一下
package springTest2;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
public class MainTest {
    public static void main(String[] args) {
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(ConfigTest.class);
        System.out.println("#################################");
        context.close();
    }
}
场景3:Spring如何动态地加载一个bean到bean容器中,不是通过配置文件配置的?

首先:通过ApplicationContext获得Spring的BeanFactory

applicationContext = ContextLoader.getCurrentWebApplicationContext();
DefaultListableBeanFactory beanFactory = (DefaultListableBeanFactory)applicationContext.getAutowireCapableBeanFactory();

然后,根据obj的类型、创建一个新的bean、添加到Spring容器中

// 根据obj的类型、创建一个新的bean
BeanDefinition beanDefinition = new GenericBeanDefinition();
beanDefinition.setBeanClassName(obj.getClass().getName());
		
// 添加到Spring容器中,方式一(非单例)
beanFactory.registerBeanDefinition(obj.getClass().getName(), beanDefinition);
		
// 添加到Spring容器中,方式二(单例)
applicationContext.getAutowireCapableBeanFactory().applyBeanPostProcessorsAfterInitialization(obj, obj.getClass().getName());
beanFactory.registerSingleton(obj.getClass().getName(), obj);