Step by Step into Spring(IOC)

1. IOC容器

spring的IOC容器是Spring的基础组件,其主要用于管理bean之间的关系,其将bean之间的依赖注入交由Spring来处理,我们只需要维护好对象之间的依赖关系,而具体对象的产生和引用对象调用set方法进行注入并需要我们关注。

类中依赖关系的定义:
public class BeanA {
    private BeanB beanB;

    public void print(){
        System.out.println("This is beanA");
        beanB.print();
    }

    public BeanB getBeanB() {
        return beanB;
    }

    public void setBeanB(BeanB beanB) {
        this.beanB = beanB;
    }
}
public class BeanB {
    public void print(){
        System.out.println("This is beanB");
    }
}
public class RunCode {
    public static void main(String[] args){
        ClassPathXmlApplicationContext beanFactory=new ClassPathXmlApplicationContext("applicationContext-beans.xml");
        BeanA beanA= (BeanA) beanFactory.getBean("beanA");
        beanA.print();
        }
}
配置文件(主要是用于描述依赖关系并告诉beanFactory示例化指定的bean:
<bean id="beanB" class="com.job.search.blog.code.BeanB"></bean>
<bean id="beanA" class="com.job.search.blog.code.BeanA">
    <property name="beanB" ref="beanB"></property>
</bean>

如上代码存在以上几个好处:

  1. .所有的类(bean)的对象不要我们去显示的实例化,只需要配置好xml文件中的bean,告诉IOC容器去实例化bean.因此我们已经实例化了哪些对象可以一目了然
  2. 在定义好对象之间的依赖关系(类文件中),我们只需要在xml文件中重新描述下依赖关系,因为IOC容器是依赖于xm来发现对象间的关系,在你自己定义的类关系IOC容器并不知道。对于对象之间关系的设置完全由IOC容器来注入,省去了我们对set方法的调用
  3. 由于在xml文件中的bean都是全局对象,因此其可以在不同的Bean之间共享,从而减少了内存的占用和实例化对象的时间。
  4. 在java后端开发中大部分的bean都是用于描述方法的,其数据大部分存在于数据库中,因此很少存在多线程间对象同步的问题。
  5. 如果是利用spring的MVC的注解功能,完全可以去除掉xml中bean声明的使用和依赖关系的使用。
MVC注解版:这里只是告诉你代码能够多简洁,MVC的具体内容会在以后章节介
@Service
public class BeanA {
    @Resource
    private BeanB beanB;

    public void print(){
        System.out.println("This is beanA");
        beanB.print();
    }

    public BeanB getBeanB() {
        return beanB;
    }

    public void setBeanB(BeanB beanB) {
        this.beanB = beanB;
    }
}
@Service
public class BeanB {
    public void print(){
        System.out.println("This is beanB");
    }
}

2.IOC容器的初始化

IOC容器作为Spring的基础构件,其主要作用是用于管理bean的整个生命周期,其初始化构成主要由:xml文件的定位、BeanDefinition载入和解析、BeanDefinition的注册和bean的预初始化这三个阶段。

2.1 IOC容器配置文件定位

针对于不同类型的项目,BeanFactory有不同的子类来实现配置文件的定位过程:

BeanFactory作用
FileSystemXmlApplicationContext根据传入的文件路径定位xml文件
ClassPathXmlApplicationContext在工程编译classes文件下查找xml
XmlWebApplicationContext在web项目的classes下查找配置文件

其本质上xml文件的定位是通过其中配置的不同的Resource子类来定位不同位置的xml.

2.2 BeanDefinition载入和解析

BeanDefinition是包含了要创建bean时的相关信息的一个类,如要创建bean时使用的类、属性、其他bean的引用等。其来源是通过对传入的xml中配置的bean或者注解(注解主要用于标识在类上表明这个类将要作为一个bean,可以在编译的时候通过反射来产生)来得到的。在解析Xml中的Bean信息时,主要是通过BeanFactory中配置的XmlBeanDefinitionReader来解析指定的XML,其中解析XML的规则可以通过XML配置文件上的配置头来获得,如:xmlns=”http://www.springframework.org/schema/beans”。具体的解析过程可以从Spring的源码中看到。该部分会解析得到Bean的依赖关系,并存放在BeanDefinition中,会后面通过BeanDefinition实例化Bean的对象做准备

2.3 BeanDefinition注册和bean预初始化

BeanDefinition注册主要是指通过将解析得到的BeanDefinition注册到BeanFactory中的Map和List中,为后续通过bean名称、bean的类型检索得到BeanDefinition。Bean初始化是发生在第一次调用getBean时,对于lazy-init=’false’(默认)的bean,在注册BeanDefinition的过程中会进行预初始化(即调用getBean方法),对于单例的bean(默认)会被缓存于BeanFactory中,下次再获取该类型或者该名字的Bean时,获得就是被缓存的Bean。如果想每次getBean时获得新的Bean可以将Bean中scope=”prototype”(不建议)。因此大部分的bean都在beanFactory的初始化的过程中就已经被实例化,并且在后续的时候中都会使用该bean。同时在Bean的实例化的过程中会调用依赖注入bean的getBean方法

3.Bean生命周期

Bean生命周期图
Bean从调用getBean方法开始到实例化一个Bean对象为止,根据其Bean实现的Spring的接口的不同,会经过几个不同的初始化方法的调用。一般的Bean并不会实现Spring的接口,因此其和正常的对象实例化没有区别。但是一部分Bean需要同Spring耦合,从而可以控制器初始化中的一些行为,而该部分的Bean经常是有特殊作用的Bean。

介绍一个常需要在Bean实现Spring初始化接口,来控制Bean的初始化过程的情形:

@Service
public class BgcCheckHandler extends AbstractBgcHandler {
    @Resource
    private List<AbstractFieldRushFilter> filedRushFilters;
    @PostConstruct
    public void sortRushFilter(){
        Collections.sort(filedRushFilters, new Comparator<AbstractFieldRushFilter>() {
            @Override
            public int compare(AbstractFieldRushFilter o1, AbstractFieldRushFilter o2) {
                if(o1.getPriority()>o2.getPriority()){
                    return 1;
                }else if(o1.getPriority()<o2.getPriority()){
                    return -1;
                }
                return 0;
            }
        });
    }
}

如上述代码描述:在Spring的注解中对于List会将所有继承于AbstractFieldRushFilter的类都自动注解到该容器中,然而其注解的顺序是无序的,除了将AbstractFieldRushFilter实现Comparable接口,并重写其compareTo用于排序AbstractFieldRushFilter的子类和使用TreeSet这样的有序容器外,我们常用的方法是调用Collections.sort方法来重新排序List元素.因此我们必须要有一个在Bean构造的时候(在可以正常使用之前)有一个方法来控制Bean的初始化过程。上述代码是使用的@PostConstruct注解,该注解实际上是会调用CommonAnnotationBeanPostProcessor类,该类是BeanPostProcessor接口的实现类。如上只是一个比较常见的应用场景,其实对于Bean构造过程的控制,更长用于与Spring结合的其他第三方构件。Ok,下面我们来介绍下控制Bean过程中可以使用的一些接口。(对于第三方构件控制Bean初始化的构成可以参加我另一篇文章:Qrtz定时任务框架)
第一个接口:BeanPostProcessor不予特定Bean结合,是一个单独的Bean适合于所有的Bean,常用记录日志等方法。
/**
将实现该接口的类,配置与xml文件中,该类会在所有的Bean被构造的过程中调用,我们可以根据得到的Bean对象和名字参数(Object bean,String beanName)来控制一些指定类型或者名字的bean进行特殊处理,如上述代码中的@PostConstruct就会在CommonAnnotationBeanPostProcessor中调用其标注的方法。@PostConstruct标注方法调用时机和postProcessBeforeInitialization调用时机相同
注:另外有个@PreDestory这个是在Bean从BeanFactory中移除之前调用的方法
*/

public interface BeanPostProcessor {

    /**
    BeanPostProcessor预初始化方法
     */
    Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException;

    /**
    BeanPostProcessor初始化完成之后调用的方法
     */
    Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException;
}
例子:
public class MyPostProcessor implements BeanPostProcessor {
    private final static Logger LOGGER= LoggerFactory.getLogger(MyPostProcessor.class);
    @Override
    public Object postProcessAfterInitialization(Object bean, String paramString) throws BeansException {
        //这里对每个bean进行具体的构造过程控制
        LOGGER.info(beanName+"开始初始化");
        return bean;
    }

    @Override
    public Object postProcessBeforeInitialization(final Object bean, String beanName) throws BeansException {
        LOGGER.info(beanName+"初始化完成,可以正常使用该bean");
        return bean;
    }
}
//在xml中如下配置将会对所有Bean的构造过程进行处理
<bean id="loggerPostBean" class="com.job.search.blog.code.MyPostProcessor"></bean>

第二个接口:InitializingBean应用于指定bean上,通过在实现该接口,从而在Bean的构造过程会调用该接口的afterPropertiesSet()方法。

public interface InitializingBean {
    void afterPropertiesSet() throws Exception;
}
例子:
class MyBeanA implements InitializingBean{
    //to do Bean的一些其他代码和方法
    @Override
    public void afterPropertiesSet() throws Exception {
         //to do 输入对该Bean构造过程的具体代码
    }
 }
 注:另一个对应于Bean销毁时的接口是DisposableBean

第三种方式在Bean中配置init-method属性,通过在xml中bean的声明中加入init-method属性,并设置为bean中的一个方法,该方法会在Bean初始化的过程中调用

class MyBeanB{
    //to do Bean的一些其他代码和方法

    public void initMethod() {
         //to do 输入对该Bean构造过程的具体代码
    }
 }
 <bean id="loggerPostBean" class="com.job.search.blog.code.MyPostProcessor" init-method="initMethod"></bean>
 相对的销毁方法是destroy-method

除了上述几种Bean在初始化控制方式外,对于单例bean还可以通过继承LifeCycle接口来进行初始化和销毁操作。实现了该接口的Bean会监听Spring对该Bean的操作,如Spring调用refresh时调用该接口的start方法,在Spring移除该Bean时,调用该Bean的stop方法。详解参加第6小节,该类方法常用于中间件中(例如:Qrtz、MQ进行轮询以及美团MQ通过在指定机器启动特定的消费者,从而达到在本地调试异步消息的作用)

4.Bean属性

Bean默认属性:

<bean id="personService" class="com.job.search.blog.code.BeanA" scope="singleton" lazy-init="false"></bean>

表示该Bean是单例(每次getBean时返回同一个对象),并且lazy-init=”false”表示会在容器启动过程中自动创建Bean.这个是大部分Bean都采用的属性,同时也是注解时使用的属性
Bean其他属性:

**id**: Bean的唯一标识名。它必须是合法的XML ID,在整个XML文档中唯一。

**name**: 用来为id创建一个或多个别名。它可以是任意的字母符合。多个别名之间用逗号或空格分开。

**class**: 用来定义类的全限定名(包名+类名)。只有子类Bean不用定义该属性。

**abstract**(默认为”false”):用来定义Bean是否为抽象Bean。它表示这个Bean将不会被实例化,一般用于父类Bean,因为父类Bean主要是供子类Bean继承使用。

**singleton**(默认为“true”):定义Bean是否是Singleton(单例)。如果设为“true”,则在BeanFactory作用范围内,只维护此Bean的一个实例。如果设为“flase”,Bean将是Prototype(原型)状态,BeanFactory将为每次Bean请求创建一个新的Bean实例。

**lazy-init**(默认为“default”):用来定义这个Bean是否实现懒初始化。如果为“true”,它将在BeanFactory启动时初始化所有的Singleton Bean。反之,如果为“false”,它只在Bean请求时才开始创建Singleton Bean。

**autowire**(自动装配,默认为“default”):它定义了Bean的自动装载方式。
 1.  “no”:不使用自动装配功能。
 2. “byName”:通过Bean的属性名实现自动装配。
 3. “byType”:通过Bean的类型实现自动装配。
 4. “constructor”:类似于byType,但它是用于构造函数的参数的自动组
 5. “autodetect”:通过Bean类的反省机制(introspection)决定是使用“constructor”还是使用“byType”。

**dependency-check**(依赖检查,默认为“default”):它用来确保Bean组件通过JavaBean描述的所以依赖关系都得到满足。在与自动装配功能一起使用时,它特别有用。
 1. none:不进行依赖检查。
 2. objects:只做对象间依赖的检查。
 3. simple:只做原始类型和String类型依赖的检查
 4. all:对所有类型的依赖进行检查。它包括了前面的objects和simple。

**depends-on**(依赖对象):这个Bean在初始化时依赖的对象,这个对象会在这个Bean初始化之前创建。

**init-method**:用来定义Bean的初始化方法,它会在Bean组装之后调用。它必须是一个无参数的方法。

**destroy-method**:用来定义Bean的销毁方法,它在BeanFactory关闭时调用。同样,它也必须是一个无参数的方法。它只能应用于singleton Bean。

**factory-method**:定义创建该Bean对象的工厂方法。它用于下面的“factory-bean”,表示这个Bean是通过工厂方法创建。此时,“class”属性失效。

**factory-bean**:定义创建该Bean对象的工厂类。如果使用了“factory-bean”则“class”属性失效。

其默认值default的配置在:spring-beans包中的spring-beans.dtd中如下:
<!ATTLIST beans default-lazy-init (true | false) "false">
<!ATTLIST beans default-autowire (no | byName | byType | constructor | autodetect) "no">
<!ATTLIST beans default-dependency-check (none | objects | simple | all) "none">
<!ATTLIST beans default-init-method CDATA #IMPLIED>
<!ATTLIST beans default-destroy-method CDATA #IMPLIED>

5.可感知Bean

Bean除了可以控制初始化过程、Bean的属性配置以外,Bean还可以感知到Spring的坏境,并可以通过得到Spring的BeanFactory来实现各种特色化的属性,如取得Bean的名字,列出BeanFactory中所有bean,获取指定的Bean等。其主要是通过实现如下几个接口 来完成。

获取bean在BeanFactory中的名字:
public interface BeanNameAware extends Aware {
    void setBeanName(String name);
}
获取Bean的上下文内容:
public interface BeanFactoryAware extends Aware {
    void setBeanFactory(BeanFactory var1) throws BeansException;
}
如果是web环境使用:
public interface ApplicationContextAware extends Aware {
    void setApplicationContext(ApplicationContext var1) throws BeansException;
}
列子:
@Service
public class BeanB implements BeanNameAware,BeanFactoryAware{
    private String beanName;
    private BeanFactory beanFactory;
    @override
    void setBeanName(String name){
         beanName=name;
    }
    @override
    void setBeanFactory(BeanFactory var1) throws BeansException{
         beanFactory=var1;
    }
}

从上面的Bean初始化顺序图中可以看出这两个方法会先于用户自定义的初始化方法先运行,从而使得用于可以将Spring注入的BeanFactory对象得到,来进行一些特色化的处理。

6.Lifecycle接口控制Bean的启动和关闭

对于一个Bean的初始化或者销毁除了上面介绍的几种接口外,还可以通过监听Spring的事件来对Bean进行初始化。当Spring调用refresh方法启动BeanFactory的过程中,如果实现了SmartLifecycle接口的类,并且isAutoStartup返回结果为true时,会调用该bean的start方法。当Bean从容器中移除时会触发stop方法,如果isRunning返回结果为true,那么还会接着调用stop(Runnable arg0)方法。getPhase返回的值决定启动的顺序,值越小越先启动,越后关闭,用于调整在有多个这样的bean的时候,这些bean之间启动的优先级.

public class BeanA implements SmartLifecycle{
    private static final Logger log = LoggerFactory.getLogger(BeanA.class);
    private boolean isRunning = false;
    @Override
    public boolean isAutoStartup() {
        // TODO Auto-generated method stub
        return true;
    }

    @Override
    public void stop(Runnable arg0) {
        arg0.run();
        isRunning = false;
        log.info("stop Runnable");
    }

    @Override
    public boolean isRunning() {
        // TODO Auto-generated method stub
        return isRunning;
    }
    @Override
    public void start() {
        isRunning = true;
        log.info("start ");
        //to do init code
    }
    @Override
    public void stop() {
        isRunning = false;
        log.info("stop");
        //to do destroy code
    }
    @Override
    public int getPhase() {
        return -1;
    }
}

注意:SmartLifeCycle和其他一些Bean初始化方法的不同点是其他初始化方法是在Bean的构建过程中调用,而SmartLifeCycle的start方法是在所有的bean被构造完成之后,该Bean使用之前调用,如此是保证一些Spring的资源已经加载完成。所以该接口经常使用在中间件中,如MQ、Qrtz。

7.中间件与Spring结合

最后,我们将讲一个中间件与Spring结合,通过Qrtz和Cron语法搭建的一个定时任务框架。在该框架中我们将用到Bean初始化过程控制、Lifecycle接口监控Spring的启动和关闭,以及FactoryBean的知识。
链接:

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值