从Spring的包扫描说起
SpringBoot会扫描SpringBootApplication注解标注的类,他所在的包以及这个包的子包,把那里面的Bean注册到applicationContext中,然而,在一个相对大型的项目中,Bean会很多,而且一些框架在starter中会有比较特别的配置(例如Jackson框架)。
那么,我想有没有什么办法可以限制这些Bean的活动范围,让一个模块的Bean仅仅在自己的区域起效,而不会在整个Context内四处乱来。
Spring中有一个父子容器的关系,子容器能够访问父容器,而父容器不能访问子容器,子容器之间当然也是相互隔开的,那么这就是一个很好的实现方法了。
首先,想办法限制父容器的扫描范围,让他不扫描某些Bean,然后使用BeanPostProcessor判别注解,然后根据注解建立子容器,并且扫描子容器的包范围内的Bean,子容器提供一个接口托管到父容器中,所有子容器就可以通过这些在父容器某模块的接口来相互使用其他子容器模块提供的服务,这样Bean就被限制在了自己模块的范围之内了。
初步实现
定义一个注解,用它来描述一个Spring的Bean,待会这个注解会在扫描的时候被排除在外。
import static java.lang.annotation.ElementType.TYPE;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import org.springframework.stereotype.Component;
@Retention(RUNTIME)
@Target(TYPE)
@Component
public @interface SubComp {
}
然后在SpringBootApplication下方标注:
@SpringBootApplication
@ComponentScan(excludeFilters=@Filter(type=FilterType.ANNOTATION,value=SubComp.class))
那么,接下来使用这个注解代替@Component
和@Service
、@Controller
之类的,这样组件就不会被Spring扫描到了。
接下来,定义一个BeanPostProcessor,通过它来查找带有@Configuration
的Bean,然后在发现这个注解的时候创建新的AnnotationApplicationContext,然后扫描这个Bean的包,这样,新注解标注的Bean就会被这个Context发现并且注册,最后,让这个Context成为子容器,附属于SpringBootApplication的Context。
@Component
public class RangeProcesser implements BeanPostProcessor,ApplicationContextAware{
private ApplicationContext applicationContext;
private List<ApplicationContext> contexts = new ArrayList<>();
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
if(bean.getClass().getAnnotation(Configuration.class)!=null) {
AnnotationConfigApplicationContext subContext = new AnnotationConfigApplicationContext(bean.getClass());
subContext.scan(bean.getClass().getPackage().getName());
subContext.start();
subContext.setParent(applicationContext);
contexts.add(subContext);
return null;
}
return BeanPostProcessor.super.postProcessBeforeInitialization(bean, beanName);
}
@Override
public void setApplicationContext(ApplicationContext ctx) throws BeansException {
this.applicationContext = ctx;
}
}
像这样,就有了一些子容器,父子容器关系的特点就是子容器可以访问父容器,反之不可,所以父容器在这里将会提供通用的组件,并且作为子容器相互沟通的桥梁。
未完待续。