spring快速教程(二):ioc容器及bean的配置

小TOT 创建于 2017-02-18

spring的核心为IOC控制反转也叫依赖注入。一个面向对象的程序运由对象组成,spring通过ioc帮助我们简化对象的管理,这使得我们更多经历去关注业务。前面介绍到ioc可以将其看成是一个升级版本的对象工厂,对象的创建通过配置进程创建。spring在早期的版本只提供了基于xml的配置,后来加入基于注解的配置。随着软件复杂度的增加,配置文件的内容越来越多,配置文件越来越复杂,为了解决这一问题,spring提供了代码配置。spring快速教程(一):为什么要使用spring 文章中的代码示例就使用了基于代码的配置。也许你会问,那这样岂不是由我们自己写代码创建对象了吗?spring的意义由何在呢?其实在IOC中对象的创建只是一个非常基础简单的功能,最重要的还是对象之间的依赖管理,和对象生命周期的管理。

ioc容器

spring 提供的核心功能是依赖管理,前面提到可以将ioc模式看成是工厂模式的升级版。在spring中被管理的对象被称为bean(豆子),存放bean的容器我们称之为ioc容器。IoC容器就是具有依赖注入功能的容器,IoC容器负责实例化、定位、配置应用程序中的对象以及建立这些对象间的依赖。应用程序无需直接在代码中new相关的对象,应用程序对象的依赖由IoC容器进行组装。在Spring中BeanFactory接口是IoC容器的实际代表者,在spring中提供类许多实现类,常见的有:ClassPathXmlApplicationContext,FileSystemXmlApplicationContext,XmlBeanFactory,AnnotationConfigApplicationContext。还有一个重要的接口,applicationContext,我们称他为上下文接口,该接口继承类了BeanFactory,同时还继承类其他接口,丰富了ioc容器的功能。spring中使用ApplicationContext接口作为接口来作为ioc容器的。BeanFactory接口定义了ioc容器的基本功能,ApplciationContext可以看成是一个符合jAVA EE开发的IOC容器。

spring ioc容器

BeanFactory接口提供的方法:

public interface BeanFactory {
    String FACTORY_BEAN_PREFIX = "&";

    Object getBean(String var1) throws BeansException;

    <T> T getBean(String var1, Class<T> var2) throws BeansException;

    <T> T getBean(Class<T> var1) throws BeansException;

    Object getBean(String var1, Object... var2) throws BeansException;

    <T> T getBean(Class<T> var1, Object... var2) throws BeansException;

    boolean containsBean(String var1);

    boolean isSingleton(String var1) throws NoSuchBeanDefinitionException;

    boolean isPrototype(String var1) throws NoSuchBeanDefinitionException;

    boolean isTypeMatch(String var1, ResolvableType var2) throws NoSuchBeanDefinitionException;

    boolean isTypeMatch(String var1, Class<?> var2) throws NoSuchBeanDefinitionException;

    Class<?> getType(String var1) throws NoSuchBeanDefinitionException;

    String[] getAliases(String var1);
}

BeanFatory,ApplicationContext接口的关系图如下: beanfactory-applicationContext

  • ApplicationEventPublisher接口提供类事件发布功能
  • ResourceLoader提供类获取资源的能力,无论是网络资源本地文集资源。
  • EnvironmentCapable提供了程序环境变量。比如开发环境,和生产环境,以不同的配置来运行程序。

容器里bean的配置可以使用 XML配置, java注解配置, 或者java代码配置.下面就来简单看一下bean的配置。

基于xml的配置

下面是spring使用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"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd">

    <bean id="..." class="...">
        <!-- collaborators and configuration for this bean go here -->
    </bean>

    <bean id="..." class="...">
        <!-- collaborators and configuration for this bean go here -->
    </bean>

    <!-- more bean definitions go here -->

</beans>

使用xml进行bean的配置当时,当一个配置文件中的配置过多时,管理起来难免繁琐,这个时候可以考虑将配置按照一定维度分成多个。 这是我们可以使用import命令。模板如下:

<beans>
    <import resource="services.xml"/>
    <import resource="resources/messageSource.xml"/>
    <import resource="/resources/themeSource.xml"/>

    <bean id="bean1" class="..."/>
    <bean id="bean2" class="..."/>
</beans>

下面是一些bean的配置实例供参考:

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

    <!-- services -->

    <bean id="petStore" class="org.springframework.samples.jpetstore.services.PetStoreServiceImpl">
        <property name="accountDao" ref="accountDao"/>
        <property name="itemDao" ref="itemDao"/>
        <!-- additional collaborators and configuration for this bean go here -->
    </bean>

     <bean id="accountDao"
        class="org.springframework.samples.jpetstore.dao.jpa.JpaAccountDao">
        <!-- additional collaborators and configuration for this bean go here -->
    </bean>

    <bean id="itemDao" class="org.springframework.samples.jpetstore.dao.jpa.JpaItemDao">
        <!-- additional collaborators and configuration for this bean go here -->
    </bean>

<bean id="serviceLocator" class="examples.DefaultServiceLocator">
    <!-- inject any dependencies required by this locator bean -->
</bean>

<bean id="clientService" factory-bean="serviceLocator" factory-method="createClientServiceInstance"/>

<bean id="accountService" factory-bean="serviceLocator" factory-method="createAccountServiceInstance"/>

<bean id="foo" class="x.y.Foo">
    <constructor-arg ref="bar"/>
    <constructor-arg ref="baz"/>
</bean>

<bean id="bar" class="x.y.Bar"/>
<bean id="baz" class="x.y.Baz"/>

<bean id="exampleBean" class="examples.ExampleBean">
    <constructor-arg type="int" value="7500000"/>
    <constructor-arg type="java.lang.String" value="42"/>
</bean>
    <!-- more bean definitions for services go here -->

</beans>

bean配置的一些属性说明可以参照bean overview.

使用注解配置bean

前面提到,引入注解配置bean是为了简化配置。但是加入注解配置会污染代码,修改bean的配置需要修改java源代码,代码需要重新编译才能生效。因此不能绝对的说注解的方式配置具有绝对的优势,这取决于我们的个人偏好和现实的实际场景。还好基于注解的配置和xml配置可以混合使用,因此我们可以灵活选择。

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: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/>
</beans>

在spring的配置文件中加入<context:annotation-config/>表明spring 启用基于注解的配置。该注解主要帮助spring上下文处理基于注解的依赖注入,即在配置对象时我们无需在对对象的各个属性进行配置,只需要在相应的类里面加入依赖注入相关的注解,spring自动的帮助我们完成属性的的依赖注入,无法完成基于注解的对象创建。下面请看代码示例:

public class MockDao {
    public void insert(){
        System.out.print("insert some thing");
    }
}

public class TestService {

    @Resource
    private MockDao mockDao;

    public void doSomething(){
        mockDao.insert();
        System.out.print("test Service do something...");
    }

}

 public static void main(String[] args) {
        BeanFactory beanFactory = new ClassPathXmlApplicationContext("service.xml");

        TestService bean = beanFactory.getBean(TestService.class);
        bean.doSomething();
    }

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: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/>
    <!--<context:component-scan base-package="cn.jframe"/>-->
    <bean class="cn.jframe.service.TestService"></bean>
    <bean class="cn.jframe.dao.MockDao"></bean>

</beans>

运行程序控制台输出如下:

insert some thingtest Service do something...

删除xml配置中的<context:annotation-config/>,运行代码得到空指针异常:

Exception in thread "main" java.lang.NullPointerException
    at cn.jframe.service.TestService.doSomething(TestService.java:19)
    at cn.jframe.Application.main(Application.java:18)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:498)
    at com.intellij.rt.execution.application.AppMain.main(AppMain.java:147)

<context:annotation-config/>注解只能帮助bean完成依赖处理,可以对加了@Resource注解的属性自动注入依赖(当然也可以至此其他注解,详情查看官方文档)。可以看到xml配置中TestService中bean的配置并没有指定MockDao属性,但由于开启了<context:annotation-config/>,可以通过注解配置自动注入依赖。可以看到取消<context:annotation-config/>后,基于注解完成依赖配置的功能便消失。需要说明的是,依赖的bean必须是在该上下文,或者说该beanFactory中能够找到才行。例如在通常的spring web项目中,会存在多个上下文,一个用来配置Service,dao层相关的父上下文,以及DispatcherServlet中的WebApplicationContext上下文。在spring mvc项目中,后者继承自前者,前者称为父上下文,后者称为子上下文,子上下文可以继承全部父上下文所有内容,在父上下文中无法获取到子上下文的bean。因此在依赖注入时,无论是基于xml的配置,还是基于<context:annotation-config/>的配置候一定要注意,依赖的对象是否在该上下文中是否能够找到。详细内容请参考7.9 Annotation-based container configuration

纯注解配置bean

在上一节讲到添加<context:annotation-config/>只能帮助bean完成依赖的注意,一般还需要和<context:component-scan base-package="cn.jframe"/>配合使用。在创建的bean的类上添加@org.springframework.stereotype.Service,@org.springframework.stereotype.Repository,org.springframework.stereotype.Component注解,并配置上面的Componet-scan,这样就能将指定package下面的添加以上注解的类自动注册成bean。

这一节,我们要讲的是纯注解的方式配置bean。在TestService添加@Service注解,在MockDao添加@Repository注解,上下文工厂使用AnnotationConfigApplicationContext。且看下面代码:

public class Application {

    public static void main(String[] args) {
//        BeanFactory beanFactory = new ClassPathXmlApplicationContext("service.xml");

        BeanFactory beanFactory = new AnnotationConfigApplicationContext("cn.jframe");
        TestService bean = beanFactory.getBean(TestService.class);
        bean.doSomething();
    }
}

使用java代码配置bean

基于java代码的配置是为了摆脱对xml配置的依赖,开发者常常抱怨spring的xml配置过于繁琐,由于基于注解的配置在灵活性方面无法和xml配置相提并论,于是spring推出了基于java代码的配置。基于java的配置与基于注解的配置同的缺点是,代码侵入和修改bean和需要重新编译。不过以上两个缺点其实并不明显,试想一下,你连对象的结构都修改了,从新编译一下又会多花多长时间呢。

基于java代码的配置可以看成是,将xml的配置方式搬到了java中来了,然后使用AnnotationConfigApplicationContext进行上下文的初始化。例如下面两个配置效果相同。

基于java代码的配置:


@Configuration
public class AppConfig {

    @Bean
    public MyService myService() {
        return new MyServiceImpl();
    }

}

基于xml配置:

<beans>
    <bean id="myService" class="com.acme.services.MyServiceImpl"/>
</beans>

同样的基于代码的配置也有类似xml<context:component-scan base-package="cn.jframe"/>的功能。下面两个配置效果等同。

@Configuration
@ComponentScan(basePackages = "cn.jframe")
public class AppConfig {
}

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: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/>
    <context:component-scan base-package="cn.jframe"/>

</beans>

可以看到基本上可以使用java代码配置完全替换xml配置。下面就对上节的代码就行相应的改造。

添加AppConfig类

package cn.jframe.config;

import cn.jframe.dao.MockDao;
import cn.jframe.service.TestService;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;

/**
 * Created by TOT on 2017/2/18.
 */
@Configuration
//@ComponentScan(basePackages = "cn.jframe")
public class AppConfig {

    @Bean
    TestService testService(){
        TestService testService = new TestService();
        return testService;
    }

    @Bean
    MockDao mockDao(){
        return new MockDao();
    }
}

Application.java

public class Application {

    public static void main(String[] args) {
//        BeanFactory beanFactory = new ClassPathXmlApplicationContext("service.xml");

//        BeanFactory beanFactory = new AnnotationConfigApplicationContext("cn.jframe");
        BeanFactory beanFactory = new AnnotationConfigApplicationContext(AppConfig.class);
        TestService bean = beanFactory.getBean(TestService.class);
        bean.doSomething();
    }
}

关于基于java代码的配置,更多详情可以参照官方文档:7.12 Java-based container configuration

总结

spring的bean的配置属于使用spring的基本技能,这是使用spring的第一步,需要将我们自己手功能的创建对象的思想给扭转过来,理解IOC控制反转的思想,并理解其给我们的程序带来的好处。以上简单演示了使用spring配置bean的一些基本方法。以上的示例可以满足一帮场景,如果有特殊的需求可以参考官方文档,对于不同的场景官方文档都有详细介绍。简易教程旨在为大家做一个抛砖引玉的作用。