传统 XML 配置
<!DOCTYPE web-app PUBLIC
"-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
"http://java.sun.com/dtd/web-app_2_3.dtd" >
<web-app>
<display-name>Archetype Created Web Application</display-name>
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>WEB-INF/spring.xml</param-value>
</context-param>
<servlet>
<servlet-name>mvc</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>mvc</servlet-name>
<url-pattern>*.do</url-pattern>
</servlet-mapping>
</web-app>
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/mvc
https://www.springframework.org/schema/mvc/spring-mvc.xsd">
<mvc:annotation-driven/>
</beans>
为什么 Spring MVC 可以做到 0-XML 配置
因为 Servlet 3.0 出了一个规范,所有容器,再启动的时候,需要调用一个实现了 **ServletContainerInitializer 接口 下的 onStartup **方法,具体的实现类,需要再 META-INF.services 下新建一个 javax.servlet.ServletContainerInitializer **文件,内容为实现了 ServletContainerInitializer 接口的**完整类名,同时容器会扫描该类名上的 @HandlesTypes(XXX.class),并扫描 XXX.class 的所有实现类,并传递给 onStartup 方法,同时还会给 onStartup 方法传递一个容器上下文对象
public interface ServletContainerInitializer {
public void onStartup(Set<Class<?>> c, ServletContext ctx)
throws ServletException;
}
利用这一点,我们就可以当容器启动时调用 onStartup 方法的时候,完成 Spring 容器的初始化步骤,同时配置 DispathServlet 对象
Spring-web 的实现
@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) {
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);
}
}
}
所以,我们只需要实现 WebApplicationInitializer 接口就可以完成原来 web.xml 中的配置了
实现 Spring MVC 0-XML 配置 - web.xml
public class MyWebApplicationInitializer implements WebApplicationInitializer {
/**
* Spring MVC 0-XML 配置的原理:
*
* 因为 servlet 3.0 的一个新规范(ServletContainerInitializer),
* 而 tomcat 也遵守了了这个规范,所以会调用 实现了 WebApplicationInitializer 的 onStartup 方法
*
* Spring 定义了一个 org.springframework.web.SpringServletContainerInitializer 类,实现了 servlet 3.0 的这个新规范
*
* Spring 实现方式
* spring-web\META-INF\services\javax.servlet.ServletContainerInitializer,是指需要实现的接口
* javax.servlet.ServletContainerInitializer 文件里面的 org.springframework.web.SpringServletContainerInitializer 是实现类
* @HandlesTypes(WebApplicationInitializer.class) 会由容器进行解析里面定义的接口,找出所有实现类,并回调给 Set<Class<?>> webAppInitializerClasses
* Spring 再对 webAppInitializerClasses 的子类进行遍历,分别调用其 onStartup 的方法
*
* @param servletContext Web上下文对象
* @throws ServletException
*/
public void onStartup(ServletContext servletContext) throws ServletException {
System.out.println("-------onStartup-------");
// Load Spring web application configuration
AnnotationConfigWebApplicationContext ac = new AnnotationConfigWebApplicationContext();
ac.register(AppConfig.class);
// ac.refresh();
// Create and register the DispatcherServlet
// DispatcherServlet 的 init 方法
DispatcherServlet servlet = new DispatcherServlet(ac);
ServletRegistration.Dynamic registration = servletContext.addServlet("app", servlet);
registration.setLoadOnStartup(1);
registration.addMapping("*.do");
}
}
实现 spring.xml 的 Java Configuration
通过 @EnableWebMvc 就可以完美的替代 spring.xml 中的
@Configuration
@ComponentScan("org.wesoft.mvc")
@EnableWebMvc // 相当于 XML 中的 <mvc:annotation-driven/>
public class AppConfig implements WebMvcConfigurer {
/*
Apache Commons FileUpload
To use Apache Commons FileUpload,
you can configure a bean of type CommonsMultipartResolver with a name of multipartResolver.
You also need to have commons-fileupload as a dependency on your classpath.
*/
@Bean
public CommonsMultipartResolver multipartResolver() {
CommonsMultipartResolver commonsMultipartResolver = new CommonsMultipartResolver();
return commonsMultipartResolver;
}
// 会在 Spring mvc 环境初始化完成之后,加载这个方法
@Autowired
public void iniArgumentsResolvers(RequestMappingHandlerAdapter requestMappingHandlerAdapter) {
List<HandlerMethodArgumentResolver> argumentResolvers = new ArrayList<>(requestMappingHandlerAdapter.getArgumentResolvers());
List<HandlerMethodArgumentResolver> customArgumentResolvers = requestMappingHandlerAdapter.getCustomArgumentResolvers();
argumentResolvers.removeAll(customArgumentResolvers);
argumentResolvers.addAll(0, customArgumentResolvers);
requestMappingHandlerAdapter.setArgumentResolvers(argumentResolvers);
}
/**
* 视图处理
*
* @param registry
*/
@Override
public void configureViewResolvers(ViewResolverRegistry registry) {
registry.jsp("/page/", ".html");
}
/**
* 添加消息解析器
*
* @param converters
*/
@Override
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
FastJsonHttpMessageConverter fastJsonHttpMessageConverter = new FastJsonHttpMessageConverter();
converters.add(fastJsonHttpMessageConverter);
}
/**
* 添加参数解析器
*
* @param resolvers initially an empty list
*/
@Override
public void addArgumentResolvers(List<HandlerMethodArgumentResolver> resolvers) {
// 这里其实是传过来一个空集合,对应着 customArgumentResolvers
// 也就是我们其实是在 customArgumentResolvers 里添加自定义方法
// 然后 spring mvc 会将这个集合合并到 argumentResolvers 中,所以并不能保证执行顺序
resolvers.add(new UserParamResolver());
}
}
内嵌 Tomcat
compile group: 'org.apache.tomcat.embed', name: 'tomcat-embed-core', version: '8.5.56'
Xcompile group: 'org.apache.tomcat.embed', name: 'tomcat-embed-jasper', version: '8.5.56'
public class App {
public static void main(String[] args) throws Exception {
Tomcat tomcat = new Tomcat();
tomcat.setPort(8080);
// Context context = tomcat.addContext("/", System.getProperty("java.io.tmpdir"));
// context.addLifecycleListener((LifecycleListener) Class.forName(tomcat.getHost().getConfigClass()).newInstance());
String sourcePath = App.class.getResource("/").getPath();
Context ctx = tomcat.addWebapp("/", new File("research-webmvc/src/webapp").getAbsolutePath());
WebResourceRoot resources = new StandardRoot(ctx);
resources.addPreResources(new DirResourceSet(resources, "/production/classes", sourcePath, "/"));
ctx.setResources(resources);
tomcat.start();
tomcat.getServer().await();
}
}