文章目录
1、可以通过Java类的方式来原理
从Servlet 3.0 开始,允许在Java web项目中通过注解(@WebServlet、@WebFilter@WebListener等)的方式注册Servlet组件,就可以省略到在web中添加web.xml。
原理是:基于 Servlet 3.0 Shared libraries(共享库) / runtimes pluggability(运行时插件能力)
在Servlet容器启动会自动扫描每个jar的ServletContainerInitializer的实现,我们对于ServletContainerInitializer的实现类想要被扫描到,必须提给一个文件:
- 必须在web项目的/META-INF/services/目录下提供一个javax.servlet.ServletContainerInitializer文件
- 文件内容是实现类的全类名。
下面是关于 javax.servlet.ServletContainerInitializer
接口的定义:
package javax.servlet;
import java.util.Set;
public interface ServletContainerInitializer {
/**
* classSet: 是我们需要用到的类型
* servletContext:上下文容器
*/
void onStartup(Set<Class<?>> classSet, ServletContext servletContext ) throws ServletException;
}
1、Spring通过Java配置类的方式配置Spring MVC的相关组件
现在我们来看一下,我们需要通过Java配置类的方式配置Spring MVC的相关组件,要求Servlet必须3.0以上的(所以tomcat版本必须是7以上的),在Spring Mvc官方,建议我们在配置Java配置类的时候,对于Service、DAO相关的配置尽量配置在Root WebApplicationContext , 对于和前端视图做交互的配置在Servlet WebApplicationContext)
下面实现了AbstractAnnotationConfigDispatcherServletInitializer
类,并添加两个配置类,
- 一个是
RootConfig
配置类,用于扫描除了@Controller
以外的其他注解 - 一个是
WebMvcConfig
配置类,只用于扫描@Controller
注解。
把上面两个类传递给AbstractAnnotationConfigDispatcherServletInitializer
的实现类MyWebAppInitializer
,去创建根容器和web容器,具体代码如下:
package com.example.web;
import com.example.web.config.RootConfig;
import com.example.web.config.WebMvcConfig;
import org.springframework.web.servlet.support.AbstractAnnotationConfigDispatcherServletInitializer;
public class MyWebAppInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {
/**
* 获取根容器的配置类:(Spring的配置文件),主要是配Service、DAO、事务等
* */
@Override
protected Class<?>[] getRootConfigClasses() {
return new Class[] {
RootConfig.class};
}
/**
* 获取web容器的配置类,主要是配置视图解析器、拦截器的
* */
@Override
protected Class<?>[] getServletConfigClasses() {
return new Class[] {
WebMvcConfig.class};
}
/**
* 返回我们需要拦截的请求
* */
@Override
protected String[] getServletMappings() {
return new String[] {
"/"};
}
}
接下来两个配置类:
根配置类:
package com.example.web.config;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.FilterType;
import org.springframework.stereotype.Controller;
/**
* 自定扫描的包,在项目的根(RootConfig)配置类中的时候,不扫描@Controller的注解。
*/
@ComponentScan(basePackages = "com.example.web",
excludeFilters = {
@ComponentScan.Filter(type = FilterType.ANNOTATION,classes = {
Controller.class})})
public class RootConfig {
// 省略配代码
}
web配置类:
package com.example.web.config;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.FilterType;
import org.springframework.stereotype.Controller;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.ViewResolverRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
/**
* @EnableWebMvc :启用Spring MVC 的组件
*
* @ComponentScan:自定扫描的包,在web项目配置类中的时候,只扫描使用@Controller的注解。
*/
@EnableWebMvc
@ComponentScan( basePackages = "com.example.web",
includeFilters = {
@ComponentScan.Filter(type = FilterType.ANNOTATION,classes={
Controller.class})},
useDefaultFilters = false)
public class WebMvcConfig implements WebMvcConfigurer {
@Override
public void configureViewResolvers(ViewResolverRegistry registry) {
registry.jsp("/WEB-INF/views/",".jsp");
}
}
3、现在来看一Spring MVC为我们做的哪些
现在我们来看一下,Spring Mvc中自动扫描我们定义的MyWebAppInitializer 的原理,首先在Spring-web的jar中提在/META-INF/services/目录下提供一个javax.servlet.ServletContainerInitializer文件:
文件中的内容是:org.springframework.web.SpringServletContainerInitializer
所以在tomcat启动的时候,会去扫描所有jar包下的有关javax.servlet.ServletContainerInitializer
@HandlesTypes(WebApplicationInitializer.class)
public class SpringServletContainerInitializer implements ServletContainerInitializer {
@Override
public void onStartup(@Nullable Set<Class<?>> webAppInitializerClasses, ServletContext servletContext)
throws ServletException {
List<WebApplicationInitializer> initializers = new LinkedList<>();
if (webAppInitializerClasses != null) {
for (Class<?> waiClass : webAppInitializerClasses) {
// Be defensive: Some servlet containers provide us with invalid classes,
// no matter what @HandlesTypes says...
if (!waiClass.isInterface() && !Modifier.isAbstract(waiClass.getModifiers()) &&
WebApplicationInitializer.class.isAssignableFrom(waiClass)) {
try {
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);
for (WebApplicationInitializer initializer : initializers) {
initializer.onStartup(servletContext);
}
}
}
查看一下org.springframework.web.SpringServletContainerInitializer
,在onStartup()
方法中我发现,传入需要处理的类型是上面注解@HandlesType
中的WebApplicationInitializer
类型,在上面中我们继承的的是AbstractAnnotationConfigDispatcherServletInitializer
,现在来看它的继承关系:
从继承关系来看,我们一步步从WebApplicationInitializer
接口开始到AbstractAnnotationConfigDispatcherServletInitializer
都做了哪些。
在AbstractContextLoaderInitializer
中初始化了根容器
:
public abstract class AbstractContextLoaderInitializer implements WebApplicationInitializer {
/** Logger available to subclasses. */
protected final Log logger = LogFactory.getLog(getClass());
@Override
public void onStartup(ServletContext servletContext) throws ServletException {
registerContextLoaderListener(servletContext);
}
protected void registerContextLoaderListener(ServletContext servletContext) {
WebApplicationContext rootAppContext = createRootApplicationContext();
if (rootAppContext != null) {
ContextLoaderListener listener = new ContextLoaderListener(rootAppContext);
listener.setContextInitializers(getRootApplicationContextInitializers());
servletContext.addListener(listener);
}
else {
logger.debug("No ContextLoaderListener registered, as " +
"createRootApplicationContext() did not return an application context");
}
}
}
在AbstractDispatcherServletInitializer中:
- 创建一个web的ioc容器:
createServletApplicationContext()
; - 创建
了DispatcherServlet
;createDispatcherServlet()
; - 将创建的
DispatcherServlet添加到ServletContext中
; - 添加映射
registration.addMapping(getServletMappings());
public abstract class AbstractDispatcherServletInitializer extends AbstractContextLoaderInitializer {
/**
* The default servlet name. Can be customized by overriding {@link #getServletName}.
*/
public static final String DEFAULT_SERVLET_NAME = "dispatcher";
@Override
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");
WebApplicationContext servletAppContext = createServletApplicationContext();
Assert.notNull(servletAppContext, "createServletApplicationContext() must not return null");
FrameworkServlet dispatcherServlet = createDispatcherServlet(servletAppContext);
Assert.notNull(dispatcherServlet, "createDispatcherServlet(WebApplicationContext) must not return null");
dispatcherServlet.setContextInitializers(getServletApplicationContextInitializers());
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.");
}
registration.setLoadOnStartup(1);
registration.addMapping(getServletMappings());
registration.setAsyncSupported(isAsyncSupported());
Filter[] filters = getServletFilters();
if (!ObjectUtils.isEmpty(filters)) {
for (Filter filter : filters) {
registerServletFilter(servletContext, filter);
}
}
customizeRegistration(registration);
}
而我们使用到的AbstractAnnotationConfigDispatcherServletInitializer继承了AbstractDispatcherServletInitializer类
public abstract class AbstractAnnotationConfigDispatcherServletInitializer
extends AbstractDispatcherServletInitializer
所以我们只需要实现AbstractAnnotationConfigDispatcherServletInitializer中需要的方法,Spring就会帮我们去初始化一个DispatcherServlet,并创建相应的根容器和Web容器,后续我们就可以去在配置类中去配置我们需要配置的例如相应的视图解析器、拦截器等
4、在Spring MVC中的特定bean
bean类型 | 说明 |
---|---|
HandlerMapping |
更具一些标准,将接收到的请求映射到处理程序列表(处理程序拦截器),其中的细节具体看HandlerMapping的实现,最主要的实现支持注解控制器即RequestMappingHandlerMapping 和SimpleUrlHandlerMapping,前者支持@RequestMapping注解方法,后者为URI处理程序显示注解URI路径模式 |
HandlerAdapter |
帮助DispatcherServlet调用映射到处理程序,HandlerAdapter的主要目的就是屏蔽DispatcherServlet的细节。 |
HandlerExceptionResolver |
解决异常的策略,可以将他们映射到处理程序或者HTML错误视图等 |
ViewResolver |
将请求程序返回到基于字符串的逻辑视图名称解析为实际视图,以便返回使用返回的响应。 |
LocaleResolver , LocaleContextResolver |
解决客户端正在使用的区域设置及可能的时区,以便能够提供国际化的视图 |
ThemeResolver |
解析Web应用程序可以使用的主题,如提供个性化布局等 |
MultipartResolver |
解析multi-part请求的抽象,如浏览器表单文件上传 |
FlashMapManager |
存储和检索可用于将属性从一个请求到零一请求的“输入”,和输出 FashMap ,一般用于重定向 |