springMVC零配置原理分析
注解方式初始化
springMVC零配置,使用注解方式初始化
官网参考:
https://docs.spring.io/spring/docs/current/spring-framework-reference/web.html#mvc-servlet
示例代码
前言说明:使用内嵌tomcat,运行使用maven(tomcat7:run)
pom.xml
<modelVersion>4.0.0</modelVersion>
<groupId>com</groupId>
<artifactId>spring-mvc</artifactId>
<version>1.0</version>
<packaging>war</packaging>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>5.0.8.RELEASE</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.apache.tomcat.embed/tomcat-embed-core -->
<!-- <dependency>-->
<!-- <groupId>org.apache.tomcat.embed</groupId>-->
<!-- <artifactId>tomcat-embed-core</artifactId>-->
<!-- <version>8.5.31</version>-->
<!-- </dependency>-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.47</version>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>3.1.0</version>
<scope>provided</scope>
</dependency>
<!-- https://mvnrepository.com/artifact/dom4j/dom4j -->
<dependency>
<groupId>dom4j</groupId>
<artifactId>dom4j</artifactId>
<version>1.6.1</version>
</dependency>
</dependencies>
<build>
<finalName>spring-mvc</finalName>
<plugins>
<plugin>
<groupId>org.apache.tomcat.maven</groupId>
<artifactId>tomcat7-maven-plugin</artifactId>
<version>2.2</version>
<configuration>
<port>80</port>
<path>/</path>
</configuration>
</plugin>
<!--tomcat7:run-->
</plugins>
</build>
package com.mvc;
public class MyWebApplicationInitializer implements WebApplicationInitializer {
@Override
public void onStartup(ServletContext servletContext) throws ServletException {
//初始化spring 容器
AnnotationConfigWebApplicationContext ac = new AnnotationConfigWebApplicationContext();
ac.register( AppConfig.class);
// ac.setServletContext(servletCxt);
// ac.refresh();
DispatcherServlet servlet = new DispatcherServlet(ac);
ServletRegistration.Dynamic registration = servletContext.addServlet("app", servlet);
registration.setLoadOnStartup(1);
// registration.setInitParameter("contextConfigLocation","spring mvc.xml 的地址");
registration.addMapping("*.do");
}
}
package com.mvc;
@Controller
public class TestController {
@RequestMapping("/test.do")
@ResponseBody //user 对象 Map
public Object test(String name, HttpServletRequest request, HttpServletResponse response ){
request.getParameter("name");
Map hashMap = new HashMap();
hashMap.put("key","value");
return hashMap;
}
@RequestMapping("/model.do")
// @ResponseBody
//@ResponseBody 加注释访问静态资源 /app/index.html
public String model(HttpServletRequest request,HttpServletResponse response){
// /app/index.html
return "index";
}
}
package com;
@Configuration
@ComponentScan("com.mvc")
@EnableWebMvc
public class AppConfig implements WebMvcConfigurer{
@Override
public void configurePathMatch(PathMatchConfigurer configurer) {
}
@Override
public void configureContentNegotiation(ContentNegotiationConfigurer configurer) {
}
@Override
public void configureAsyncSupport(AsyncSupportConfigurer configurer) {
}
@Override
public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {
}
@Override
public void addFormatters(FormatterRegistry registry) {
}
@Override
public void addInterceptors(InterceptorRegistry registry) {
}
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
}
@Override
public void addCorsMappings(CorsRegistry registry) {
}
@Override
public void addViewControllers(ViewControllerRegistry registry) {
}
//视图解析
@Override
public void configureViewResolvers(ViewResolverRegistry registry) {
registry.jsp( "/app/",".html" );
}
@Override
public void addArgumentResolvers(List<HandlerMethodArgumentResolver> resolvers) {
}
@Override
public void addReturnValueHandlers(List<HandlerMethodReturnValueHandler> handlers) {
}
//消息转换
@Override
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
converters.add( new FastJsonHttpMessageConverter() );
}
@Override
public void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
}
@Override
public void configureHandlerExceptionResolvers(List<HandlerExceptionResolver> resolvers) {
}
@Override
public void extendHandlerExceptionResolvers(List<HandlerExceptionResolver> resolvers) {
}
@Override
public Validator getValidator() {
return null;
}
@Override
public MessageCodesResolver getMessageCodesResolver() {
return null;
}
}
webapp/app/index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<h1>index1</h1>
</body>
</html>
与XML比较
注解方式与web.xml配置同样的效果
listener作用,在web应用启动的时候能够加载spring环境
contextConfigLocation表示资源位置
servlet 初始化DispatcherServlet类环境
前缀后缀及消息转换器
实现类
只需要在AppConfig类中,以实现WebMvcConfigurer接口。具体使用查看示例代码AppConfig类的configureViewResolvers、configureMessageConverters方法
//视图解析
@Override
public void configureViewResolvers(ViewResolverRegistry registry) {
registry.jsp( "/app/",".html" );
}
//消息转换
@Override
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
converters.add( new FastJsonHttpMessageConverter() );
}
@Bean方式
只需要在AppConfig配置类中
@Bean
public InternalResourceViewResolver internalResourceViewResolver(){
InternalResourceViewResolver internalResourceViewResolver = new InternalResourceViewResolver();
internalResourceViewResolver.setPrefix("/");
internalResourceViewResolver.setSuffix(".jsp");
return internalResourceViewResolver;
}
SPI技术
为什么web容器启动时,会调用实现类MyWebApplicationInitializer的onStartup方法
在WebApplicationInitializer接口类的依赖包
META-INF/services/javax.servlet.ServletContainerInitializer文件夹有SpringServletContainerInitializer(springweb容器初始化类)
spring-web.png
SpringServletContainerInitializer实现了jdk的ServletContainerInitializer接口
ServletContainerInitializer是servlet规范,例如tomcat也实现了这个规范
@HandlesTypes(WebApplicationInitializer.class)
public class SpringServletContainerInitializer implements ServletContainerInitializer {
/**
* Delegate the {@code ServletContext} to any {@link WebApplicationInitializer}
* implementations present on the application classpath.
* <p>Because this class declares @{@code HandlesTypes(WebApplicationInitializer.class)},
* Servlet 3.0+ containers will automatically scan the classpath for implementations
* of Spring's {@code WebApplicationInitializer} interface and provide the set of all
* such types to the {@code webAppInitializerClasses} parameter of this method.
* <p>If no {@code WebApplicationInitializer} implementations are found on the classpath,
* this method is effectively a no-op. An INFO-level log message will be issued notifying
* the user that the {@code ServletContainerInitializer} has indeed been invoked but that
* no {@code WebApplicationInitializer} implementations were found.
* <p>Assuming that one or more {@code WebApplicationInitializer} types are detected,
* they will be instantiated (and <em>sorted</em> if the @{@link
* org.springframework.core.annotation.Order @Order} annotation is present or
* the {@link org.springframework.core.Ordered Ordered} interface has been
* implemented). Then the {@link WebApplicationInitializer#onStartup(ServletContext)}
* method will be invoked on each instance, delegating the {@code ServletContext} such
* that each instance may register and configure servlets such as Spring's
* {@code DispatcherServlet}, listeners such as Spring's {@code ContextLoaderListener},
* or any other Servlet API componentry such as filters.
* @param webAppInitializerClasses all implementations of
* {@link WebApplicationInitializer} found on the application classpath
* @param servletContext the servlet context to be initialized
* @see WebApplicationInitializer#onStartup(ServletContext)
* @see AnnotationAwareOrderComparator
*/
@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);
}
}
所以tomcat启动时,将会调用到web应用的实现类SpringServletContainerInitializer的onStartup方法
SpringServletContainerInitializer.png
在onStartup方法内:对传过来的WebApplicationInitializer(web应用初始化)集合遍历及其执行实现类onStartup方法,最终会调用到自定义实现的MyWebApplicationInitializer方法
所以,SpringServletContainerInitializer便是使用SPI技术,通过配置文件加进去的
自定义SPI实现
说明:使用内嵌tomcat,运行使用maven(tomcat7:run)
package com.service;
import javax.servlet.ServletContext;
public interface MyWebInit {
void onStartup(ServletContext context);
}
package com.service;
@HandlesTypes(MyWebInit.class)
public class MyServletContainerInitializer implements ServletContainerInitializer {
@Override
public void onStartup(Set<Class<?>> set, ServletContext servletContext) throws ServletException {
List<MyWebInit> list = new ArrayList<>();
for (Class<?> aClass : set) {
try {
list.add((MyWebInit) aClass.newInstance());
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
for (MyWebInit webInit : list) {
webInit.onStartup( servletContext);
}
}
}
package com.mvc;
public class MyWebApplicationInitializer implements MyWebInit {
public void onStartup(ServletContext servletContext) {
//初始化spring 容器 以注解的方式
AnnotationConfigWebApplicationContext ac = new AnnotationConfigWebApplicationContext();
ac.register( AppConfig.class);
// ac.setServletContext(servletCxt);
// ac.refresh();
DispatcherServlet servlet = new DispatcherServlet(ac);
ServletRegistration.Dynamic registration = servletContext.addServlet("app", servlet);
registration.setLoadOnStartup(1);
// registration.setInitParameter("contextConfigLocation","spring mvc.xml 的地址");
registration.addMapping("*.do");
}
}
webapp/META-INF/services/javax.servlet.ServletContainerInitializer
值是com.service.MyServletContainerInitializer
![servletContainerInitializer.png](https://img.haomeiwen.com/i7310356/64941ed7f317d662.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
MyServletContainerInitializer实现ServletContainerInitializer。META-INF目录的javax.servlet.ServletContainerInitializer文件包含MyServletContainerInitializer类。HandlesTypes中自定义一个接口,MyWebApplicationInitializer实现这个接口。就可以实现相同的执行效果。
springboot实现
不使用内嵌的tomcat
pom.xml中tomcat-embed-core注释去掉
package com;
import org.apache.catalina.Context;
import org.apache.catalina.LifecycleListener;
import org.apache.catalina.startup.Tomcat;
public class AppMVC {
public static void main(String[] args) throws Exception {
Tomcat tomcat = new Tomcat();
tomcat.setPort(80);
//
Context context = tomcat.addContext("/", System.getProperty("java.io.tmpdir"));
//只会去初始化一个 context的资源目录 并不会加载 web的生命周期
// webapps
// .war 文件夹
// tomcat.addWebapp("/","C:\\Program Files\\pro\\public-luban-project\\spring-mvc\\src\\main\\webapp");
context.addLifecycleListener((LifecycleListener) Class.forName(tomcat.getHost().getConfigClass()).newInstance());
tomcat.start();
tomcat.getServer().await();
}
}
springboot通过main方法启动,设置tomcat,初始化Context,添加声明周期
tomcat启动