关于 Spring 父子容器的三个问题

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/qq_35542218/article/details/84201466

关于 Spring 父子容器的三个问题

前言

  对 Spring 父容器和子容器做了一个案例的测试。对于已有的问题进行了一个好的测试。

正文

  我先把本项目的Web启动类,以及一些基本配置发上来。关于如何构建一个Web项目,可以参数我的这篇文章《纯Java启动Web(无配置web.xml)》

WebApp.java(启动类)

package vip.wulang.start;

import org.springframework.web.servlet.support.AbstractAnnotationConfigDispatcherServletInitializer;
import vip.wulang.config.ChildContext;
import vip.wulang.config.ParentContext;

/**
 * @author CoolerWu on 2018/11/18.
 * @version 1.0
 */
public class WebApp extends AbstractAnnotationConfigDispatcherServletInitializer {
    @Override
    protected Class<?>[] getRootConfigClasses() {
        return new Class[]{ParentContext.class};
    }

    @Override
    protected Class<?>[] getServletConfigClasses() {
        return new Class[]{ChildContext.class};
    }

    @Override
    protected String[] getServletMappings() {
        return new String[]{"/"};
    }
}

ParentContext.java(父容器也就是 Spring 容器)

package vip.wulang.config;

import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.FilterType;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;

/**
 * @author CoolerWu on 2018/11/18.
 * @version 1.0
 */
@Configuration
@ComponentScan(
        value = "vip.wulang",
        excludeFilters = @ComponentScan.Filter(type = FilterType.ANNOTATION, classes = EnableWebMvc.class)
)
public class ParentContext implements ApplicationContextAware {

    private static ApplicationContext parentContext;

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.parentContext = applicationContext;
    }

    public static ApplicationContext getParentContext() {
        return parentContext;
    }
}

ChildContext.java(子容器也就是 Spring MVC 容器)

package vip.wulang.config;

import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.ViewResolver;
import org.springframework.web.servlet.config.annotation.DefaultServletHandlerConfigurer;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
import org.springframework.web.servlet.view.InternalResourceViewResolver;
import vip.wulang.controller.UserController;

/**
 * @author CoolerWu on 2018/11/18.
 * @version 1.0
 */
@Configuration
@EnableWebMvc
@ComponentScan("vip.wulang.controller")
public class ChildContext extends WebMvcConfigurerAdapter implements ApplicationContextAware {

    private static ApplicationContext childContext;

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.childContext = applicationContext;
    }

    public static ApplicationContext getChildContext() {
        return childContext;
    }

    @Bean
    public ViewResolver viewResolver() {
        InternalResourceViewResolver resolver = new InternalResourceViewResolver();
        resolver.setPrefix("/WEB-INF/view/");
        resolver.setSuffix(".jsp");
        resolver.setExposeContextBeansAsAttributes(true);
        return resolver;
    }

    public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {
        configurer.enable();
    }
    
}

UserController.java(Controller类,用于测试)

package vip.wulang.controller;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import static vip.wulang.config.ParentContext.getParentContext;
import static vip.wulang.config.ChildContext.getChildContext;

@Controller
public class UserController {

}

问题一:Spring 父容器和子容器扫描同一个类时,子容器还会创建一个新的bean实例吗?

  其实这个扫描跟在 Java 配置类里面写一个方法并带有 @Bean 注解是一个道理,倘若 ParentContext 和 ChildContext 都有一个 bean 实例,那肯定不一样。我们使用 @ComponentScan 注解来扫描同一个包,来看看具体结果,现在 UserController 类添加如下代码:

	@RequestMapping("/")
	@ResponseBody
	public String getAllUser() {
		System.out.println(getParentContext()); // 父容器
		System.out.println(getChildContext()); // 子容器
		System.out.println(getChildContext().getParent() == getParentContext()); // 验证是否属于父子关系
		System.out.println(getParentContext().getBean("userController") == getChildContext().getBean("userController")); // 验证两个容器的 userController 是否不同
		return "ok";
	}

Root WebApplicationContext: startup date [Sun Nov 18 15:24:57 CST 2018]; root of context hierarchy

WebApplicationContext for namespace ‘dispatcher-servlet’: startup date [Sun Nov 18 15:24:57 CST 2018]; parent: Root WebApplicationContext

true

false

  看见结果,就知道了。总结:父容器和子容器同时扫描一个类会产生不同实例,并且倘若子容器没有该实例,可以从父容器里面获取。

问题二:一个类被父容器和子容器同时扫描,并且它也实现了 ApplicationContextAware 这个接口,那么注入的是哪个接口呢,或者说会报错呢?

  话不多说,进行测试,我还是在原来的基础上修改了 UserController 一个类而已,代码如下:

package vip.wulang.controller;

import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import static vip.wulang.config.ParentContext.getParentContext;
import static vip.wulang.config.ChildContext.getChildContext;

@Controller
public class UserController implements ApplicationContextAware {
	private ApplicationContext applicationContext;

	@Override
	public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
		this.applicationContext = applicationContext;
	}

	@RequestMapping("/")
	@ResponseBody
	public String getAllUser() {
		System.out.println(applicationContext == getParentContext()); // 是否是父容器
		System.out.println(applicationContext == getChildContext()); // 是否是子容器
		return "ok";
	}

}

false

true

  你以为就是子容器注入成功了?是的没错,它确实是注入成功了,但是父容器也是被注入过,只不过子容器是最后被注入的。稍微修改 UserController 类的方法 setApplicationContext 代码:

	@Override
	public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
		this.applicationContext = applicationContext;

        // 如果是父容器,输出
		if (applicationContext == getParentContext()) {
			System.out.println("Parent is coming...");
		}
        // 如果是子容器,输出
		if (applicationContext == getChildContext()) {
			System.out.println("Child is coming...");
		}
	}

  在控制台上寻找,你会发现:

Parent is coming…

Child is coming…

  原来 Spring 注入 ApplicationContext 类,是按照一定顺序注入的,先注入父容器,再注入子容器,最后注入的才能决定真正的 ApplicationContext 的引用类型。

问题三:Spring与SpringMVC的容器冲突的原因到底在那里?

  我们先把 ChildContext 类的 @ComponentScan(“vip.wulang.controller”) 注释掉,启动服务器,会出现 404,表示没有找到。那我们先把 ChildContext 类的 @ComponentScan(“vip.wulang.controller”) 的注释去掉,然后把 ParentContext 类的 @ComponentScan 注解中扫描到 vip.wulang.controller 这个包给排除掉,如下:

// ParentContext 类
@ComponentScan(
        value = "vip.wulang",
        excludeFilters = {
                @ComponentScan.Filter(type = FilterType.ANNOTATION, classes = EnableWebMvc.class),
                @ComponentScan.Filter(type = FilterType.ANNOTATION, classes = Controller.class)
        }
)

  启动服务器,我们会发现该服务器可以正常运作并且返回了JSON “ok”,通过Debug,我们找到了 AbstractHandlerMethodMapping 类,该类的 initHandlerMethods() 方法的作用是扫描ApplicationContext中的bean,检测和注册处理程序方法,该方法的源代码如下:

	protected void initHandlerMethods() {
		if (logger.isDebugEnabled()) {
			logger.debug("Looking for request mappings in application context: " + getApplicationContext());
		}
		String[] beanNames = (this.detectHandlerMethodsInAncestorContexts ?
				BeanFactoryUtils.beanNamesForTypeIncludingAncestors(getApplicationContext(), Object.class) :
				getApplicationContext().getBeanNamesForType(Object.class));

		for (String beanName : beanNames) {
			if (!beanName.startsWith(SCOPED_TARGET_NAME_PREFIX)) {
				Class<?> beanType = null;
				try {
					beanType = getApplicationContext().getType(beanName);
				}
				catch (Throwable ex) {
					// An unresolvable bean type, probably from a lazy bean - let's ignore it.
					if (logger.isDebugEnabled()) {
						logger.debug("Could not resolve target class for bean with name '" + beanName + "'", ex);
					}
				}
                // 见下面 RequestMappingHandlerMapping 类的部分源代码
				if (beanType != null && isHandler(beanType)) {
					detectHandlerMethods(beanName);
				}
			}
		}
		handlerMethodsInitialized(getHandlerMethods());
	}

  RequestMappingHandlerMapping 类的 isHandler() 方法的作用是判断是否含有 Controller 或者 RequestMapping 注解,该方法的源代码如下:

	@Override
	protected boolean isHandler(Class<?> beanType) {
		return (AnnotatedElementUtils.hasAnnotation(beanType, Controller.class) ||
				AnnotatedElementUtils.hasAnnotation(beanType, RequestMapping.class));
	}

  我们会发现,Spring MVC 容器只在该容器中查找 bean 实例,而没有去查找父容器,这就是关键所在。根据官方建议我们就可以很好把不同类型的Bean分配到不同的容器中进行管理。所以没必要让父容器也扫描 @Controller 注解,可以进行剔除。

结束语

  遇到每一个知识点,都应该自己去发散思维,把自己不懂得都给用代码测试出来,只有亲眼所见才能印象深刻。
  

猜你喜欢

转载自blog.csdn.net/qq_35542218/article/details/84201466