前言
各位老铁们是否遇曾经遇到过这样的疑惑:同样是Spring容器里的Bean,为何能够@Autowireservice进Controller里面,但是反之注入就报错呢?报找不到bean~
但是自己从容器里明明可以拿到这个Bean啊,怎么回事呢?
同样的我们发现,容器里面的属性值,容器之间也是不互通的?
环境准备
准备一个传统的Spring环境(注意,一定不能是Spring Boot环境),为了偷懒,项目环境各位移步此处:
【小家Spring】Spring注解驱动开发—Servlet 3.0整合Spring MVC(不使用web.xml部署描述符,全注解驱动)
如何证明Spring是存在父子容器的
我们现在的结论是,在Web环境中,是分为SpringMvc管理的子容器,和Spring管理的父容器。如何证明呢?
基于上面的项目环境(请参看项目环境,因为如果项目环境不对,得到的效果可能会被误导的,总之就是保证@Controller只被扫描一次)
我们分别让Controller和Service实现ApplicationContextAware,就可以证明出:
结论,虽然类型AnnotationConfigWebApplicationContext
:但是显然是不一样的,从它从写的toString()方法里可以看出:
@Override public String toString() { StringBuilder sb = new StringBuilder(getDisplayName()); sb.append(": startup date [").append(new Date(getStartupDate())); sb.append("]; "); ApplicationContext parent = getParent(); if (parent == null) { sb.append("root of context hierarchy"); } else { sb.append("parent: ").append(parent.getDisplayName()); } return sb.toString(); }
==============备注:说一下Spring Boot环境下的:
结果我们发现(各位自己去试验哈),使用的是一模一样的(这里指的一模一样,就是一个,地址值都是一样的)AnnotationConfigEmbeddedWebApplicationContext,可以看出在boot环境中使用的是相同的容器管理的(无父子容器概念)。备注:该类在org.springframework.boot.context.embedded中这个包里面,属于Boot后来自己实现的
附上一个继承图谱:
注意:
我们的ApplicationContext
以及BeanFactory
都是可以直接@Autowired
的,如下:
Controller注入: @Autowired private ApplicationContext applicationContext; WebApplicationContext for namespace 'dispatcher-servlet': startup date [Thu Mar 07 15:25:04 CST 2019]; parent: Root @Autowired private BeanFactory beanFactory; // org.springframework.beans.factory.support.DefaultListableBeanFactory@41b6d6b3: defining beans [... Service注入: @Autowired private ApplicationContext applicationContext; //Root WebApplicationContext: startup date [Thu Mar 07 15:25:02 CST 2019]; root of context hierarchy @Autowired private BeanFactory beanFactory; //rg.springframework.beans.factory.support.DefaultListableBeanFactory@747a2296: defining beans [...
由此可以看出,容器直接注入进来就行。但是,但是,但是如果存在父子容器的话,在不同的层,注入的对象也是不一样的,这点在了解了Spring容器的机制的情况下,是很好理解的~~~
如何证明Spring的父容器不能访问子容器的Bean
其实这个在上面的那篇博文里已经举例了。
比如,我在Web子容器的配置文件里注册一个Bean:
@ComponentScan(value = "com.fsx", useDefaultFilters = false, includeFilters = {@Filter(type = FilterType.ANNOTATION, classes = {Controller.class})} ) @Configuration public class AppConfig { @Bean public Child child() { return new Child(); } }
然后在Spring管理的Root父容器里注册一个Bean:
@ComponentScan(value = "com.fsx", excludeFilters = { @Filter(type = FilterType.ANNOTATION, classes = {Controller.class}), //排除掉web容器的配置文件,否则会重复扫描 @Filter(type = FilterType.ASSIGNABLE_TYPE, classes = {AppConfig.class}) }) @Configuration public class RootConfig { @Bean public Parent parent() { return new Parent(); } }
然后我们先在Controller里面注入这两个Bean,如下:
@Controller @RequestMapping("/controller") public class HelloController implements ApplicationContextAware { @Autowired private HelloService helloService; @Autowired private Parent parent; @Autowired private Child child; @ResponseBody @GetMapping("/hello") public String helloGet() { System.out.println(parent); System.out.println(child); System.out.println(helloService); System.out.println(helloService.hello()); return "hello...Get"; } }
启动,访问。我们发现一切正常,并且都有值。
现在我在Service里注入这两个类如下:
@Service public class HelloServiceImpl implements HelloService, ApplicationContextAware { @Autowired private Parent parent; @Autowired private Child child; @Override public Object hello() { System.out.println(parent); System.out.println(child); return "service hello"; } }
启动项目,我们就发现报错了,找不到Child这和Bean.
从上面这个例子,就可以看出。子容器是可以访问父容器里的Bean的,但是父容器不能访问子容器内的Bean。所以很显然,直接向Service里面@Autowire一个Controller,启动时候也是会报错的~
另外可以说一点,父子容器的初始化顺序为:先父容器,再子容器。所以web组件一般都是最后被初始化的(当然还存在循环嵌套的情况,另当别论了)。因为若使用web.xml配置Spring容器,是先执行ContextLoaderListener#contextInitialized启动Spring容器,再初始化DispatcherServlet来启动web容器的