bean-config-1.yaml:
server: port: 8080 spring: application: name: child1
接下来分别是ChildContext2,ChildContext3的:
package com.test.spring.context.config.child2; import com.hopegaming.scaffold.spring.context.bean.ChildBean; import com.hopegaming.scaffold.spring.context.bean.RootBean; import com.hopegaming.scaffold.spring.context.config.YamlPropertyLoaderFactory; import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.PropertySource; @SpringBootApplication(scanBasePackages = {"com.test.spring.context.controller"}) @PropertySource(value = "classpath:/bean-config-2.yaml", factory = YamlPropertyLoaderFactory.class) public class ChildContext2 { @Bean public ChildBean getChildBean(@Value("${spring.application.name}") String name, RootBean fatherBean) { ChildBean childBean = new ChildBean(); childBean.setFatherBean(fatherBean); childBean.setName(name); return childBean; } } server: port: 8081 spring: application: name: child2 management: endpoint: health: show-details: always endpoints: jmx: exposure: exclude: '*' web: exposure: include: '*' package com.test.spring.context.config.child3; import com.hopegaming.scaffold.spring.context.bean.ChildBean; import com.hopegaming.scaffold.spring.context.bean.RootBean; import com.hopegaming.scaffold.spring.context.config.YamlPropertyLoaderFactory; import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.PropertySource; @SpringBootApplication(scanBasePackages = {"com.test.spring.context.controller"}) @PropertySource(value = "classpath:/bean-config-3.yaml", factory = YamlPropertyLoaderFactory.class) public class ChildContext3 { @Bean public ChildBean getChildBean(@Value("${spring.application.name}") String name, RootBean fatherBean) { ChildBean childBean = new ChildBean(); childBean.setFatherBean(fatherBean); childBean.setName(name); return childBean; } } server: port: 8082 spring: application: name: child3 management: endpoint: health: show-details: always endpoints: jmx: exposure: exclude: '*' web: exposure: include: '*'
测试接口TestController
:
package com.test.spring.context.controller; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import java.util.Locale; @RestController public class TestController { @Autowired private ChildBean childBean; @RequestMapping("/test") public ChildBean getChildBean() { return childBean; } }
启动类:
package com.test.spring.context; import com.hopegaming.scaffold.spring.context.config.child1.ChildContext1; import com.hopegaming.scaffold.spring.context.config.child2.ChildContext2; import com.hopegaming.scaffold.spring.context.config.child3.ChildContext3; import com.hopegaming.scaffold.spring.context.config.root.RootContext; import org.springframework.boot.builder.SpringApplicationBuilder; import org.springframework.context.ConfigurableApplicationContext; public class ContextMain { public static void main(String[] args) { SpringApplicationBuilder appBuilder = new SpringApplicationBuilder() .sources(RootContext.class) //第一个子context用child,剩下的都用sibling .child(ChildContext1.class) .sibling(ChildContext2.class) .sibling(ChildContext3.class); ConfigurableApplicationContext applicationContext = appBuilder.run(); } }
启动后,访问http://127.0.0.1:8080/test
返回:
{"fatherBean":{"name":"root"},"name":"child1"}
访问http://127.0.0.1:8081/test
返回:
{"fatherBean":{"name":"root"},"name":"child2"}
访问http://127.0.0.1:8082/test
返回:
{"fatherBean":{"name":"root"},"name":"child3"}
访问http://127.0.0.1:8080/actuator/beans
会有类似于下面的返回(省略了不关心的bean):
{ "contexts": { "application-1": { "beans": { "getChildBean": { "aliases": [], "scope": "singleton", "type": "com.hopegaming.scaffold.spring.context.bean.ChildBean", "resource": "com.hopegaming.scaffold.spring.context.config.child2.ChildContext2", "dependencies": [ "getFatherBean" ] }, "childContext2": { "aliases": [], "scope": "singleton", "type": "com.hopegaming.scaffold.spring.context.config.child2.ChildContext2$$EnhancerBySpringCGLIB$$26f80b15", "resource": null, "dependencies": [] } ....... }, "parentId": "application" }, "application": { "beans": { "getFatherBean": { "aliases": [], "scope": "singleton", "type": "com.hopegaming.scaffold.spring.context.bean.RootBean", "resource": "com.hopegaming.scaffold.spring.context.config.root.RootContext", "dependencies": [] }, "rootContext": { "aliases": [], "scope": "singleton", "type": "com.hopegaming.scaffold.spring.context.config.root.RootContext$$EnhancerBySpringCGLIB$$18d9c26f", "resource": null, "dependencies": [] } ....... }, "parentId": null } } }
通过这个例子,想必大家对于 ApplicationContext 层级有了一定的理解
Bean 加载条件
我们会经常看到@Conditional
相关的注解,例如@ConditionalOnBean
还有@ConditionalOnClass
等等,这些注解提供了自动装载时候根据某些条件加载不同类的灵活性。@Conditional
注解是 spring-context 提供的特性,Spring Boot 在这个注解的基础上,提供了更多具体的条件配置注解,包括:
@ConditionalOnBean
,如果当前 ApplicationContext 的 BeanFactory 已经包含这些 Bean,则满足条件。与之相反的是@ConditionalOnMissingBean
,如果当前 ApplicationContext 的 BeanFactory 不包含这些 Bean,则满足条件。@ConditionalOnClass
,如果当前 classpath 中有这些类,则满足条件。与之相反的是@ConditionalOnMissingClass
,如果当前 classpath 中没有这些类,则满足条件@ConditionalOnProperty
,指定属性是否存在,并且值满足havingValue
指定的值(没设置就是不为false
就行),matchIfMissing
代表如果属性不存在代表条件满足还是不满足。
以上几个注解是比较常用的,剩下的例如ConditionalOnCloudPlatform
这些不太常用,这里先不提了。
如果有多个类似的@Conditional
注解作用于同一个方法或者类,这些加载条件是“And”的关系。
Configuration 加载顺序
由于 Bean 加载条件的复杂性,有时候我们想某些 Configuration 类先加载,某些在特定的 Configuration 加载完之后再加载。例如:
@Configuration public class FirstConfiguration { @Bean @ConditionalOnMissingBean public Service service1() { ...... } } @Configuration public class SecondConfiguration { @Bean @ConditionalOnMissingBean public Service service1() { ...... } }
假设这两个类在不同 jar 包,我们没有办法确定最后创建的是哪一个类的 Service
,这时候我们就需要用到一些决定 Configuration 加载顺序的注解。注意这里的 Configuration 加载顺序仅仅是 Bean 定义加载顺序,主要是为了限制上面提到的 Bean 加载条件的判断顺序,而不是创建 Bean 的顺序。Bean 创建的顺序主要由 Bean 依赖决定以及@DependsOn
注解限制。
相关的注解如下:
@AutoConfigureAfter
指定当前 Configuration 在 某个 Configuration 之后加载。@AutoConfigureBefore
指定当前 Configuration 在 某个 Configuration 之前加载。@AutoConfigureOrder
类似于@Order
注解,指定当前 Configuration 的加载序号,默认是 0 ,越小越先加载。
Bean 排序
对于同一类型的 Bean(实现了同一接口的 Bean),我们可以用一个 List 进行自动装载,例如:
public interface Service { void test(); } @Componenet public class ServiceA implements Service { @Override public void test() { System.out.println("ServiceA"); } } @Componenet public class ServiceB implements Service { @Override public void test() { System.out.println("ServiceB"); } } @Componenet public class Test { @Autowired private List<Service> services; }
private List<Service> services
中就会有 serviceA
和 serviceB
这两个 Bean,但是谁在前谁在后呢?可以通过@Order
注解指定。
@Retention(RetentionPolicy.RUNTIME) @Target({ElementType.TYPE, ElementType.METHOD, ElementType.FIELD}) @Documented public @interface Order { /** * The order value. * <p>Default is {@link Ordered#LOWEST_PRECEDENCE}. * @see Ordered#getOrder() */ int value() default Ordered.LOWEST_PRECEDENCE; }
值越小,越靠前。