007-Spring IoC 基于Java编程的配置

@Bean和@Configuration

@Configuration类似于@Component,它标明当前类时一个配置类,用于配置bean。
@Bean对应xml配置中的<bean/>标签,拥有<bean/>标签所有的属性,如果init-method等属性

@Configuration
public class AppConfig {

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

AppConfig类等价于如下配置

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

MyServiceOne是我们上一节中定义的MyService接口的一个实现类

AnnotationConfigApplicationContext

此节之前我们都是使用的ClassPathXmlApplicationContext读取的xml配置文件进行的容器初始化。即便我们使用了基于注解的配置,也需要配置xml标签自动扫描包等。有了AnnotationConfigApplicationContext之后,我们就可以不再使用xml配置了。

package com.yyoo.boot.config;

import com.yyoo.boot.annotation.MyService;
import com.yyoo.boot.annotation.MyServiceImplOne;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class AppCofig1 {

    @Bean
    public MyService getMyService(){
        return new MyServiceImplOne();
    }

}

import com.yyoo.boot.config.AppCofig1;
import org.junit.Test;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

public class Demo9 {

    @Test
    public void test(){

        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppCofig1.class);

        MyService myService = context.getBean(MyService.class);
        System.out.println(myService);
        myService.print();

    }

}

使用AnnotationConfigApplicationContext的register方法

public static void main(String[] args) {
    AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
    ctx.register(AppConfig.class, OtherConfig.class);
    ctx.register(AdditionalConfig.class);
    ctx.refresh();
    MyService myService = ctx.getBean(MyService.class);
    myService.doStuff();
}

使用自动组件扫描

package com.yyoo.boot.config;

import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;

@Configuration
@ComponentScan({"com.yyoo.boot"})
public class AppConfig2 {



}

我们在AppConfig2中没有定义Bean,通过scan扫描了com.yyoo.boot包下的所有bean,我们之前章节的代码中有MyService以及它的两个实现类都使用了@Service注解标注,在scan扫描的时候会将他们扫描到容器,容器可以直接使用。

package com.yyoo.boot.annotation;

import com.yyoo.boot.config.AppConfig2;
import org.junit.Test;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

public class Demo10 {

    @Test
    public void test(){

        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConfig2.class);

        MyService myService = (MyService)context.getBean("myServiceImplOne");
        System.out.println(myService);
        myService.print();

    }

}

我们的示例getBean的时候直接通过bean的名称获取了MyServiceOne的实例,如果我们直接通过type获取的话MyService有两个满足条件的实现类,容器会报错。因为我们没有使用@Primary等注解。

如果我们不用@ComponentScan注解,也可以通过AnnotationConfigApplicationContext的scan方法定义扫描包

public static void main(String[] args) {
    AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
    ctx.scan("com.yyoo.boot");
    ctx.refresh();
    MyService myService = ctx.getBean(MyService.class);
}

实际一般建议使用@ComponentScan注解定义扫描包即可。

@ComponentScan排除某些类不扫描或包含某些类需要扫描

package com.yyoo.boot.config;

import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;

@Configuration
@ComponentScan(value = {"com.yyoo.boot"},
        includeFilters = @ComponentScan.Filter(type = FilterType.REGEX, pattern = ".*Stub.*Repository"),
        excludeFilters = @ComponentScan.Filter(Repository.class))
public class AppConfig2 {



}

其中FilterType枚举类中包含如下几类

过滤器类型示例说明
ANNOTATION(默认类型)@ComponentScan.Filter(Repository.class)任意的注释类
ASSIGNABLE_TYPE任意类的class任意的类
ASPECTJorg.example…*Service+目标匹配的AspectJ类型表达式
REGEXorg.example.Default.*匹配的正则表达式
CUSTOMorg.example.MyTypeFilter自定义实现的org.springframework.core.type.TypeFilter接口

AnnotationConfigWebApplicationContext

在web应用中,我们使用AnnotationConfigWebApplicationContext来时效性上述功能。

<web-app>
    <!-- Configure ContextLoaderListener to use AnnotationConfigWebApplicationContext
        instead of the default XmlWebApplicationContext -->
    <context-param>
        <param-name>contextClass</param-name>
        <param-value>
            org.springframework.web.context.support.AnnotationConfigWebApplicationContext
        </param-value>
    </context-param>

    <!-- Configuration locations must consist of one or more comma- or space-delimited
        fully-qualified @Configuration classes. Fully-qualified packages may also be
        specified for component-scanning -->
    <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>com.acme.AppConfig</param-value>
    </context-param>

    <!-- Bootstrap the root application context as usual using ContextLoaderListener -->
    <listener>
        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    </listener>

    <!-- Declare a Spring MVC DispatcherServlet as usual -->
    <servlet>
        <servlet-name>dispatcher</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <!-- Configure DispatcherServlet to use AnnotationConfigWebApplicationContext
            instead of the default XmlWebApplicationContext -->
        <init-param>
            <param-name>contextClass</param-name>
            <param-value>
                org.springframework.web.context.support.AnnotationConfigWebApplicationContext
            </param-value>
        </init-param>
        <!-- Again, config locations must consist of one or more comma- or space-delimited
            and fully-qualified @Configuration classes -->
        <init-param>
            <param-name>contextConfigLocation</param-name>
            <param-value>com.acme.web.MvcConfig</param-value>
        </init-param>
    </servlet>

    <!-- map all requests for /app/* to the dispatcher servlet -->
    <servlet-mapping>
        <servlet-name>dispatcher</servlet-name>
        <url-pattern>/app/*</url-pattern>
    </servlet-mapping>
</web-app>

以上是官网示例,如果使用spring boot此处可以跳过。

@Bean的工作机制

我们再用之前的A、B两个类来做示例

package com.yyoo.boot.bean;

public class A {

    public A(){
        System.out.println("A无参构造");
    }
}

package com.yyoo.boot.bean;

public class B {

    private A a;

    public B(){
        System.out.println("B无参构造");
    }

    public B(A a){
        this.a = a;
        System.out.println("B带参构造");
    }

    public A getA() {
        return a;
    }

    public void setA(A a) {
        this.a = a;
        System.out.println("B的setA方法");
    }
}

@Bean示例程序

package com.yyoo.boot.config;

import com.yyoo.boot.bean.A;
import com.yyoo.boot.bean.B;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class AppConfig2 {


    @Bean
    public A getA(){
        return new A();
    }

    @Bean
    public B getB(){
        return new B(getA());
    }

    @Bean
    public B getB1(){
        return new B(getA());
    }


}

package com.yyoo.boot.annotation;

import com.yyoo.boot.bean.B;
import com.yyoo.boot.config.AppConfig2;
import org.junit.Test;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

public class Demo10 {

    @Test
    public void test(){

        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConfig2.class);
        B b = (B)context.getBean("getB");
        B b1 = (B)context.getBean("getB1");
        System.out.println(b.getA());
        System.out.println(b1.getA());

    }

}

执行结果

14:53:05.809 [main] DEBUG org.springframework.context.annotation.AnnotationConfigApplicationContext - Refreshing org.springframework.context.annotation.AnnotationConfigApplicationContext@7a0ac6e3
14:53:05.899 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'org.springframework.context.annotation.internalConfigurationAnnotationProcessor'
14:53:06.243 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'org.springframework.context.event.internalEventListenerProcessor'
14:53:06.248 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'org.springframework.context.event.internalEventListenerFactory'
14:53:06.252 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'org.springframework.context.annotation.internalAutowiredAnnotationProcessor'
14:53:06.255 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'org.springframework.context.annotation.internalCommonAnnotationProcessor'
14:53:06.275 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'appConfig2'
14:53:06.288 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'getA'
A无参构造
14:53:06.312 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'getB'
B带参构造
14:53:06.313 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'getB1'
B带参构造
com.yyoo.boot.bean.A@1283bb96
com.yyoo.boot.bean.A@1283bb96

1、最直观的结果,A的无参构造只执行了一次。虽然我们在getB或getB1方法中都调用了方法getA,而且getA也是@Bean注解的方法,但是getA只执行了一次。因为getA也是@Bean注解的,在实例化后会被缓存起来,在getB执行的时候会先查询缓存的getA。而且两个B类的示例中a属性是一个实例。

2、我们的示例中@Bean没有定义name属性,那么其定义的bean默认名称是对应的方法名称(注意跟@Component的默认名称区别),也就是我们的示例中定义的bean名称分别为:getA、getB、getB1

所有@Configuration类在启动时都使用CGLIB. 在子类中,子方法在调用父方法并创建新实例之前,首先检查容器中是否有任何缓存的(作用域)bean。从 Spring 3.2 开始,CGLIB 类已被重新打包org.springframework.cglib并直接包含在 spring-core JAR 中。因此我们不再需要单独引入CGLIB的包。

让我们把getA方法的@Bean注解去掉试试

14:52:07.176 [main] DEBUG org.springframework.context.annotation.AnnotationConfigApplicationContext - Refreshing org.springframework.context.annotation.AnnotationConfigApplicationContext@7a0ac6e3
14:52:07.228 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'org.springframework.context.annotation.internalConfigurationAnnotationProcessor'
14:52:07.474 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'org.springframework.context.event.internalEventListenerProcessor'
14:52:07.478 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'org.springframework.context.event.internalEventListenerFactory'
14:52:07.481 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'org.springframework.context.annotation.internalAutowiredAnnotationProcessor'
14:52:07.484 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'org.springframework.context.annotation.internalCommonAnnotationProcessor'
14:52:07.498 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'appConfig2'
14:52:07.507 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'getB'
A无参构造
B带参构造
14:52:07.527 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'getB1'
A无参构造
B带参构造
com.yyoo.boot.bean.A@7d8995e
com.yyoo.boot.bean.A@130d63be

A的无参构造调用了两次。因为我们没有@Bean注解getA方法,那么在getB和getB1方法中调用该方法就成了普通的方法调用,所以A被调用了两次而且对应的getB和getB1的a属性不是同一个实例。

以上示例的另一种写法

package com.yyoo.boot.config;

import com.yyoo.boot.bean.A;
import com.yyoo.boot.bean.B;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class AppConfig2 {


    @Bean
    public A getA(){
        return new A();
    }

    @Bean
    public B getB(A a){
        return new B(a);
    }

    @Bean
    public B getB1(A a){
        return new B(a);
    }


}

这样Spring会为两个getB方法注入getA实例

如果我们有多个A实例怎么办?

package com.yyoo.boot.config;

import com.yyoo.boot.bean.A;
import com.yyoo.boot.bean.B;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class AppConfig2 {


    @Bean
    public A getA(){
        return new A();
    }

    @Bean
    public A getA1(){
        return new A();
    }

    @Bean
    public B getB(A a){
        return new B(a);
    }

    @Bean
    public B getB1(A a){
        return new B(a);
    }


}

以上写法会报错,因为在getB方法上对应的A参数实例,Spring在注入时不知道该注入getA还是getA1,此时我们需要借助@Qualifier注解

package com.yyoo.boot.config;

import com.yyoo.boot.bean.A;
import com.yyoo.boot.bean.B;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class AppConfig2 {


    @Bean
    public A getA(){
        return new A();
    }

    @Bean
    public A getA1(){
        return new A();
    }

    @Bean
    public B getB(@Qualifier("getA")A a){
        return new B(a);
    }

    @Bean
    public B getB1(@Qualifier("getA1")A a){
        return new B(a);
    }


}

如果我们的getA是prototype而不是单例的会是什么结果

package com.yyoo.boot.config;

import com.yyoo.boot.bean.A;
import com.yyoo.boot.bean.B;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Scope;

@Configuration
public class AppConfig2 {


    @Bean
    @Scope("prototype")
    public A getA(){
        return new A();
    }


    @Bean
    public B getB(A a){
        return new B(a);
    }

    @Bean
    public B getB1(A a){
        return new B(a);
    }


}

package com.yyoo.boot.annotation;

import com.yyoo.boot.bean.B;
import com.yyoo.boot.config.AppConfig2;
import org.junit.Test;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

public class Demo10 {

    @Test
    public void test(){

        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConfig2.class);
        B b = (B)context.getBean("getB");
        B b1 = (B)context.getBean("getB1");
        System.out.println(b.getA());
        System.out.println(b1.getA());

    }

}

结果如下

15:17:57.051 [main] DEBUG org.springframework.context.annotation.AnnotationConfigApplicationContext - Refreshing org.springframework.context.annotation.AnnotationConfigApplicationContext@7a0ac6e3
15:17:57.092 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'org.springframework.context.annotation.internalConfigurationAnnotationProcessor'
15:17:57.364 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'org.springframework.context.event.internalEventListenerProcessor'
15:17:57.371 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'org.springframework.context.event.internalEventListenerFactory'
15:17:57.374 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'org.springframework.context.annotation.internalAutowiredAnnotationProcessor'
15:17:57.377 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'org.springframework.context.annotation.internalCommonAnnotationProcessor'
15:17:57.394 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'appConfig2'
15:17:57.406 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'getB'
A无参构造
15:17:57.438 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Autowiring by type from bean name 'getB' via factory method to bean named 'getA'
B带参构造
15:17:57.441 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'getB1'
A无参构造
15:17:57.441 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Autowiring by type from bean name 'getB1' via factory method to bean named 'getA'
B带参构造
com.yyoo.boot.bean.A@34a3d150
com.yyoo.boot.bean.A@2a4fb17b

上一篇:006-Spring IoC 基于注解配置
下一篇:008-Spring Ioc 环境与配置

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值