美文网首页
0x02.IoC容器配置

0x02.IoC容器配置

作者: 0x70e8 | 来源:发表于2018-07-12 22:20 被阅读0次

[TOC]

Spring配置方案

(不仅仅是Ioc的配置)
从前文容器的具体实现已经知道,配置容器或者说配置应用上下文有多种方式,如Java类,来自文件系统或来自classpath的的xml文件,还有Spring基于注解的自动配置(依赖前两种开启自动扫描)。

总结下来,对于Spring最核心和最基础的自动装配功能(集成依赖注入),主要有以下三种装配机制:

  • 在XML中显式配置
  • 在java类中显式配置
  • 隐式bean发现机制(自动扫描装配)

三种方式并非互斥,甚至三者可以共同使用。
以下记录三种方式配置IOC容器。

自动化装配

自动化装配的思路是:

  1. 指明要被容器管理的bean
  2. bean之间的依赖关系

定义Bean的方式和定义bean之间的依赖注入都有两种方式:基于xml和基于注解。也就是说可以使用xml或注解来定义bean,可以使用xml或注解来定义bean之间的依赖注入关系。这两个是分开的,你可以使用xml定义所有的bean,然后使用注解的形式来注入依赖(需要开启注解配置),也可以使用注解来定义bean、然后使用注解来定义依赖注入。(没有使用注解定义bean再使用xml定义依赖注入这种方式);

定义bean的方式是在Spring读取配置元数据这个时期需要处理的事情,所以和这个过程相关的是取得BeanDefinition的过程,是和BeanFactoryPostProcessor相关的;而注入依赖是BeanPostProcessor做的事情,BeanPostProcessor是在bean实例化之后介入bean的生命周期,为对象装配属性;构造器注入的bean在实例化的时候调用的是有参构造器,构造器参数肯定在此前实例化完成,所以这步依赖的注入在实例化bean时完成。

使用xml定义bean方式即为在xml中使用<bean>标签来配置,在xml中定义注入关系或者开启注解使用注解来配置依赖关系,开启注解的方法是在xml中添加<context:annotation-config/>,然后使用如@Autowired,@PostConstruct,@PreDestroy,@Inject,@Named,@Required来配置注入;
使用注解的形式定义bean实际上分为两种,一种是集中式的定义在Java类中,另一种是分散在各个包中的bean类定义上,前者就是后面要说的基于java类的配置,后者是基于Spring发现机制的隐式配置。前者在JavaConfig类中使用@Bean来定义bean,然后使用前述的注解配置依赖关系;后者则是有一系列的注解来定义bean,且这些注解是在具体要定义成bean的类中加的,如@Component@Service@Controller@Repository,声明此bean交由容器管理和装配属性,另外给需要由容器注入的属性加上类似@Autowired、@Resource、@Value的注解,指明依赖关系。

IOC常用注解

@Required

这个注释表明,bean属性必须在配置时填充,通过bean定义中的显式属性值或通过自动装配。

@Component

声明此class为一个Spring组件,在class上使用,可以在注解后面指定beanID,@Component是声明为Spring组件最基础的注解,在其基础上还有@Controller、@Service、@Repository等语义更加细粒的注解形式。

Spring提供了进一步的模板注释:@Component, @Service和@Controller。@Component是任何spring管理组件的通用原型。@Repository、@Service和@Controller分别是针对更具体的用例(例如,在持久性、服务和表示层)的@Component的专门化。因此,您可以使用@Component对组件类进行注释,但是通过使用@Repository、@Service或@Controller对它们进行注释,您的类更适合通过工具进行处理或与方面关联。例如,这些模板注释是切入点的理想目标。在Spring框架的未来版本中,@Repository、@Service和@Controller也可能包含额外的语义。因此,如果您正在为您的服务层选择使用@Component还是使用@Service,那么@Service显然是更好的选择。类似地,如上所述,@Repository已经被支持作为持久性层中自动异常转换的标记。

  • Meta-annotations
    例如@Component是@Service的元注解,也就是这种更细粒度的注解也是基于基础注解的。
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component // Spring will see this and treat @Service in the same way as @Component
public @interface Service {

    // ....
}

Meta-annotations can also be combined to create composed annotations. For example, the @RestController annotation from Spring MVC is composed of @Controller and @ResponseBody.

@Autowired

声明需要注入一个属性 | 在属性,构造器,setter上使用

As of Spring Framework 4.3, an @Autowired annotation on a constructor is no longer necessary if the target bean only defines one constructor to begin with. However, if several constructors are available, at least one must be annotated to teach the container which one to use.
The required attribute of @Autowired is recommended over the @Required annotation.

另外Spring支持Java依赖注入规范(Java Dependency Injection)提供的注解来替代Spring的注解,如使用@Named替换@Component,使用@Inject替代@Autowired.

@Qualifier

限定beanID,和@Autowired组合使用限定注入的BeanID,也可以和@Component组合使用,设置当前BeanID

@Resource

和Autowired相似,@Autowired按byType自动注入,而@Resource默认按byName自动注入。@Resource有两个属性是比较重要的,分别是name和type,Spring将@Resource注解的name属性解析为bean的名字,而type属性则解析为bean的类型。

@Primary

@Primary表示当多个bean可以自动装配到单值依赖项时,应该优先选择特定的bean。如果候选者中只存在一个“主”bean,则它将是自动装配的值。此注解用来标明bean是primary,一般和@Bean结合使用,还可以在xml中bean定义中primary="true"标明此bean是primary。使用@Qualifier("main")拥有更细的粒度;

@PostConstruct和@PreDestroy

这两个是lifecycle annotations,对应initialization callbacks和destruction callbacks。在Spring 2.5中引入,对这些注释的支持提供了初始化回调和销毁回调中描述的另一种替代方法。 如果CommonAnnotationBeanPostProcessor在Spring ApplicationContext中注册,则在生命周期的同一点调用承载这些注释之一的方法,作为相应的Spring生命周期接口方法或显式声明的回调方法。

@Lazy

可以放在bean定义一起来延迟创建bean,也可以和@Autowired或@Inject一起使用来延迟注入依赖

配置注入关系的注解的使用范围

@Autowired, @Inject, @Resource, and @Value annotations are handled by Spring BeanPostProcessor implementations which in turn means that you cannot apply these annotations within your own BeanPostProcessor or BeanFactoryPostProcessor types (if any). These types must be 'wired up' explicitly via XML or using a Spring @Bean method.

1.基于XML文件配置自动装配方式

  • 配置思路

使用xml配置声明bean,在xml中配置DI或使用注解配置DI

XML定义bean和DI

  • 示例
<?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:aop="http://www.springframework.org/schema/aop"
    xsi:schemaLocation="http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.1.xsd
        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-3.0.xsd">

    <!-- 配置一个bean -->
    <!--如果没有指明ID,则默认ID为“com.springinaction.ioc.BraveKnight#0”-->
    <bean id="knight" class="com.springinaction.ioc.BraveKnight">
        <!--给构造函数传递参数,没有的话则调用默认构造方法 -->
        <constructor-arg ref="quest" />
    </bean>

    <bean id="quest" class="com.springinaction.ioc.SlayDragonQuest">
        <constructor-arg value="#{T(System).out}" />
    </bean>
    <bean id="minstrel" class="com.springinaction.aop.Minstrel">
        <constructor-arg value="#{T(System).out}" />
    </bean>
</beans>
  • 启动容器测试
    ApplicationContext context = new ClassPathXmlApplicationContext(
                "classpath:spring.xml");
    // 也可选择FileSystemXmlApplicationContext类来定义上下文启动容器
    BraveKnight knight = context.getBean(BraveKnight.class);
    knight.fight();

需要注意的是,Spring在遇到<bean>创建bean时,调用的是默认构造器(如果没有定义构造器注入的话);

XML定义bean,注解配置DI

  • 示例
<?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:aop="http://www.springframework.org/schema/aop"
    xsi:schemaLocation="http://www.springframework.org/schema/aop 
    http://www.springframework.org/schema/aop/spring-aop-3.1.xsd
    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-3.0.xsd">

    <!-- 配置一个bean -->
    <bean id="testBean" class="com.springinaction.ioc.beaninject.TestBean"></bean>
    <!-- 依赖TestBean,但是依赖关系定义在类的注解中 -->
    <bean id="testBean2" class="com.springinaction.ioc.beaninject.TestBean2"></bean>
    <!-- 启用注解支持 -->
    <context:annotation-config />
</beans>
  • java类中使用注解配置DI
public class TestBean2 {

    public TestBean2() {
        System.out.println("TestBean2 default constructor");
    }

    private TestBean t;
    // 配置DI
    @Autowired
    public TestBean2(TestBean t) {
        System.out.println("TestBean2 set t in constructor");
        this.t = t;
    }

    // ...
}
  • 启动容器测试
    @Test
    public void testDefineBeanInXMLButDIbyAnnotation() {
        context = new ClassPathXmlApplicationContext(
                "classpath:com/springinaction/ioc/beaninject/spring.xml");
        TestBean2 t2 = context.getBean(TestBean2.class);
        System.out.println(t2.getT());
    }

2.基于Java类配置自动装配方式

Java类中定义bean,类中配置DI。依赖注解@Bean和@Configuration。

@Bean和@Configuration

@Bean和@Configuration是基于Java配置容器的核心组件。@Bean用来注解方法,用来指明一个实例化、配置和初始化一个由Spring容器管理的新对象的方法。@Bean的语义和xml中<bean>的作用类似。@Bean可以和@Component一起使用,但是一般是和@Configuration一起使用。

@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Bean {
    /**
     * The name of this bean, or if plural, aliases for this bean. If left unspecified
     * the name of the bean is the name of the annotated method. If specified, the method
     * name is ignored.
     */
    String[] name() default {};
    /**
     * Are dependencies to be injected via convention-based autowiring by name or type?
     */
    Autowire autowire() default Autowire.NO;
    String initMethod() default "";
    String destroyMethod() default AbstractBeanDefinition.INFER_METHOD;
}

@Bean的使用


@Configuration
public class AppConfig {
    @Bean(name = "myFoo")

    @Bean(initMethod = "init")
    public Foo foo() {
        return new Foo();
    }

    @Bean(destroyMethod = "cleanup") //bean默认会调用类的公共close或shutdown方法作为destoryMethod,如果不需要可以使用 @Bean(destroyMethod="")
    @Bean(name = { "dataSource", "subsystemA-dataSource", "subsystemB-dataSource" })  // alias
    @Description("Provides a basic example of a bean")
    public Bar bar() {
        return new Bar();
    }

    //  定义scope
    @Bean
    @Scope("prototype")
    public Encryptor encryptor() {
        // ...
    }
    // scoped-proxy
    // an HTTP Session-scoped bean exposed as a proxy
    @Bean
    @SessionScope
    public UserPreferences userPreferences() {
        return new UserPreferences();
    }
}

@Configuretion的使用

@Configuretion注解用来注解一个class,表明它是一个bean定义的配置类。此外,@Configuration类允许通过简单地调用同一类中的其他@Bean方法来定义bean间的依赖关系。

This method of declaring inter-bean dependencies only works when the @Bean method is declared within a @Configuration class. You cannot declare inter-bean dependencies using plain @Component classes.

All @Configuration classes are subclassed at startup-time with CGLIB.In the subclass, the child method checks the container first for any cached (scoped) beans before it calls the parent method and creates a new instance.

There is another way to achieve the same result. Remember that @Configuration classes are ultimately just another bean in the container: This means that they can take advantage of @Autowired and @Value injection etc just like any other bean!

@Bean的lite模式

@Bean注解的方法所在的类没有使用@Configuration注解的情况,就是lite模式。这种模式下,不能在类中定义DI关系(直接调用其他的@Bean方法不是使用的Spring容器注入的bean,而是新创建的对象),此场景下@Bean方法定义的是bean的工厂方法。(有一种prototype的语义)

常规Spring组件中的@Bean方法与Spring @Configuration类中的对应方法处理方式不同。不同之处在于,并没有使用CGLIB增强@Component类来拦截方法和字段的调用。CGLIB代理是在@Configuration类中的@Bean方法中调用方法或字段的方法,它为协作对象创建bean元数据引用;这些方法不是使用普通的Java语义来调用的,而是通过容器来提供Spring bean通常的生命周期管理和代理,即使是通过对@Bean方法的编程调用来引用其他bean时也是如此。相反,在普通的@Component类中调用@Bean方法中的方法或字段具有标准的Java语义,不应用特殊的CGLIB处理或其他约束。

注意,对静态@Bean方法的调用永远不会被容器截获,甚至在@Configuration类中也不会被拦截(参见上面的内容)。这是由于技术限制:CGLIB子类化只能覆盖非静态方法。因此,直接调用另一个@Bean方法将具有标准的Java语义,从而直接从factory方法本身返回一个独立的实例。

最后,请注意,单个类可以为同一个bean保存多个@Bean方法,作为根据运行时可用依赖项使用的多个工厂方法的安排。这与在其他配置场景中选择“greediest”构造函数或工厂方法的算法是相同的:在构建时将选择具有最大数量可满足依赖性的变体,类似于容器在多个@Autowired构造

public class SpringBeanInjectConfig4TestBean {

    @Bean
    public TestBean testBean() {
        return new TestBean();
    }

    @Bean
    public TestBean2 testBean2() {
        return new TestBean2(testBean());
    }
}

    @Test
    public void testJavaBasedConfig() {
        context = new AnnotationConfigApplicationContext(SpringBeanInjectConfig4TestBean.class);
        TestBean2 t2 = context.getBean(TestBean2.class);
        TestBean t = context.getBean(TestBean.class);
        org.junit.Assert.assertEquals(t, t2.getT()); // error
    }

配置示例

  1. 创建配置类
  2. 使用注解@Bean声明bean
  3. 借助JavaConfig注入
  • config类
@Configuration
public class SpringBeanInjectConfig4TestBean {

    @Bean
    public TestBean testBean() {
        return new TestBean();
    }

    @Bean
    public TestBean2 testBean2() {
        return new TestBean2(testBean());
    }
}
  • bean
public class TestBean2 {
    private TestBean t;
    public TestBean2(TestBean t) {
        System.out.println("TestBean2 set t in constructor");
        this.t = t;
    }
  // ...
}
  • 启动容器
    @Test
    public void testJavaBasedConfig() {
        context = new AnnotationConfigApplicationContext(
                SpringBeanInjectConfig4TestBean.class);
        TestBean2 t2 = context.getBean(TestBean2.class);
        System.out.println(t2.getT());
    }

还可以:

    AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
    ctx.register(AppConfig.class, OtherConfig.class);
    ctx.register(AdditionalConfig.class);
    ctx.refresh();

@Bean注解声明此方法返回类型对应的bean注册为Spring Bean,且默认方法名为beanID。Spring会自动调用@Bean注解的方法中的方法创建对象和注入依赖。这个方式更像是手动注入依赖和创建对象,因为需要手动去写new,以及传入属性。

@Bean is a method-level annotation and a direct analog of the XML <bean/> element. @Bean带注释的方法可以有任意数量的参数来描述构建该bean所需的依赖关系。

@Bean注解的方法是作为bean的factory-method来创建bean的,类似在xml中定义factory-method。使用此方式的bean不能在配置类的外部使用@Autowired等配置DI。只能在config类中配置,配置方式有两种:一是直接在@Bean注解的方法内调用其他@Bean的方法,或者使用方法参数来注入:

    @Bean
    public TestBean2 testBean2(TestBean t) {
        return new TestBean2(t);
    }

3.基于隐式bean发现机制的自动装配方式

使用此方式不需要使用xml或java类明确集中式地定义bean,而是使用注解声明一个class是bean,然后通过Spring的自动扫描机制将其装入容器中,此方式下的DI也通过注解的形式配置。

此方式基于注解和Spring detect机制;需要借助前两种方式来开启包扫描功能才能使用注解来让Spring发现,此方式相较前两种的集中式配置方案,它的配置是分散在各个类中的。

“Annotation injection is performed before XML injection, thus the latter configuration will override the former for properties wired through both approaches.”

Excerpt From: Rod Johnson. “Spring Framework Reference Documentation.” iBooks.

bean声明

  • 使用Spring 模板注解(@Component, @Repository, @Service, and @Controller)是,声明一个bean
  • bean附加属性如name,scope
@Scope("prototype")
@Repository("myrepos")
public class MovieFinderImpl implements MovieFinder {
    // ...
}
  • @Qualifier可以和@Component一起使用来设置name

Spring组件模型元素vs JSR-330变体

Spring javax.inject.*
@Autowired @Inject
@Component @Named / @ManagedBean
@Scope("singleton") @Singleton
@Qualifier @Qualifier / @Named
@Value -
@Required -
@Lazy -
ObjectFactory Provider

自动扫描

自动扫描的配置需要借助Java类或者是XML来配置开启自动扫描以及扫描的包目录;

基于Java类配置包扫描

示例:

@Configuration
@ComponentScan // 配置自动扫描,默认是本类所在的包及子包
public class SpringConfig {

    //...
}

启动容器,以java配置类定义应用上下文:

public static void main(String[] args) {
        ApplicationContext context = null;
        // 使用java类配置方式
        context = new AnnotationConfigApplicationContext(com.springinaction.SpringConfig.class);
        // ...
        // or
        AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
        ctx.scan("com.acme");
        ctx.refresh();
    }

@Configuration

从Spring3.0,@Configuration用于定义配置类,可替换xml配置文件,被注解的类内部包含有一个或多个被@Bean注解的方法,这些方法将会被AnnotationConfigApplicationContext或AnnotationConfigWebApplicationContext类进行扫描,并用于构建bean定义,初始化Spring容器。

如果像示例一样在main方法中直接指明了使用AnnotationConfigApplicationContext来从此类定义应用上下文,可以不加这个注解。

@ComponentScan
  • 这个注解默认情况下开启扫描本类所在的包以及子包;
  • @ComponentScan("com.test"):指定包名
  • @ComponentScan(basePackages="com.test")
  • @ComponentScan(basePackages={"com.test","com.test2"})
  • @ComponentScan(basePackageClasses={A.class,B.class}):类所在包及子包
扫描过滤
  • 示例
@Configuration
@ComponentScan(basePackages = "org.example",
        includeFilters = @Filter(type = FilterType.REGEX, pattern = ".*Stub.*Repository"),
        excludeFilters = @Filter(Repository.class))
public class AppConfig {
    ...
}
  • 支持的格式
Filter Type Example Expression
annotation (default) org.example.SomeAnnotation
assignable org.example.SomeClass
aspectj org.example..*Service+
regex org\.example\.Default.*

还可以实现org.springframework.core.type .TypeFilter接口自定义过滤器

基于xml配置包扫描

示例:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:context="
    http://www.springframework.org/schema/context" xmlns:xsi="
    http://www.w3.org/2001/XMLSchema-instance" 
    xsi:schemaLocation="
    http://www.springframework.org/schema/context
    http://www.springframework.org/schema/context/spring-context.xsd
    http://www.springframework.org/schema/beans
    http://www.springframework.org/schema/beans/spring-beans.xsd">

    <!-- 自动扫描web包 ,将带有注解的类 纳入spring容器管理 -->
    <context:component-scan base-package="com.test.*"></context:component-scan>
</beans>

使用了<context:annotation-config>自动包含了<context:annotation-config>

扫描过滤
<beans>
    <context:component-scan base-package="org.example">
        <context:include-filter type="regex"
                expression=".*Stub.*Repository"/>
        <context:exclude-filter type="annotation"
                expression="org.springframework.stereotype.Repository"/>
    </context:component-scan>
</beans>

启动容器定义应用上下文:

public static void main(String[] args) {
        ApplicationContext context = null;
        // 使用xml配置文件方式
        context = new ClassPathXmlApplicationContext("spring.xml");
        
}

如此,基本的基于注解的自动扫描装配就可用了。
使用自动扫描机制实际上隐式地包含了AutowiredAnnotationBeanPostProcessorCommonAnnotationBeanPostProcessor,它们是自动扫描装配的支撑。

4.混合配置方式

JavaConfig引用XML和其他JavaConfig

@Configuration
@Import(BConfig.class)
public class AConfig{
    @Bean
    //...
}
@Configuration
@ImportResource("classpath:spring.xml")
public class BConfig{
    //..
}

@Configuration
@Import({AConfig.class,BConfig.class})
public class AllConfig{
    
}

xml中引用JavaConfig

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="...">
 <!--引用xml-->
 <import resource="spring.xml"/>
 <!--引用JavaConfig-->
 <bean class=com.JavaConfig.class/>
 
</beans>

参考资料

[1] Spring In Action

[2] Spring Framework Reference Documentation

相关文章

  • 0x02.IoC容器配置

    [TOC] Spring配置方案 (不仅仅是Ioc的配置)从前文容器的具体实现已经知道,配置容器或者说配置应用上下...

  • Spring学习笔记

    Spring基于XML方式的容器配置 Spring基于注解方式的容器配置 Spring基于Java方式的容器配置

  • Docker安装Logstash

    拉取镜像 创建容器 创建要挂载的配置目录 设置配置目录权限 拷贝Logstash容器内部的配置 停止并删除容器 重...

  • 3、Docker中常见问题

    1、直接docker容器中配置文件,导致容器无法启动 容器中的配置文件被修改错误,导致容器无法启动,容器无法启动则...

  • Eclipse搭建本地动态网站环境

    准备Eclipse和Tomcat完成Web容器的配置 在Eclipse中配置Tomcat (web容器) 1、选择...

  • 蒸发量和降水量图表显示本地环境搭建-读取TXT数据文档

    准备Eclipse和Tomcat完成Web容器的配置 在Eclipse中配置Tomcat (web容器) 1、选择...

  • 优化:nginx容器的配置 2019-05-26

    以更方便的方式配置nginx容器: 过程: ①将一个容器卷挂载到nginx容器的配置文件目录②查看这个容器卷在宿主...

  • docker安装Nginx

    下载nginx1.10的docker镜像: 从容器中拷贝nginx配置先运行一次容器(为了拷贝配置文件): 将容器...

  • 2、CentOS7 Docker 的容器的备份 与恢复

    当基于一个镜像创建的容器做了配置修改后,如果要保存容器的这些配置修改,就需要把容器保存成镜像。 因为之前已经配置过...

  • Docker的Dockerfile

    Dockerfile 配置容器化应用 • MariaDB:• 命令行选项• 配置文件• 容器化方式运行MariaD...

网友评论

      本文标题:0x02.IoC容器配置

      本文链接:https://www.haomeiwen.com/subject/wlokpftx.html