本文主要了解springMVC全注解启动和springMVC父子容器的初始化
在学习springMVC注解启动启动之前,我们先了解下ServletContainerInitializer
。
在web容器启动时为提供给第三方组件机会做一些初始化的工作,例如注册servlet或者filtes等,servlet3.0规范中通过ServletContainerInitializer
的onStartup(Set
实现此功能。就是容器启动是会调用这个方法。
每个框架要使用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代码化,那就不得不要将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.ContextLoaderListenerlistener-class>
listener>
<context-param>
<param-name>contextConfigLocationparam-name>
<param-value>/WEB-INF/自己设置根容器路径.xmlparam-value>
context-param>
....
web-app>
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根容器就初始化完成了
AbstractDispatcherServletInitializer:注册和设置DispatcherServlet
public abstract class AbstractDispatcherServletInitializer extends AbstractContextLoaderInitializer {
//定义servlet名字 springmvc
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>dispatcherservlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServletservlet-class>
<init-param>
<param-name>contextConfigLocationparam-name>
<param-value>/WEB-INF/自己设置web容器路径.xmlparam-value>
init-param>
<load-on-startup>1load-on-startup>
servlet>
<servlet-mapping>
<servlet-name>dispatcherservlet-name>
<url-pattern>自己设置url-pattern>
servlet-mapping>
..可以设置filter
web-app>
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);
}
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.ContextLoaderListenerlistener-class>
listener>
<context-param>
<param-name>contextConfigLocationparam-name>
<param-value>/WEB-INF/SpringIocConfig.xmlparam-value>
context-param>
<servlet>
<servlet-name>dispatcherservlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServletservlet-class>
<init-param>
<param-name>contextConfigLocationparam-name>
<param-value>/WEB-INF/MyWebMvcConfig.xmlparam-value>
init-param>
<load-on-startup>1load-on-startup>
servlet>
<servlet-mapping>
<servlet-name>dispatcherservlet-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