Java类的形式配置bean

  除了传统的使用XML来配置底层的bean容器定义,Spring还支持使用大家熟悉的Java类的形式来进行配置。

  使用Java类的形式来进行配置时,我们将使用一个Java类来作为配置的主体,并在类上使用@Configuration进行标注,表示其是一个配置类。然后将对应的bean定义定义为Java配置类中的一个公用方法,并在方法上使用@Bean进行标注,表示其是一个bean定义。使用@Bean进行标注的方法对应的返回类型就是生成的bean定义对应的Class类型,对应的方法体实现,就是用来产生对应bean定义的实例的过程,对应的方法名就是bean定义的默认beanName。下面我们先来简单的看一个对应的实现。

@Configuration
public class SpringConfig {

    @Bean
    public Hello hello() {
        return new Hello()  ;
    }
    
}

  在上面代码中通过@Configuration标注了类SpringConfig是一个Spring的配置类,然后在其中定义了一个使用@Bean进行标注的方法,Spring会将其作为一个bean定义添加到bean容器中,对应beanName为“hello”,然后直接new一个对应的实例作为bean定义的实例。之后我们就可以从该配置类产生的bean容器中获取对应的bean定义,对应测试代码可以是如下这样。

@ContextConfiguration(classes={SpringConfig.class})
@RunWith(SpringJUnit4ClassRunner.class)
public class ConfigurationTest {
    
    @Autowired
    private ApplicationContext context;
    
    @Test
    public void test() {
        System.out.println(context.getBean("hello", Hello.class));
    }

}

@Bean

  在使用基于Java类进行配置时,可以使用@Bean来标注配置类中需要定义为bean容器中一个bean定义的方法。标注后,默认会以该方法的名称作为对应bean定义的beanName,以方法的返回类型作为对应bean定义的Class类型。

@Configuration
public class SpringConfig {

    @Bean
    public Hello hello() {
        return new Hello();
    }
    
}

如上示例,我们就在基于Java类的配置中使用@Bean注解定义了一个bean定义,其相当于我们在XML配置文件中的如下定义。

<bean id="hello" class="com.app.Hello"/>

指定beanName

默认情况Spring会以方法名称作为对应bean定义的beanName。如果用户希望指定对应bean定义的beanName,则可以通过@Bean的name属性进行指定。

@Bean(name="hello1")
public Hello hello() {
    return new Hello();
}

可以通过@Bean的name属性给bean定义指定多个名称,这个时候就可以将name属性以一个数组的形式进行指定。如下示例中我们就表示同时指定beanName为“hello”和“hello1”。

@Bean(name={"hello", "hello1"})
public Hello hello() {
    return new Hello();
}

指定生命周期回调方法

  bean的生命周期回调方法,可以通过三种方式进行指定,使用JSR-250标准的@PostConstruct和@PreDestroy进行标注、实现InitializationBean和DisposableBean接口和通过bean定义的init-method和destroy-method进行指定。对于前两种方式而言,使用@Bean进行配置的bean也可以像其它基于注解或基于XML配置的bean一样进行回调。

  对于第三种方式,我们就需要通过@Bean的initMethod和destroyMethod属性进行指定,对应属性值即表示对应的回调方法名称。

  @Bean(initMethod="init", destroyMethod="destroy")
    public Hello hello() {
        return new Hello();
    }

  通过initMethod属性指定了bean hello的初始化回调方法为init(),通过destroyMethod属性指定了对应的销毁前回调方法为destroy()。其就相当于我们基于XML配置的如下形式

  <bean id="hello" class="com.app.Hello" init-method="init" destroy-method="destroy"/>

指定是否自动注入
  在使用XML进行配置的时候,可以通过其autowire指定是否需要进行自动注入。对应的我们可以在@Bean上使用autowire属性来指定是否进行自动注入。可选值有Autowire.NO、Autowire.BY_NAME和Autowire.BY_TYPE。默认是Autowire.NO,即不进行自动注入。Autowire.BY_NAME表示按名称进行自动注入,Autowire.BY_TYPE表示按类型进行自动注入。

  @Bean(autowire=Autowire.BY_TYPE)
    public Hello hello() {
        return new Hello();
    }

  上述示例表示按照类型对依赖的bean进行自动注入。其等同于如下XML定义。

  <bean id="hello" class="com.app.Hello" autowire="byType"/>

指定Scope
  通过@Bean进行定义的bean对应的Scope默认也是singleton的,用户如果希望更改这种默认策略,则需要在@Bean对应的方法上使用@Scope标注进行定义,具体用法与我们之前介绍的在基于注解配置bean定义时是一样的。如下表示我们定义对应的Scope为prototype。

   @Bean
    @Scope("prototype")
    public Hello hello() {
        return new Hello();
    }

如果需要被代理,可以指定proxyMode。

  @Bean
    @Scope(value="prototype", proxyMode=ScopedProxyMode.TARGET_CLASS)
    public Hello hello() {
        return new Hello();
    }

依赖注入

  对于处于同一个使用@Configuration进行标注的配置类中的bean注入,可以直接通过方法调用的形式来获取对应的bean实例以进行注入。如下示例中我们就通过调用world()方法获取了一个World实例,然后注入给了Hello的实例。

@Bean
    public Hello hello() {
        Hello hello = new Hello();
        hello.setWorld(this.world());
        return hello;
    }
    
    @Bean
    public World world() {
        return new World();
    }

  也可以像之前介绍的使用@Autowired或@Inject标注进行自动注入,即在对应的bean对应的Class类中进行定义。如下示例中我们就通过@Autowired定义了world需要进行自动注入。对于使用Java类进行配置的情况,Spring会自动扫描对应的注解以完成对应的bean定义。

public class Hello {
    
    @Autowired
    private World world;

}

  在下面的示例中,我们调用了两次world()方法,分别获取对应的bean实例注入hello和beanA。因为Spring默认生成的bean实例是singleton类型的,为了实现这种机制,则在第一次调用world()产生第一个实例后,Spring会将对应的对象缓存起来,待下次再调用world()方法获取实例时,直接获取缓存起来的那个实例。所以对于如下这种情况,实际上Spring也只会产生一个World实例。Spring的这种基于Java类配置的单例bean机制是通过CGLIB动态生成类来实现的,在系统启动时,Spring将通过CGLIB构建所有使用@Configuration进行标注的Class的子类,在子类调用父类对应方法产生对应的bean实例之前,会先从子类的缓存中获取,只有在取不到的情况下才会调用父类产生新的实例,产生了对应的实例后就将其缓存到对应的缓存中。另外,鉴于Spring的这种机制就要求我们对应的Java配置Class不是final型的,且存在一个无参构造方法。

@Configuration
public class SpringConfig {

    @Bean
    public Hello hello() {
        Hello hello = new Hello();
        hello.setWorld(world());
        return hello;
    }
    
    @Bean
    public World world() {
        return new World();
    }
    
    @Bean
    public BeanA beanA() {
        BeanA beanA = new BeanA();
        beanA.setWorld(world());
        return beanA;
    }
    
}

使用lookup-method 

     在之前介绍XML配置时,使用lookup-method的方式以解决单例bean引用多例bean的问题。在使用基于Java进行配置时,如果我们也希望以这样的方式来解决单例bean引用多例bean的情况,则可以通过如下方式进行解决。打个比方我们有一个单例bean,对应类Hello,其需要引用一个多例的bean World,那么我们就可以在Hello中定义一个公用的方法以获取World实例,然后每次需要使用World实例时都通过该方法获取,如下的getWorld()方法就是这个功能。

public class Hello {
    
    public void doSomething() {
        World world = this.getWorld();
        System.out.println(world);
        //...
    }
    
    public World getWorld() {
        return null;
    }

}

  然后在定义Hello对应的单例bean时,可以new一个匿名的Hello类的子类,然后在其中重写getWorld()方法,对应方法体则是通过@Configuration标注的配置类的对应方法获取一个多例的World实例。这样在单例的hello中每次调用getWorld()方法时都会从bean容器中获取到一个全新的World实例。

@Configuration
public class SpringConfig {

    @Bean
    public Hello hello() {
        Hello hello = new Hello() {
            public World getWorld() {
                return world();
            }
        };
        return hello;
    }
    
    @Bean
    @Scope("prototype")
    public World world() {
        return new World();
    }
    
}

组合多个配置

  通常在使用基于Java类的配置时我们可能不是单独使用一个Java类进行配置,有的时候可能会建立多个Java类进行配置,也可能是基于Java类的配置和基于XML的配置一起使用

多个Java类配置组合
  对于多个Java配置类的情况,可以通过将@Import标注在对应的配置类上以引入另外一个配置类,这样在使用的时候就可以只以其中的某个或某几个配置类来直接作为配置类。如下示例我们在配置类SpringConfig上使用@Import引入了另外两个配置类ServiceConfig和DaoConfig。

@Configuration
@Import({ServiceConfig.class, DaoConfig.class})
public class SpringConfig {

}

  那么,对应在使用的时候可以只显示的使用SpringConfig作为配置类,但实际上是使用SpringConfig、ServiceConfig和DaoConfig三个配置类,即如下示例中的context将包含这三个配置类中定义的所有的bean。

@ContextConfiguration(classes={SpringConfig.class})
@RunWith(SpringJUnit4ClassRunner.class)
public class ConfigurationTest {
    
    @Autowired
    private ApplicationContext context;
    
}

以Java类配置为主组合XML配置
  当以Java配置类为主组合XML配置时,只需要在Java配置类上使用@ImportResource进行标注以引入对应的XML配置即可。如下示例中即通过@ImportResource引入了类路径下的applicationContext.xml,这样我们可以继续使用SpringConfig作为配置类,但其中会包含applicationContext.xml中定义的bean。

@Configuration
@ImportResource({"classpath:applicationContext.xml"})
public class SpringConfig {

    @Bean
    public Hello hello() {
        return new Hello();
    }
    
}

以XML配置为主组合Java类配置
  由于@Configuration是标注@Component的,所以XML配置为主时,如果我们的XML配置定义了<context:component-scan/>,且其能扫描到我们使用了@Configuration进行标注的配置类,那么对应的配置类将作为一个bean定义到对应的bean容器中。与此同时,Spring能检测到其中使用@Bean进行标注的方法,并把它们作为一个bean进行定义。其实如果我们的配置类不是使用的@Configuration进行标注的,而是使用的其它Spring会把它当做一个bean进行定义的注解进行标注也是可以的,如@Component、@Service等。

  但是当我们在XML配置中没有使用<context:component-scan/>或者对应的<context:component-scan/>扫描不到我们基于Java的配置类,这个时候就需要我们自己在XML配置中,定义对应的bean了,这样Spring就能自动组合基于Java类配置的bean定义了。

  <bean class="com.app.SpringConfig"/>
  自己单独在XML配置中,定义Java配置类对应的bean时,如果配置类中有使用@Autowired进行自动注入,那就需要启用<context:annotation-config/>。当然如果使用了<context:component-scan/>就可以不再单独指定<context:annotation-config/>了,因为<context:component-scan/>默认会启用<context:annotation-config/>。

 需要修改web.xml中监听器。加载多个配置文件:

<!-- 加载spring容器,与servletContext绑定 -->
    <listener>
        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    </listener>
    <!-- 如果不指定contextConfigLocation,默认加载/WEB-INF/applicationContext.xml -->
    <context-param>
        <param-name>contextConfigLocation</param-name>
        <!-- spring配置文件 可以采用加载多个文件,中间以半角逗号分隔,也可以使用通配符号 -->
        <param-value>classpath:spring/applicationContext.xml,classpath:spring/applicationContext-*.xml</param-value>
    </context-param>

 

参考:
原文:https://blog.csdn.net/elim168/article/details/77155995

posted on 2018-10-26 12:48  溪水静幽  阅读(1528)  评论(0)    收藏  举报