springMVC全注解启动和容器的初始化

SpringMVC 专栏收录该内容
9 篇文章 1 订阅

前言

本文主要了解springMVC全注解启动和springMVC父子容器的初始化

在学习springMVC注解启动启动之前,我们先了解下ServletContainerInitializer

在web容器启动时为提供给第三方组件机会做一些初始化的工作,例如注册servlet或者filtes等,servlet3.0规范中通过ServletContainerInitializeronStartup(Set<Class<?>> var1, ServletContext var2)实现此功能。就是容器启动是会调用这个方法。

每个框架要使用ServletContainerInitializer就必须在对应的jar包的META-INF/services 目录创建一个名为javax.servlet.ServletContainerInitializer的文件,文件内容指定具体的ServletContainerInitializer实现类。
在这里插入图片描述
在spring中,其实现类是org.springframework.web.SpringServletContainerInitializer

@HandlesTypes(WebApplicationInitializer.class)
public class SpringServletContainerInitializer implements ServletContainerInitializer {

	/**
	 * 容器启动时就会分别调用每个ServletContainerInitializer的onStartup方法,并将感兴趣的类作为参数传入。
	 * @param webAppInitializerClasses tomcat会扫描项目中所有的WebApplicationInitializer,通过@HandlesTypes注解,注入WebApplicationInitializer的子类
	 * @param servletContext servlet上下文
	 */
	public void onStartup(@Nullable Set<Class<?>> webAppInitializerClasses, ServletContext servletContext) throws ServletException {
		List<WebApplicationInitializer> initializers = new LinkedList<>();
		if (webAppInitializerClasses != null) {
			for (Class<?> waiClass : webAppInitializerClasses) {
				//注入的class必须是WebApplicationInitializer的子类,且不是接口和抽象类
				if (!waiClass.isInterface() && !Modifier.isAbstract(waiClass.getModifiers()) &&
						WebApplicationInitializer.class.isAssignableFrom(waiClass)) {
					try {
					    //实例话符合要求的class,并将其对象放到initializers集合中
						initializers.add((WebApplicationInitializer)
								ReflectionUtils.accessibleConstructor(waiClass).newInstance());
					}
					catch (Throwable ex) {
						throw new ServletException("Failed to instantiate WebApplicationInitializer class", ex);
					}
				}
			}
		}
		if (initializers.isEmpty()) {
			servletContext.log("No Spring WebApplicationInitializer types detected on classpath");
			return;
		}

		servletContext.log(initializers.size() + " Spring WebApplicationInitializers detected on classpath");
		AnnotationAwareOrderComparator.sort(initializers);
		//调用initializers集合中的WebApplicationInitializer对象的onStartup方法。
		for (WebApplicationInitializer initializer : initializers) {
			initializer.onStartup(servletContext);
		}
	}

}

简单来说:web容器启动时,会调用ServletContainerInitializer子类的onStartup方法,对于SpringMvc来说,容器启动时会调用其SpringServletContainerInitializer的onStartup方法,进而调用项目中每个WebApplicationInitializer子类的的onStartup方法。

一、web.xml代码化

说到web.xml代码化,那就不得不要将WebApplicationInitializer,它是web.xml代码化的核心接口
在这里插入图片描述

补充:WebApplicationInitializer还有一个直系抽象类AbstractReactiveWebInitializer

AbstractContextLoaderInitializer:将SpringIOC容器和WEB容器建立关系

public abstract class AbstractContextLoaderInitializer implements WebApplicationInitializer {

	@Override
	public void onStartup(ServletContext servletContext) throws ServletException {
		registerContextLoaderListener(servletContext);
	}
    protected void registerContextLoaderListener(ServletContext servletContext) {
	   //创建SpringIoc容器
	   //createRootApplicationContext是模版方法
		WebApplicationContext rootAppContext = createRootApplicationContext();
		if (rootAppContext != null) {
		    //注册ContextLoaderListener监听器,并监听SpingIOC容器是否创建
			ContextLoaderListener listener = new ContextLoaderListener(rootAppContext);
			//如果SpingIOC容器创建了,那么就创建根容器 
			//getRootApplicationContextInitializers是模版方法
			listener.setContextInitializers(getRootApplicationContextInitializers());
			//把这个监听器放到Servlet上下文中
			servletContext.addListener(listener);
		}
		else {
			logger.debug("No ContextLoaderListener registered, as " +
					"createRootApplicationContext() did not return an application context");
		}
	}
    
	//创建SpingIOC容器
	protected abstract WebApplicationContext createRootApplicationContext();
	@Nullable
	protected ApplicationContextInitializer<?>[] getRootApplicationContextInitializers() {
		return null;
	}

这段代码的作用是,创建根容器(一般是springIOC容器),创建监听器ContextLoaderListener监听根容器,并将其注册到ServletContext中.
如果容器启动了,将初始化SpringIOC容器.
相当于xml如下

<web-app>
    <listener>
        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    </listener>
    <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>/WEB-INF/自己设置根容器路径.xml</param-value>
    </context-param>
    ....
</web-app>

二、SpringIOC容器容器的初始化

ContextLoaderListener是一个ServletContextListener。
WEB服务器启动时,ServletContextListener 的 contextInitialized()方法就会被调用
WEB服务器将要关闭时,ServletContextListener 的 contextDestroyed()方法就会被调用

public class ContextLoaderListener extends ContextLoader implements ServletContextListener {

	//忽略代码.........
	public ContextLoaderListener(WebApplicationContext context) {
		super(context);
	}

    //WEB服务器启动时调用
	@Override
	public void contextInitialized(ServletContextEvent event) {
		initWebApplicationContext(event.getServletContext());
	}
    //WEB服务器将要关闭时调用
	@Override
	public void contextDestroyed(ServletContextEvent event) {
		closeWebApplicationContext(event.getServletContext());
		ContextCleanupListener.cleanupAttributes(event.getServletContext());
	}
}

ContextLoaderListener的具体实现都在其父类ContextLoader中

public class ContextLoader { {
	
	@Nullable
	private WebApplicationContext context;
	
    public ContextLoader(WebApplicationContext context) {
		this.context = context;
	}

	
   public WebApplicationContext initWebApplicationContext(ServletContext servletContext) {
        //从servlet上下文中获取对应属性的值是否存在,当然了,一般第一次进来是为null的
		if (servletContext.getAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE) != null) {
			throw new IllegalStateException("");
		}
		servletContext.log("Initializing Spring root WebApplicationContext");
		Log logger = LogFactory.getLog(ContextLoader.class);
		if (logger.isInfoEnabled()) {
			logger.info("Root WebApplicationContext: initialization started");
		}
		long startTime = System.currentTimeMillis();
		try {
			//本文中是注解驱动的方式,所以此处不会null。
			if (this.context == null) {
			   // 这句特别重要,兼容了web.xml的方式以及注解驱动的方式。本文中是注解驱动的方式,所以此处不会null。
				this.context = createWebApplicationContext(servletContext);
			}
			if (this.context instanceof ConfigurableWebApplicationContext) {
				ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) this.context;
				//一般来说刚创建的context并没有处于激活状态,所以会进来完善一些更多的容器信息。比如刷新容器等等
				if (!cwac.isActive()) {
					//绝大多数情况下,SpringIOC容器不用再给设置父容器
					if (cwac.getParent() == null) {
						ApplicationContext parent = loadParentContext(servletContext);
						cwac.setParent(parent);
					}
					//读取相应的配置并且刷新context对象,注册和实例话springbean
					configureAndRefreshWebApplicationContext(cwac, servletContext);
				}
			}
			//放进ServletContext上下文,避免再次被初始化,也让我们能更加方便的获取到容器
			servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.context);

			//此处把容器和当前线程绑定,这样就可以更加方便得得到容器.类为:ContextLoader
			ClassLoader ccl = Thread.currentThread().getContextClassLoader();
			
            //忽略代码。。。
            if (logger.isInfoEnabled()) {
				long elapsedTime = System.currentTimeMillis() - startTime;
				logger.info("Root WebApplicationContext initialized in " + elapsedTime + " ms");
			}
			return this.context;
		}
		catch (RuntimeException | Error ex) {
			logger.error("Context initialization failed", ex);
			servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, ex);
			throw ex;
		}
	}
	
}
//读取相应的配置并且刷新context对象
protected void configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac, ServletContext sc) {
		//一般此处为真,给ApplicationContext设置一个id
		if (ObjectUtils.identityToString(wac).equals(wac.getId())) {
			//获取servletContext中的contextId属性  contextId,可在web.xml里配置,一般也不用配置,采用else里的默认值即可
			String idParam = sc.getInitParameter(CONTEXT_ID_PARAM);
			if (idParam != null) {
				//存在则设为指定的id名
				wac.setId(idParam);
			}
			else {
				// 生成默认id... 一般为org.springframework.web.context.WebApplicationContext:${contextPath}
				wac.setId(ConfigurableWebApplicationContext.APPLICATION_CONTEXT_ID_PREFIX +ObjectUtils.getDisplayString(sc.getContextPath()));
			}
		}

		//让容器关联上servlet上下文
		wac.setServletContext(sc);
		//读取contextConfigLocation属性(在web.xml配置,但是注解驱动里没有,因此为null)
		String configLocationParam = sc.getInitParameter(CONFIG_LOCATION_PARAM);
		if (configLocationParam != null) {
			//设置指定的spring文件所在地,支持classpath前缀并多文件,以,;为分隔符
			wac.setConfigLocation(configLocationParam);
		}
		ConfigurableEnvironment env = wac.getEnvironment();
		if (env instanceof ConfigurableWebEnvironment) {
			((ConfigurableWebEnvironment) env).initPropertySources(sc, null);
		}
		customizeContext(sc, wac);
		//这里先理解为就是初始化容器,比如加载bean、拦截器、各种处理器的操作就够了~(也是最耗时的一步操作)
		wac.refresh();
	}

该方法完成之后,看到控制台log日志:

Root WebApplicationContext: initialization completed in 75383 ms

就证明Spring根容器就初始化完成了

三、注册DispatcherServlet

AbstractDispatcherServletInitializer:注册和设置DispatcherServlet

public abstract class AbstractDispatcherServletInitializer extends AbstractContextLoaderInitializer {

	//定义servlet名字 <servlet-name>springmvc</servlet-name>
	public static final String DEFAULT_SERVLET_NAME = "dispatcher";

	public void onStartup(ServletContext servletContext) throws ServletException {
		super.onStartup(servletContext);
		registerDispatcherServlet(servletContext);
	}

	protected void registerDispatcherServlet(ServletContext servletContext) {
		String servletName = getServletName();
		Assert.hasLength(servletName, "getServletName() must not return null or empty");
        //创建web容器
		WebApplicationContext servletAppContext = createServletApplicationContext();
		Assert.notNull(servletAppContext, "createServletApplicationContext() must not return null");
        //创建DispatcherServlet对象
		FrameworkServlet dispatcherServlet = createDispatcherServlet(servletAppContext);
		Assert.notNull(dispatcherServlet, "createDispatcherServlet(WebApplicationContext) must not return null");
		dispatcherServlet.setContextInitializers(getServletApplicationContextInitializers());
        //把dispatcherServlet作为Servlet注册到上下文中
		ServletRegistration.Dynamic registration = servletContext.addServlet(servletName, dispatcherServlet);
		if (registration == null) {
			throw new IllegalStateException("Failed to register servlet with name '" + servletName + "'. " +
					"Check if there is another servlet registered under the same name.");
		}
        //容器在启动的时候加载这个servlet,其优先级为1(正数的值越小,该servlet的优先级越高,应用启动时就越先加载)
		registration.setLoadOnStartup(1);
		//设置Servlet映射mapping路径
		//getServletMappings()是模版方法,需要我们自己配置
		registration.addMapping(getServletMappings());
		//设置是否支持异步请求
		//isAsyncSupported默认是true
		registration.setAsyncSupported(isAsyncSupported());
        //处理自定义的Filter进来,一般我们Filter不这么加进来,而是自己@WebFilter,或者借助Spring,  
        //备注:这里添加进来的Filter都仅仅只拦截过滤上面注册的dispatchServlet
		Filter[] filters = getServletFilters();
		if (!ObjectUtils.isEmpty(filters)) {
			for (Filter filter : filters) {
				registerServletFilter(servletContext, filter);
			}
		}
		//这个很清楚:调用者若相对dispatcherServlet有自己更个性化的参数设置,复写此方法即可
		customizeRegistration(registration);
	}
	protected void customizeRegistration(ServletRegistration.Dynamic registration) {}
	//设置过滤器规则
	protected Filter[] getServletFilters() {return null;}
	//是否支持异步请求
	protected boolean isAsyncSupported() {return true;}
    //创建web容器
	protected abstract WebApplicationContext createServletApplicationContext();
    //设置Servlet到mapping的映射
	protected abstract String[] getServletMappings();
	@Nullable
	protected ApplicationContextInitializer<?>[] getServletApplicationContextInitializers() {
		return null;
	}
}

这段代码的作用是,创建web容器,在web容器中创建DispatcherServlet对象对象,并把dispatcherServlet作为Servlet注册到ServletContext中。这段代码作用

<web-app>
     ....
    <servlet>
        <servlet-name>dispatcher</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <init-param>
            <param-name>contextConfigLocation</param-name>
            <param-value>/WEB-INF/自己设置web容器路径.xml</param-value>
        </init-param>
        <load-on-startup>1</load-on-startup>
    </servlet>
    
    <servlet-mapping>
        <servlet-name>dispatcher</servlet-name>
        <url-pattern>自己设置</url-pattern>
    </servlet-mapping>
    
  ..可以设置filter
</web-app>

四、SpringMVC容器的初始化

Servlet 的生命周期有3个阶段:初始化阶段,响应客户请求阶段,终止阶段。javax.servlet.Servlet接口中定义了三个 方法init(), service(), destroy(),它们将分 别 在 Servlet 的 不 同 阶 段 被 调 用 。
1、调用Servlet的init方法,时机:客户首次向 Servlet 发出请求后,或者容器在启动(设置load-on-startup为1)
2、客户向 Servlet 发出请求后,调用service方法
3、调用Servlet的destroy方法,时机是:Web应用被终止或Servlet容器终止运行或 Servlet容器重新装载Servlet的新实例

在这里插入图片描述
从上图可以看出DispatcherServlet是一个Servlet,由于其load-on-startup为1,所以在容器启动后,就会调用其init方法

public abstract class HttpServletBean extends HttpServlet implements EnvironmentCapable, EnvironmentAware {

	public final void init() throws ServletException {

		// 从init参数设置bean属性.
		PropertyValues pvs = new ServletConfigPropertyValues(getServletConfig(), this.requiredProperties);
		if (!pvs.isEmpty()) {
			try {
				BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(this);
				ResourceLoader resourceLoader = new ServletContextResourceLoader(getServletContext());
				bw.registerCustomEditor(Resource.class, new ResourceEditor(resourceLoader, getEnvironment()));
				initBeanWrapper(bw);
				bw.setPropertyValues(pvs, true);
			}
			catch (BeansException ex) {
				if (logger.isErrorEnabled()) {
					logger.error("Failed to set bean properties on servlet '" + getServletName() + "'", ex);
				}
				throw ex;
			}
		}

		// 让子类做他们喜欢的任何初始化
		initServletBean();
	}
public abstract class FrameworkServlet extends HttpServletBean implements ApplicationContextAware {

	protected final void initServletBean() throws ServletException {
		  //忽略代码。。。
		try {
		    // 这是重点,开始初始化这个子容器了
			this.webApplicationContext = initWebApplicationContext();
			//继续留一个口,给子类去复写初始化所需要的操作  一般都为空实现即可,除非自己要复写DispatcherServlet,做自己需要做的事
			initFrameworkServlet();
		}
		catch (ServletException | RuntimeException ex) {
			logger.error("Context initialization failed", ex);
			throw ex;
		}

		//忽略代码。。。
		
        //当我们看到这句日志,就能知道dispatcherServlet已经初始化完成,web子容器也就初始化完成了
		if (logger.isInfoEnabled()) {
			logger.info("Completed initialization in " + (System.currentTimeMillis() - startTime) + " ms");
		}
	}

protected WebApplicationContext initWebApplicationContext() {
       //获取父容器对象
		WebApplicationContext rootContext = WebApplicationContextUtils.getWebApplicationContext(getServletContext());
		WebApplicationContext wac = null;

		if (this.webApplicationContext != null) {
			// 在构造时注入了一个上下文实例->使用它
			wac = this.webApplicationContext;
			if (wac instanceof ConfigurableWebApplicationContext) {
				ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) wac;
				if (!cwac.isActive()) {
					if (cwac.getParent() == null) {
						//此处把根容器,设置为自己的父容器
						cwac.setParent(rootContext);
					}
					//根据绑定的配置,初始化、刷新容器
					configureAndRefreshWebApplicationContext(cwac);
				}
			}
		}
		//若是web.xml方式,会走这里
		if (wac == null) {
			wac = findWebApplicationContext();
		}
		//若是web.xml方式,会走这里
		if (wac == null) {
			wac = createWebApplicationContext(rootContext);
		}
		if (!this.refreshEventReceived) {
			synchronized (this.onRefreshMonitor) {
			//上下文不是具有刷新功能的ConfigurableApplicationContext
            //在构建时注入的支持或上下文已经刷新->在此处手动触发初始刷新。
            //子类实现
			onRefresh(wac);
			}
		}
      //我们是否需要吧我们的容器发布出去,作为ServletContext的一个属性值呢
		if (this.publishContext) {
			// Publish the context as a servlet context attribute.
			String attrName = getServletContextAttributeName();
			getServletContext().setAttribute(attrName, wac);
		}
		return wac;
	}
	
protected void configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac) {
		if (ObjectUtils.identityToString(wac).equals(wac.getId())) {
			//应用程序上下文id仍设置为其原始默认值,根据可用信息分配更有用的id
			if (this.contextId != null) {
				wac.setId(this.contextId);
			}
			else {
				// 生成默认id。。。
				wac.setId(ConfigurableWebApplicationContext.APPLICATION_CONTEXT_ID_PREFIX +
						ObjectUtils.getDisplayString(getServletContext().getContextPath()) + '/' + getServletName());
			}
		}

		wac.setServletContext(getServletContext());
		wac.setServletConfig(getServletConfig());
		wac.setNamespace(getNamespace());
		wac.addApplicationListener(new SourceFilteringListener(wac, new ContextRefreshListener()));
         
		ConfigurableEnvironment env = wac.getEnvironment();
		if (env instanceof ConfigurableWebEnvironment) {
			((ConfigurableWebEnvironment) env).initPropertySources(getServletContext(), getServletConfig());
		}
        //继续留一个口,给子类去复写初始化所需要的操作
		postProcessWebApplicationContext(wac);
		//调用前端控制器中所有ApplicationContextInitializer对象的Initializers方法
		applyInitializers(wac);
		//这里先理解为就是初始化容器,比如加载bean、拦截器、各种处理器的操作就够了~(也是最耗时的一步操作)
		wac.refresh();
	}

FrameworkServlet策略式的实现了监听方法,监听应用的刷新事件。当我们刷新应用的时候(比如上面执行refresh()方法,这里就会执行,并且打上标记说已经执行过了),然而onRefresh()是一个模版方法,具体实现交给子类,这样子DispatcherServlet就可以做做初始化web组件的一些事情了~ 这种设计模式可谓非常优秀,

这就是为何会抽象出FrameworkServlet的原因,因为它设计的初衷不仅仅只想支持到Servlet

自此。SpringMVC父子容器已经启动完毕

优点:能让web环境和普通的Spring环境达到隔离的效果。web容器专注于管理web相关Bean,其余的交给父容器即可。 这样子强制隔离,也能驱动我们在编码过程中注重分层,使得层次结构更加的明晰

缺点:父子容器的设计提高了Spring初始化、管理Bean的复杂度(虽然对我们使用者一般都无感),我们万一要用到相关功能的时候,若不理解原理会有莫名其妙的一些问题,提高了复杂性

理论上我们可以有任意多个容器(只是我们一般其它的都只放进主容器统一管理上,但Spring是提供了这样的功能的),比如

主容器:applicationContext.xml(主文件,包括JDBC配置,hibernate.cfg.xml,与所有的Service与DAO基类)
web子容器:application-servlet.xml(管理Spring MVC9打组件以及相关的Bean)
cache子容器:applicationContext-cache.xml(cache策略配置,管理和缓存相关的Bean)
JMX子容器:applicationContext-jmx.xml(JMX相关的Bean)

总之:通过父子容器分层管理是好的设计思想,但是在编码使用中,大道至简才是我们追求的方式。所以我个人觉得:父子容器的设计并不是一根很好的设计(理想是好的,但实施上却不见得是好的),估计这也是为何Spring Boot中只采用一个容器的原因吧,简单的或许就是最好的

值得注意的是,springMVC在调用HandlerMapper进行url到controller函数方法映射解析的时候,HandlerMapper会在springMVC容器中寻找controller,也就是在子容器中寻找,不会去父容器spring容器中寻找的。
所以如果用父容器来管理controller的话,子容器不去管理,在访问页面的时候会出现404错误

这里在多说一下在DispatcherServlet重写了onRefresh方法。在MVC容器初始化完成后,这个方法会会赋予DispatcherServlet的请求调度功能

public class DispatcherServlet extends FrameworkServlet {

	protected void onRefresh(ApplicationContext context) {
		initStrategies(context);
	}

	//初始化此servlet使用的策略对象.可以在子类中重写以初始化进一步的策略对象.
	protected void initStrategies(ApplicationContext context) {
		initMultipartResolver(context);
		initLocaleResolver(context);
		initThemeResolver(context);
		initHandlerMappings(context);
		initHandlerAdapters(context);
		initHandlerExceptionResolvers(context);
		initRequestToViewNameTranslator(context);
		initViewResolvers(context);
		initFlashMapManager(context);
	}

五、注解配置IOC和MVC容器

AbstractAnnotationConfigDispatcherServletInitializer

public abstract class AbstractAnnotationConfigDispatcherServletInitializer extends AbstractDispatcherServletInitializer {

    //创建根容器
	protected WebApplicationContext createRootApplicationContext() {
		Class<?>[] configClasses = getRootConfigClasses();
		if (!ObjectUtils.isEmpty(configClasses)) {
			AnnotationConfigWebApplicationContext context = new AnnotationConfigWebApplicationContext();
			context.register(configClasses);
			return context;
		}
		else {
			return null;
		}
	}

    //创建web容器
	protected WebApplicationContext createServletApplicationContext() {
		AnnotationConfigWebApplicationContext context = new AnnotationConfigWebApplicationContext();
		Class<?>[] configClasses = getServletConfigClasses();
		if (!ObjectUtils.isEmpty(configClasses)) {
			context.register(configClasses);
		}
		return context;
	}
    //
	protected abstract Class<?>[] getRootConfigClasses();
	
	protected abstract Class<?>[] getServletConfigClasses();
}

案例

SpringIOC容器

//扫描所有类,除了标注注解@Controller,@ControllerAdvice,@RestControllerAdvice的类
@ComponentScan(value = "clyu", excludeFilters = {
        @ComponentScan.Filter(type = FilterType.ANNOTATION, classes = {Controller.class, ControllerAdvice.class, RestControllerAdvice.class})
})
@Configuration
@Configuration
public class SpringIocConfig {}

WEB容器

//只扫描标注注解@Controller,@ControllerAdvice,@RestControllerAdvice的类
@ComponentScan(value = "clyu", useDefaultFilters = false,
               includeFilters = {@ComponentScan.Filter(type = FilterType.ANNOTATION, classes = {Controller.class, ControllerAdvice.class, RestControllerAdvice.class})}
        )
@Configuration
public class MyWebMvcConfig extends WebMvcConfigurationSupport {}

启动类配置

public class MyWebAppInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {
    @Override
    protected Class<?>[] getRootConfigClasses() {
        return new Class[]{SpringIocConfig.class};
    }
    @Override
    protected Class<?>[] getServletConfigClasses() {
        return new Class[]{MyWebMvcConfig.class};
    }
    @Override
    protected String[] getServletMappings() {
        return new String[]{"/"};
    }
}

useDefaultFilters默认值为true,表示默认情况下@Component、@Repository、@Service、@Controller都会扫描
useDefaultFilters=false加上includeFilters我们就可以只扫描指定的组件了,比如Spring MVC的web子容器只扫描Controller组件
excludeFilters的时候,就不需要去设置useDefaultFilters=false,这样子我们直接排除掉即可~

特别注意:useDefaultFilters的正确使用,不要造成重复扫描。否则很有可能造成事务不生效,并且你还非常不好定位这个错误

其相当于xml

<web-app>

    <listener>
        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    </listener>

    <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>/WEB-INF/SpringIocConfig.xml</param-value>
    </context-param>

    <servlet>
        <servlet-name>dispatcher</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <init-param>
            <param-name>contextConfigLocation</param-name>
            <param-value>/WEB-INF/MyWebMvcConfig.xml</param-value>
        </init-param>
        <load-on-startup>1</load-on-startup>
    </servlet>

    <servlet-mapping>
        <servlet-name>dispatcher</servlet-name>
        <url-pattern>/</url-pattern>
    </servlet-mapping>

</web-app>

测试1

@RestController
@RequestMapping("person")
public class PersonController {
    @GetMapping("getList")
    public String getPerson(){
        return "1";
    }
}

运行程序,调用路径http://localhost:8080/clyu/person/getList输出 1

特别注意的是

按照上面的配置,我偶然的发现了,SpringIocConfig仍然还是去扫描了我的controller,导致我的controller被扫描了两次,怎么回事呢???
在这里插入图片描述
找了好久,终于找到原因了,并不是@ComponentScan或者excludeFilters的问题,而是因为咱们在执行SpringIOCConfig的时候,虽然不去扫描Controller注解了,但是它会扫描MyWebMvcConfig.java这个配置类,从而间接的又去扫描了@Controller了,因此最正确的做法应该

@ComponentScan(value = "clyu", excludeFilters = {
        @ComponentScan.Filter(type = FilterType.ANNOTATION, classes = {Controller.class, ControllerAdvice.class, RestControllerAdvice.class}),@ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE, classes = {MyWebMvcConfig.class})
})
@Configuration
public class SpringIocConfig  {
}

这样子,我们的Controller就只会被扫描一次了,容器也就非常的干净了

测试2

@RestController
@RequestMapping("person")
public class PersonController {
    @Autowired
    private PersonService personService;
    @GetMapping("getList")
    public Person getPerson(){
        return personService.getPerson();
    }
}

运行程序,调用路径http://localhost:8080/clyu/person/getList输出 , 发现接口报错。当我们在web配置类添加@EnableWebMvc。接口正常输出。

原因浅析:

不开启注解@EnableWebMvc。springMVC项目会默认注册4个消息转换器,他们是

ByteArrayHttpMessageConverter      StringHttpMessageConverter,
SourceHttpMessageConverter         AllEncompassingFormHttpMessageConverter

开启注解@EnableWebMvc,springMVC项目会默认注册7个消息转换器,他们是

AllEncompassingFormHttpMessageConverter    StringHttpMessageConverter,
ByteArrayHttpMessageConverter              ResourceHttpMessageConverter,
ResourceRegionHttpMessageConverter         SourceHttpMessageConverter
Jaxb2RootElementHttpMessageConverter
  • 4
    点赞
  • 1
    评论
  • 8
    收藏
  • 一键三连
    一键三连
  • 扫一扫,分享海报

©️2021 CSDN 皮肤主题: 技术黑板 设计师:CSDN官方博客 返回首页
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、C币套餐、付费专栏及课程。

余额充值