大家好,我是路人,这是SpringMVC系列第15篇。
前面写的14篇springmvc文章中都用到了配置文件,比如web.xml,springmvc的配置文件等等,使用起来比较繁琐,本文将把所有配置文件抛弃掉,采用全注解的方式使用springmvc,且会带大家了解其原理。
1、本文内容
- 全注解方式使用springmvc
- 全注解方式原理解析
2、全注解方式使用springmvc
2.1、新建maven web项目
项目中不需要web.xml配置文件,maven配置如下
<?xml version="1.0" encoding="UTF-8"?><project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"><modelVersion>4.0.0</modelVersion><groupId>com.javacode2018</groupId><artifactId>chat12-annotation</artifactId><version>1.0-SNAPSHOT</version><packaging>war</packaging><name>chat12-annotation Maven Webapp</name><description>springmvc全注解方式</description><url>http://www.itsoku.com</url><properties><maven.compiler.source>8</maven.compiler.source><maven.compiler.target>8</maven.compiler.target><project.build.sourceEncoding>UTF-8</project.build.sourceEncoding></properties><dependencies><!-- 添加springmvc依赖 --><dependency><groupId>org.springframework</groupId><artifactId>spring-webmvc</artifactId><version>5.3.6</version></dependency><!-- 添加jackson配置 --><dependency><groupId>com.fasterxml.jackson.core</groupId><artifactId>jackson-core</artifactId><version>2.11.4</version></dependency><dependency><groupId>com.fasterxml.jackson.core</groupId><artifactId>jackson-databind</artifactId><version>2.11.4</version></dependency><!-- 添加servlet 依赖 --><dependency><groupId>javax.servlet</groupId><artifactId>javax.servlet-api</artifactId><version>4.0.1</version><scope>provided</scope></dependency><!-- 日志 --><dependency><groupId>ch.qos.logback</groupId><artifactId>logback-classic</artifactId><version>1.2.3</version></dependency><!--文件上传的jar包--><dependency><groupId>commons-fileupload</groupId><artifactId>commons-fileupload</artifactId><version>1.4</version></dependency></dependencies><build><finalName>chat12-annotation</finalName><resources><resource><directory>src/main/resources</directory><filtering>false</filtering><includes><include>**/*.*</include></includes></resource></resources><plugins><plugin><groupId>org.apache.maven.plugins</groupId><artifactId>maven-war-plugin</artifactId><version>2.2</version><configuration><failOnMissingWebXml>false</failOnMissingWebXml></configuration></plugin></plugins></build></project>
注意:上面配置中多了一个插件的配置,由于maven在web项目打包的时候,发现项目中没有web.xml,会报错,所以需要加入下面配置,让插件忽略这个问题

2.2、创建初始化类,代替web.xml
在Servlet3.0环境中,容器会在类路径中查找实现javax.servlet.ServletContainerInitializer接口的类,如果找到的话就用它来配置Servlet容器。 Spring提供了这个接口的实现,名为SpringServletContainerInitializer,这个类反过来又会查找实现WebApplicationInitializer的类并将配置的任务交给它们来完成。Spring3.2引入了一个便利的WebApplicationInitializer基础实现,名为
AbstractAnnotationConfigDispatcherServletInitializer,当我们的类扩展了AbstractAnnotationConfigDispatcherServletInitializer并将其部署到Servlet3.0容器的时候,容器会自动发现它,并用它来配置Servlet上下文。
我们来创建的 MvcInit类,需继承AbstractAnnotationConfigDispatcherServletInitializer ,项目启动的时候,servlet容器会自动加载这个类,这个类相当于 web.xml 的功能。
package com.javacode2018.springmvc.chat12.config;import org.springframework.web.filter.CharacterEncodingFilter;import org.springframework.web.servlet.support.AbstractAnnotationConfigDispatcherServletInitializer;import javax.servlet.Filter;/*** ①:1、创建Mvc初始化类,需要继承AbstractAnnotationConfigDispatcherServletInitializer类*/public class MvcInit extends AbstractAnnotationConfigDispatcherServletInitializer {/*** springmvc容器的父容器spring配置类* 实际工作中我们的项目比较复杂,可以将controller层放在springmvc容器中* 其他层,如service层、dao层放在父容器了,bean管理起来更清晰一些* 也可以没有父容器,将所有bean都放在springmvc容器中** @return*/@Overrideprotected Class<?>[] getRootConfigClasses() {return new Class[0];}/*** ②:2、设置springmvc容器的spring配置类** @return*/@Overrideprotected Class<?>[] getServletConfigClasses() {return new Class[]{MvcConfig.class};}/*** ③:3、配置DispatcherServlet的url-pattern** @return*/@Overrideprotected String[] getServletMappings() {return new String[]{"/"};}/*** ④:4、注册拦截器** @return*/@Overrideprotected Filter[] getServletFilters() {//添加拦截器,解决乱码问题CharacterEncodingFilter characterEncodingFilter = new CharacterEncodingFilter();characterEncodingFilter.setEncoding("UTF-8");characterEncodingFilter.setForceRequestEncoding(true);characterEncodingFilter.setForceResponseEncoding(true);return new Filter[]{characterEncodingFilter};}}
2.3、创建配置springmvc配置类,代替springmvc配置文件
下面这个类相当于springmvc配置文件的功能,springmvc需要的各种组件可以在这个里面配置,大家注意啦,这个类的特点
- 需要继承
WebMvcConfigurer接口,这个接口中提供了很多方法,预留给开发者用来配置springmvc中的各种组件,springmvc容器启动的过程中,会自动调用这些方法 - 标注有@Configuration注解,表示这是一个配置类
- 标注有@ComponentScan注解,用来扫描组件,将bean注册到springmvc容器
- 需要标注@EnableWebMvc注解,用来起来springmvc注解配置功能,有了这个注解,springmvc容器才会自动调用WebMvcConfigurer接口中的方法
- WebMvcConfigurer接口中提供了一系列方法,用来配置视图解析器、静态资源处理器、拦截器
- 在这个类中我们配置了(②视图解析器、③拦截器、④静态资源处理器、⑤文件上传解析器)
package com.javacode2018.springmvc.chat12.config;import com.javacode2018.springmvc.chat12.interceptor.MyInterceptor;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.ComponentScan;import org.springframework.context.annotation.Configuration;import org.springframework.core.Ordered;import org.springframework.web.multipart.commons.CommonsMultipartResolver;import org.springframework.web.servlet.config.annotation.*;import org.springframework.web.servlet.view.InternalResourceViewResolver;/*** 1.开启springmvc注解配置* 2、配置视图解析器* 3、配置截器* 4、配置静态资源访问* 5、配置文件上传解析器* 6、配置全局异常处理器*/@Configuration@ComponentScan("com.javacode2018.springmvc.chat12")@EnableWebMvc //1:使用EnableWebMvc开启springmvc注解方式配置public class MvcConfig implements WebMvcConfigurer {/*** ②:2、添加视图解析器(可以添加多个)** @param registry*/@Overridepublic void configureViewResolvers(ViewResolverRegistry registry) {InternalResourceViewResolver resolver = new InternalResourceViewResolver();resolver.setPrefix("/WEB-INF/view/");resolver.setSuffix(".jsp");resolver.setOrder(Ordered.LOWEST_PRECEDENCE);registry.viewResolver(resolver);}@Autowiredprivate MyInterceptor myInterceptor;/*** ③:3、添加拦截器(可以添加多个)** @param registry*/@Overridepublic void addInterceptors(InterceptorRegistry registry) {registry.addInterceptor(this.myInterceptor).addPathPatterns("/**");}/*** ④:4、配置静态资源访问处理器** @param registry*/@Overridepublic void addResourceHandlers(ResourceHandlerRegistry registry) {registry.addResourceHandler("/static/**").addResourceLocations("/static/");}/*** ⑤:5、配置文件上传解析器** @return*/@Beanpublic CommonsMultipartResolver multipartResolver() {CommonsMultipartResolver commonsMultipartResolver = new CommonsMultipartResolver();//maxUploadSizePerFile:单个文件大小限制(byte)//maxUploadSize:整个请求大小限制(byte)commonsMultipartResolver.setMaxUploadSizePerFile(10 * 1024 * 1024);commonsMultipartResolver.setMaxUploadSize(100 * 1024 * 1024);return commonsMultipartResolver;}}
2.4、创建自定义拦截器
上面的MvcConfig配置类中,我们定义了一个拦截器MyInterceptor myInterceptor;,这个类的代码如下
package com.javacode2018.springmvc.chat12.interceptor;import org.springframework.stereotype.Component;import org.springframework.web.servlet.HandlerInterceptor;import javax.servlet.http.HttpServletRequest;import javax.servlet.http.HttpServletResponse;@Componentpublic class MyInterceptor implements HandlerInterceptor {@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {System.out.println("这是MyInterceptor拦截器");return true;}}
2.5、创建全局异常处理类
异常处理,我们也给整上,添加一个类,当出错的时候,跳转到错误页面。
package com.javacode2018.springmvc.chat12.config;import org.springframework.web.bind.annotation.ControllerAdvice;import org.springframework.web.bind.annotation.ExceptionHandler;import org.springframework.web.servlet.ModelAndView;/*** 异常处理*/@ControllerAdvicepublic class GlobalExceptionHandler {@ExceptionHandlerpublic ModelAndView doException(Exception e) {ModelAndView modelAndView = new ModelAndView();modelAndView.setViewName("error");modelAndView.addObject("ex", e);return modelAndView;}}
2.6、测试功能
添加一个controller及几个jsp页面,测效果
@Controllerpublic class IndexController {/*** 首页** @return*/@RequestMapping("/")public String index() {return "index";}/*** 测试异常情况** @return*/@RequestMapping("/testError")public String testError() {System.out.println(10 / 0);return "success";}}
webapp/WEB-INF/view中创建3个页面
index.jsp:
<%@ page contentType="text/html;charset=UTF-8" language="java" %><html><head><title>Title</title></head><body><h2>全注解的方式配置springmvc</h2><br/><a target="_blank" href="${pageContext.request.contextPath}/static/imgs/1.jpg">测试访问静态资源</a><br/><a target="_blank" href="${pageContext.request.contextPath}/testError">测试触发全局异常处理</a></body></html>
error.jsp,错误跳转的页面,会显示异常信息
<%@ page contentType="text/html;charset=UTF-8" language="java" %><html><head><title>Title</title></head><body><h2>出错啦,错误信息如下:</h2><h3>${ex}</h3></body></html>
success.jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %><html><head><title>Title</title></head><body><h2>success</h2></body></html>
在搞一个图片放在webapp/static/imgs中,稍后测试静态资源访问的效果。
2.7、项目整体结构

2.8、测试效果
项目发布到tomcat,访问首页,首页上有2个连接,可以点击一下,分别用来测试静态资源是否可以访问,另外一个测试全局异常处理的效果。

连接1效果:

连接2效果:

3、原理:ServletContainerInitializer接口
刚才上面2.2章节中有提到过,重点在于Servlet3.0环境中,容器会在类路径中查找实现javax.servlet.ServletContainerInitializer接口的类,如果找到的话就用它来配置Servlet容器,servlet3.0赋予了web项目免去所有配置文件(web.xml)的能力。
所以重点就在于ServletContainerInitializer这个接口上,springmvc全注解方式就是依靠这个接口来实现的,掌握了这个接口的用法,springmvc全注解的原理大家基本上就搞懂了,对阅读springmvc源码也是非常有利的。
下面看来这个接口的用法。
3.1、ServletContainerInitializer源码
这个接口比较简单,只有一个onStartup方法,web容器启动的时候会自动调用这个方法,有2个参数,第1个参数稍后介绍,第2个参数ctx是servlet上下文,通过servlet上下文对象,我们可以在这个方法中实现web.xml的所有操作。
public interface ServletContainerInitializer {public void onStartup(Set<Class<?>> c, ServletContext ctx)throws ServletException;}
3.2、ServletContainerInitializer使用
1、可以自定义一个实现ServletContainerInitializer接口,这个类必须在jar包的META-INF/services/javax.servlet.ServletContainerInitializer文件里面进行声明,这个文件的内容就是自定义类的全类名
2、Servlet容器启动会在所有jar和classes目录中扫描META-INF/services/javax.servlet.ServletContainerInitializer文件,然后找到这个文件中的具体的类,然后会自动实例化这个类,调用这个类的onStartup方法
3.2、onStartup的第1个参数,@HandlesTypes注解
提到onStartup方法的第一个参数,这里就需要介绍一下@HandlesTypes这个注解,先来看一下其源码,比较简单,就只有一个Calss数组类型的value属性。
Target({ElementType.TYPE})@Retention(RetentionPolicy.RUNTIME)public @interface HandlesTypes {Class<?>[] value();}
1、@HandlesTypes标签用在实现ServletContainerInitializer接口的类上面,比如:
@HandlesTypes(WebApplicationInitializer.class)public class SpringServletContainerInitializer implements ServletContainerInitializer
2、servlet容器会扫描项目中的所有类(jar包和classes路径中),如果符合@HandlesTypes注解value值指定的类型,就会放在一个数组中,最终会传递给onStartup方法的第一个参数
3、当容器启动的时候,我们就可以通过拿到Set
3、springmvc全注解的原理
了解了ServletContainerInitializer接口的原理,咱们来看springmvc,spring-web.jar中包含了META-INF/services/javax.servlet.ServletContainerInitializer文件

这个文件中指定的是org.springframework.web.SpringServletContainerInitializer这个类,重点来了,springmvc就是依靠这个类来实现注解功能的,大家可以去看看这个类的源码,在其onStartup方法中添加断点,可以看到完整清晰的启动过程。

后面会专门有一篇文章带大家阅读源码,一步步带大家了解springmvc容器的整个启动过程。
4、总结
建议大家自己去实战一下,光看是不行的,看可能觉得什么都会了,但是抛开文章自己去试试,又是一番景象,学技术一定要多动手。
有问题欢迎留言。
5、案例代码
git地址:https://gitee.com/javacode2018/springmvc-series
最新资料
注意:本文归作者所有,未经作者允许,不得转载