文章目录
- 二、SpringMVC注解支持
- 2.1 回顾Servlet容器启动源码流程
- 2.2 分析SpringMVC启动源码分析
- 2.2.1 SpringServletContainerInitializer源码分析
- 2.2.2 WebApplicationInitializer源码分析
- 1)AbstractContextLoaderInitializer
- 2)AbstractDispatcherServletInitializer
- 3)AbstractAnnotationConfigDispatcherServletInitializer
- 2.2.3 纯注解方式定义SpringMVC
- 2.2.4 定制化SpringMVC
- WebMvcConfigurer源码如下:
- 1)配置资源放行
- 2)配置拦截器
- 3)配置资源映射
- 4)配置视图解析器
- 5)配置视图控制
- 6)配置转换器Converter
- 7)配置格式化器Formatter
二、SpringMVC注解支持
2.1 回顾Servlet容器启动源码流程
在Servlet3.0规范中规定:为了提供给第三方框架做初始化工作,WEB容器启动时,会去扫描每个jar包下的META-INF/services
目录下的一个名为javax.servlet.ServletContainerInitializer
的文件,文件中指定ServletContainerInitializer
的实现类的全类名;
- 引入依赖:
<dependency>
<groupId>org.apache.tomcat</groupId>
<artifactId>tomcat-api</artifactId>
<version>8.5.71</version>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>servlet-api</artifactId>
<version>2.5</version>
</dependency>
搭建工程:
- 准备HelloService:
package com.dfbz.service;
/**
* @author lscl
* @version 1.0
* @intro:
*/
public interface HelloService {
}
- AbstractHelloService:
package com.dfbz.service.impl;
import com.dfbz.service.HelloService;
/**
* @author lscl
* @version 1.0
* @intro:
*/
public abstract class AbstractHelloService implements HelloService {
}
- HelloServiceImpl:
package com.dfbz.service.impl;
import com.dfbz.service.HelloService;
/**
* @author lscl
* @version 1.0
* @intro:
*/
public class HelloServiceImpl implements HelloService {
}
- HelloServlet:
package com.dfbz.servlet;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
/**
* @author lscl
* @version 1.0
* @intro:
*/
public class HelloServlet extends HttpServlet {
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
System.out.println("hello servlet...");
resp.getWriter().println("hello servlet...");
}
}
- HelloFilter:
package com.dfbz.filter;
import javax.servlet.*;
import java.io.IOException;
/**
* @author lscl
* @version 1.0
* @intro:
*/
public class HelloFilter implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
}
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
System.out.println("hello filter...");
chain.doFilter(request,response);
}
@Override
public void destroy() {
}
}
- HelloListener:
package com.dfbz.listener;
import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
/**
* @author lscl
* @version 1.0
* @intro:
*/
public class HelloListener implements ServletContextListener {
@Override
public void contextInitialized(ServletContextEvent sce) {
System.out.println("context init...");
}
@Override
public void contextDestroyed(ServletContextEvent sce) {
System.out.println("context destroyed...");
}
}
- MyServletContainerInitializer:
package com.dfbz;
import com.dfbz.filter.HelloFilter;
import com.dfbz.listener.HelloListener;
import com.dfbz.service.HelloService;
import com.dfbz.servlet.HelloServlet;
import javax.servlet.*;
import javax.servlet.annotation.HandlesTypes;
import java.util.EnumSet;
import java.util.Set;
/**
* @author lscl
* @version 1.0
* @intro
*/
@HandlesTypes(HelloService.class) // 标注需要把什么类型的子类(后代类,包括子类/子接口/抽象子类)传递到onStartup方法中的Set中
public class MyServletContainerInitializer implements ServletContainerInitializer {
/**
* WEB容器在启动时,自动调用ServletContainerInitializer的onStartUp来初始化
*
* @param set : @HandlesTypes注解中指定类型的子类(后代类)
* @param servletContext : servlet上下文对象
* @throws ServletException
*/
@Override
public void onStartup(Set<Class<?>> set, ServletContext servletContext) throws ServletException {
for (Class<?> clazz : set) {
System.out.println(clazz);
}
// 注册servlet
ServletRegistration.Dynamic servletDynamic = servletContext.addServlet("helloServlet", new HelloServlet());
servletDynamic.addMapping("/hello");
// 注册filter
FilterRegistration.Dynamic filterDynamic = servletContext.addFilter("helloFilter", new HelloFilter());
filterDynamic.addMappingForUrlPatterns(EnumSet.of(DispatcherType.REQUEST), true, "/*");
// 注册listener
servletContext.addListener(new HelloListener());
}
}
在resources目录下创建/META-INF/services
目录,然后再创建javax.servlet.ServletContainerInitializer
文件,文件中填写引导类的全路径:
com.dfbz.MyServletContainerInitializer
工程搭建如下:
启动服务器,访问http://localhost:8080/hello
2.2 分析SpringMVC启动源码分析
2.2.1 SpringServletContainerInitializer源码分析
我们翻开spring-web-5.2.12.RELEASE.jar
包,发现在这个jar包下存在一个引导类SpringServletContainerInitializer
:
- 查看
SpringServletContainerInitializer
源码:
package org.springframework.web;
import java.lang.reflect.Modifier;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
import javax.servlet.ServletContainerInitializer;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.annotation.HandlesTypes;
import org.springframework.core.annotation.AnnotationAwareOrderComparator;
import org.springframework.lang.Nullable;
import org.springframework.util.ReflectionUtils;
// 在web容器启动时,会加载WebApplicationInitializer的后代类传递给onStartup方法
@HandlesTypes({WebApplicationInitializer.class})
public class SpringServletContainerInitializer implements ServletContainerInitializer {
public SpringServletContainerInitializer() {
}
public void onStartup(@Nullable Set<Class<?>> webAppInitializerClasses, ServletContext servletContext) throws ServletException {
List<WebApplicationInitializer> initializers = new LinkedList();
Iterator var4;
if (webAppInitializerClasses != null) {
var4 = webAppInitializerClasses.iterator();
// 遍历这个set集合(所有的WebApplicationInitializer子类)
while(var4.hasNext()) {
Class<?> waiClass = (Class)var4.next();
if (!waiClass.isInterface() && !Modifier.isAbstract(waiClass.getModifiers()) && WebApplicationInitializer.class.isAssignableFrom(waiClass)) {
try {
// // 不是接口也不是抽象类就添加到initializers集合中
initializers.add(
(
WebApplicationInitializer)ReflectionUtils.accessibleConstructor(
waiClass, new Class[0]).newInstance()
);
} catch (Throwable var7) {
throw new ServletException("Failed to instantiate WebApplicationInitializer class", var7);
}
}
}
}
if (initializers.isEmpty()) {
servletContext.log("No Spring WebApplicationInitializer types detected on classpath");
} else {
servletContext.log(initializers.size() + " Spring WebApplicationInitializers detected on classpath");
AnnotationAwareOrderComparator.sort(initializers);
var4 = initializers.iterator();
// 然后遍历initializers(筛选之后的WebApplicationInitializer后代对象)
while(var4.hasNext()) {
WebApplicationInitializer initializer = (WebApplicationInitializer)var4.next();
// 调用各自引导器的onStartup方法
initializer.onStartup(servletContext);
}
}
}
}
上述源码很简单,就是先获取到WebApplicationInitializer
类的所有后代类,然后进过一层筛选,去除抽象类和接口相关的后代类(只保存普通类),然后执行各自的onStartup方法进行初始化;那么WebApplicationInitializer有哪些后代呢?
查看WebApplicationInitializer
类的继承体系:
WebApplicationInitializer
有4个子类,但都是抽象类,并不会直接执行他们的onStartup方法;换句话来说,这几个抽象类都是提供给我们自己来继承的,以便获取他们的功能;那他们都提供有哪些功能呢?
2.2.2 WebApplicationInitializer源码分析
1)AbstractContextLoaderInitializer
AbstractContextLoaderInitializer
源码如下:
Tips:是一个抽象类
package org.springframework.web.context;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.context.ApplicationContextInitializer;
import org.springframework.lang.Nullable;
import org.springframework.web.WebApplicationInitializer;
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方法
registerContextLoaderListener(servletContext);
}
protected void registerContextLoaderListener(ServletContext servletContext) {
// 通过createRootApplicationContext方法获取了一个IOC容器
WebApplicationContext rootAppContext = createRootApplicationContext();
if (rootAppContext != null) {
listener = new ContextLoaderListener(rootAppContext);
listener.setContextInitializers(getRootApplicationContextInitializers());
// 注册了一个ContextLoaderListener监听器
servletContext.addListener(listener);
}
else {
logger.debug("No ContextLoaderListener registered, as " +
"createRootApplicationContext() did not return an application context");
}
}
// 创建IOC容器的方法,这个方法是抽象的,到时候要留给子类重写
@Nullable
protected abstract WebApplicationContext createRootApplicationContext();
@Nullable
protected ApplicationContextInitializer<?>[] getRootApplicationContextInitializers() {
return null;
}
}
总结作用:通过createRootApplicationContext方法(留给子类写的)创建了一个根容器(WebApplicationContext),并往上下文对象里面注册了一个ContextLoaderListener
监听器(帮我们创建Spring容器的监听器)
2)AbstractDispatcherServletInitializer
AbstractDispatcherServletInitializer
源码:
Tips:是AbstractContextLoaderInitializer的子类;并且也是一个抽象类;
package org.springframework.web.servlet.support;
import java.util.EnumSet;
import javax.servlet.DispatcherType;
import javax.servlet.Filter;
import javax.servlet.FilterRegistration;
import javax.servlet.FilterRegistration.Dynamic;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.ServletRegistration;
import org.springframework.context.ApplicationContextInitializer;
import org.springframework.core.Conventions;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import org.springframework.util.ObjectUtils;
import org.springframework.web.context.AbstractContextLoaderInitializer;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.servlet.DispatcherServlet;
import org.springframework.web.servlet.FrameworkServlet;
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 {
// 调用父类的onStartup方法(保留父类的功能)
super.onStartup(servletContext);
// 执行自身的逻辑
registerDispatcherServlet(servletContext);
}
protected void registerDispatcherServlet(ServletContext servletContext) {
String servletName = getServletName();
Assert.hasLength(servletName, "getServletName() must not return null or empty");
// 调用createServletApplicationContext方法创建了一个WEB类型的IOC容器
WebApplicationContext servletAppContext = createServletApplicationContext();
Assert.notNull(servletAppContext, "createServletApplicationContext() must not return null");
// 获取一个dispatcherServlet(到这里初始化了SpringMVC)
FrameworkServlet dispatcherServlet = createDispatcherServlet(servletAppContext);
Assert.notNull(dispatcherServlet, "createDispatcherServlet(WebApplicationContext) must not return null");
dispatcherServlet.setContextInitializers(getServletApplicationContextInitializers());
// 注册servlet(注册SpringMVC)
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);
// 该SpringMVC拦截的请求
registration.addMapping(getServletMappings());
// 是否支持异步
registration.setAsyncSupported(isAsyncSupported());
Filter[] filters = getServletFilters();
if (!ObjectUtils.isEmpty(filters)) {
for (Filter filter : filters) {
registerServletFilter(servletContext, filter);
}
}
customizeRegistration(registration);
}
protected String getServletName() {
return DEFAULT_SERVLET_NAME;
}
// 如何创建IOC容器留给子类重写
protected abstract WebApplicationContext createServletApplicationContext();
// 创建的是DispatcherServlet(初始化SpringMVC)
protected FrameworkServlet createDispatcherServlet(WebApplicationContext servletAppContext) {
return new DispatcherServlet(servletAppContext);
}
@Nullable
protected ApplicationContextInitializer<?>[] getServletApplicationContextInitializers() {
return null;
}
// 该SpringMVC拦截什么路径也留给子类重写
protected abstract String[] getServletMappings();
@Nullable
protected Filter[] getServletFilters() {
return null;
}
protected FilterRegistration.Dynamic registerServletFilter(ServletContext servletContext, Filter filter) {
String filterName = Conventions.getVariableName(filter);
Dynamic registration = servletContext.addFilter(filterName, filter);
if (registration == null) {
int counter = 0;
while (registration == null) {
if (counter == 100) {
throw new IllegalStateException("Failed to register filter with name '" + filterName + "'. " +
"Check if there is another filter registered under the same name.");
}
registration = servletContext.addFilter(filterName + "#" + counter, filter);
counter++;
}
}
registration.setAsyncSupported(isAsyncSupported());
registration.addMappingForServletNames(getDispatcherTypes(), false, getServletName());
return registration;
}
private EnumSet<DispatcherType> getDispatcherTypes() {
return (isAsyncSupported() ?
EnumSet.of(DispatcherType.REQUEST, DispatcherType.FORWARD, DispatcherType.INCLUDE, DispatcherType.ASYNC) :
EnumSet.of(DispatcherType.REQUEST, DispatcherType.FORWARD, DispatcherType.INCLUDE));
}
// 默认支持异步
protected boolean isAsyncSupported() {
return true;
}
protected void customizeRegistration(ServletRegistration.Dynamic registration) {
}
}
总结作用:通过createServletApplicationContext方法(抽象,留给子类写的)创建了一个WEB类型(SpringMVC)的IOC容器,还创建了DispatcherServlet(初始化SpringMVC),拦截规则留给子类写了;
留下一个抽象方法:
protected abstract String[] getServletMappings();
:该SpringMVC要接管(拦截)的路径
3)AbstractAnnotationConfigDispatcherServletInitializer
- 源码分析:
Tips:是AbstractDispatcherServletInitializer的子类,也是一个抽象类;
package org.springframework.web.servlet.support;
import org.springframework.lang.Nullable;
import org.springframework.util.ObjectUtils;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.context.support.AnnotationConfigWebApplicationContext;
// 这个类没有重写onStartup方法,保留父类的逻辑
public abstract class AbstractAnnotationConfigDispatcherServletInitializer
extends AbstractDispatcherServletInitializer {
// 实现了创建IOC容器的逻辑
@Override
@Nullable
protected WebApplicationContext createRootApplicationContext() {
// 获取一个Class对象
Class<?>[] configClasses = getRootConfigClasses();
if (!ObjectUtils.isEmpty(configClasses)) {
// 创建一个基于注解的IOC容器
AnnotationConfigWebApplicationContext context = new AnnotationConfigWebApplicationContext();
// 往IOC容器里面注册一个class对象(配置类对象)
context.register(configClasses);
return context;
}
else {
return null;
}
}
// 实现了创建WEB类型的IOC容器的逻辑
@Override
protected WebApplicationContext createServletApplicationContext() {
// 创建一个基于注解的IOC容器
AnnotationConfigWebApplicationContext context = new AnnotationConfigWebApplicationContext();
// 获取一个Class对象
Class<?>[] configClasses = getServletConfigClasses();
if (!ObjectUtils.isEmpty(configClasses)) {
// 往WEB类的IOC容器里面注册这个class
context.register(configClasses);
}
return context;
}
@Nullable
protected abstract Class<?>[] getRootConfigClasses(); // Spring类型的IOC容器所需要的配置类
// 留给子类实现
@Nullable
protected abstract Class<?>[] getServletConfigClasses(); // SpringMVC类型的IOC容器所需要的配置类
// getServletMappings(); // SpringMVC的拦截规则还没有重写
}
总结作用:实现了创建IOC容器和WEB类型的IOC容器的逻辑(根据配置类来创建IOC容器),并把配置类的方法交给子类重写了;
留下两个方法:
protected abstract Class<?>[] getRootConfigClasses();
:用于创建根容器的配置类protected abstract Class<?>[] getServletConfigClasses();
:用于创建WEB类型的IOC容器的配置类;
2.2.3 纯注解方式定义SpringMVC
- HelloController:
package com.dfbz.controller;
import com.dfbz.service.HelloService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;
/**
* @author lscl
* @version 1.0
* @intro:
*/
@Controller
public class HelloController {
@Autowired
private HelloService helloService;
@GetMapping("/hello")
@ResponseBody
public String hello() {
String str = helloService.hello("SpringMVC");
return "controller:" + str;
}
}
- HelloService:
package com.dfbz.service;
import org.springframework.stereotype.Service;
/**
* @author lscl
* @version 1.0
* @intro:
*/
@Service
public class HelloService {
public String hello(String name) {
return "service:" + name;
}
}
- RootConfig(Spring的配置类):
package com.dfbz.config;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.FilterType;
import org.springframework.stereotype.Controller;
/**
* @author lscl
* @version 1.0
* @intro: Spring不扫描Controller相关的Bean
*/
@Configuration
@ComponentScan(
value = "com.dfbz",
excludeFilters = {@ComponentScan.Filter(type = FilterType.ANNOTATION, classes = Controller.class)}
)
public class RootConfig {
}
- WebConfig(SpringMVC的配置类):
package com.dfbz.config;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.FilterType;
import org.springframework.stereotype.Controller;
/**
* @author lscl
* @version 1.0
* @intro: SpringMVC容器只扫描Controller相关的Bean
*
* useDefaultFilters = false: 禁用Spring默认的扫描规则
*/
@Configuration
@ComponentScan(
value = "com.dfbz",
includeFilters = {@ComponentScan.Filter(type = FilterType.ANNOTATION,classes = Controller.class)},
useDefaultFilters = false
)
public class WebConfig {
}
- MyServletContainerInitializer:
package com.dfbz;
import com.dfbz.config.RootConfig;
import com.dfbz.config.WebConfig;
import org.springframework.web.servlet.support.AbstractAnnotationConfigDispatcherServletInitializer;
/**
* @author lscl
* @version 1.0
* @intro:
*/
public class MyServletContainerInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {
/**
* 加载根容器的配置类
*
* @return
*/
@Override
protected Class<?>[] getRootConfigClasses() {
return new Class[]{RootConfig.class};
}
/**
* 加载WEB容器的配置类
*
* @return
*/
@Override
protected Class<?>[] getServletConfigClasses() {
return new Class[]{WebConfig.class};
}
/**
* SpringMVC要接管(拦截)的路径
*
* @return
*/
@Override
protected String[] getServletMappings() {
return new String[]{"/"}; // 除了jsp之外的所有请求都拦截
}
}
访问:http://localhost:8080/hello
2.2.4 定制化SpringMVC
由于我们是采用纯注解的方式配置SpringMVC,意味着我们以前在SpringMVC.xml中的所有配置都要写在配置类里面了,SpringMVC帮助我们提供了一个接口WebMvcConfigurer
,该类里面有关于SpringMVC大量配置;
WebMvcConfigurer源码如下:
WebMvcConfigurer
源码如下:
package org.springframework.web.servlet.config.annotation;
import org.springframework.format.FormatterRegistry;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.lang.Nullable;
import org.springframework.validation.MessageCodesResolver;
import org.springframework.validation.Validator;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.method.HandlerTypePredicate;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.method.support.HandlerMethodReturnValueHandler;
import org.springframework.web.servlet.HandlerExceptionResolver;
import java.util.List;
/**
* Defines callback methods to customize the Java-based configuration for
* Spring MVC enabled via {@code @EnableWebMvc}.
*
* <p>{@code @EnableWebMvc}-annotated configuration classes may implement
* this interface to be called back and given a chance to customize the
* default configuration.
*
* @author Rossen Stoyanchev
* @author Keith Donald
* @author David Syer
* @since 3.1
*/
public interface WebMvcConfigurer {
/**
* 设置路径匹配
* @param configurer
*/
default void configurePathMatch(PathMatchConfigurer configurer) {
}
/**
* 配置内容协商机制
* @param configurer
*/
default void configureContentNegotiation(ContentNegotiationConfigurer configurer) {
}
/**
* 异步支持
* @param configurer
*/
default void configureAsyncSupport(AsyncSupportConfigurer configurer) {
}
/**
* 配置servlet映射(静态资源放行)
* @param configurer
*/
default void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {
}
/**
* 添加格式化组件
* @param registry
*/
default void addFormatters(FormatterRegistry registry) {
}
/**
* 添加拦截器组件
* @param registry
*/
default void addInterceptors(InterceptorRegistry registry) {
}
/**
* 添加资源映射组件
* @param registry
*/
default void addResourceHandlers(ResourceHandlerRegistry registry) {
}
/**
* 跨域配置
* @param registry
*/
default void addCorsMappings(CorsRegistry registry) {
}
/**
* 添加视图映射组件
* @param registry
*/
default void addViewControllers(ViewControllerRegistry registry) {
}
/**
* 配置视图解析组件
* @param registry
*/
default void configureViewResolvers(ViewResolverRegistry registry) {
}
/**
* 配置参数处理器
* @param resolvers
*/
default void addArgumentResolvers(List<HandlerMethodArgumentResolver> resolvers) {
}
/**
* 配置返回值处理器
* @param handlers
*/
default void addReturnValueHandlers(List<HandlerMethodReturnValueHandler> handlers) {
}
/**
* 配置消息格式化组件(不会覆盖默认的转换器)
* @param converters
*/
default void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
}
/**
* 配置消息格式化组件(会覆盖默认的转换器)
* @param converters
*/
default void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
}
/**
* 配置异常解析器(不会覆盖默认的解析器)
* @param resolvers
*/
default void configureHandlerExceptionResolvers(List<HandlerExceptionResolver> resolvers) {
}
/**
* 配置异常解析器(会覆盖默认的解析器)
* @param resolvers
*/
default void extendHandlerExceptionResolvers(List<HandlerExceptionResolver> resolvers) {
}
/**
* 配置校验器
* @return
*/
@Nullable
default Validator getValidator() {
return null;
}
/**
* 配置错误代码解析器
* @return
*/
@Nullable
default MessageCodesResolver getMessageCodesResolver() {
return null;
}
}
1)配置资源放行
/**
* 静态资源放行
*
* @param configurer
*/
public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {
// 配置静态资源映射,相当于: <mvc:default-servlet-handler/>
configurer.enable();
}
2)配置拦截器
- 1)定义拦截器
@Component
public class MyInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
System.out.println("preHandler...");
return true;
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
System.out.println("postHandle...");
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
System.out.println("afterCompletion...");
}
}
- 2)配置拦截器
/**
* 添加拦截器
*
* @param registry
*/
public void addInterceptors(InterceptorRegistry registry) {
/*
相当于:
<mvc:interceptors>
<mvc:interceptor>
<mvc:mapping path="/**"/>
<mvc:exclude-mapping path="/test"/>
<bean class="com.dfbz.interceptor.MyInterceptor"/>
</mvc:interceptor>
</mvc:interceptors>
*/
registry.addInterceptor(new MyInterceptor())
.addPathPatterns("/**") // 拦截所有请求
.excludePathPatterns("/test"); // 放行/test请求
}
- 编写Controller:
package com.dfbz.controller;
import com.dfbz.entity.City;
import com.dfbz.entity.TestEntity;
import com.dfbz.service.HelloService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.ModelMap;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import java.util.Date;
/**
* @author lscl
* @version 1.0
* @intro:
*/
@Controller
public class HelloController {
@Autowired
private HelloService helloService;
@GetMapping("/hello")
@ResponseBody
public String hello() {
String str = helloService.hello("SpringMVC");
return "controller:" + str;
}
@GetMapping("/test")
@ResponseBody
public String test() {
return "test";
}
}
访问:
http://localhost:8080/hello(经过拦截器)
http://localhost:8080/test(不经过拦截器)
3)配置资源映射
在D://myFile
文件夹中准备几张图片:
- 配置资源映射:
/**
* 配置资源映射
*
* @param registry
*/
public void addResourceHandlers(ResourceHandlerRegistry registry) {
// 如果访问/abc下面的所有请求都去磁盘D:/myFile/去寻找
// 相当于 <mvc:resources mapping="/abc/**" location="file:D://myFile/"></mvc:resources>
registry.
addResourceHandler("/abc/**").
addResourceLocations("file:D://myFile/");
}
访问:http://localhost:8080/abc/000.png
4)配置视图解析器
创建webapp指定文件夹下创建jsp文件:
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>hello</title>
</head>
<body>
<h1>hello View Resolve~</h1>
</body>
</html>
- 视图解析器
/**
* 配置视图解析器
*
* @param registry
*/
public void configureViewResolvers(ViewResolverRegistry registry) {
/*
相当于:
<mvc:view-resolvers>
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<!--配置页面统一前缀-->
<property name="prefix" value="/WEB-INF/views/"/>
<!--配置页面统一后缀-->
<property name="suffix" value=".jsp"></property>
</bean>
</mvc:view-resolvers>
*/
registry.viewResolver(new InternalResourceViewResolver("/WEB-INF/views/", ".jsp"));
}
- 编写Controller:
@GetMapping("/test2")
public String test2() {
return "hello"; // 跳转到hello视图--->/WEB-INF/views/hello.jsp
}
访问:http://localhost:8080/test2
5)配置视图控制
/**
* 配置视图控制
*
* @param registry
*/
public void addViewControllers(ViewControllerRegistry registry) {
/*
相当于: <mvc:view-controller path="/test3" view-name="hello" />
*/
// 如果访问/test3,则返回hello视图(该视图会经过视图解析器,最终找到:/WEB-INF/views/hello.jsp)
registry.addViewController("/test3").setViewName("hello");
}
访问:http://localhost:8080/test3
6)配置转换器Converter
- City实体类:
package com.dfbz.entity;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* @author lscl
* @version 1.0
* @intro:
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
public class City {
private Integer id;
private String name;
}
- 定义一个测试实体类:
package com.dfbz.entity;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.Date;
/**
* @author lscl
* @version 1.0
* @intro:
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
public class TestEntity {
private City city;
private Date date;
}
- 定义String转Date转换器:
package com.dfbz.converter;
import org.springframework.core.convert.converter.Converter;
import org.springframework.stereotype.Component;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
/**
* @author lscl
* @version 1.0
* @intro: 自定义Converter, 实现String类型转换为Date类型
*/
@Component
public class StringToDateConverter implements Converter<String, Date> {
private SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
@Override
public Date convert(String str) {
try {
return sdf.parse(str);
} catch (ParseException e) {
e.printStackTrace();
}
return null;
}
}
- 定义String转City转换器:
package com.dfbz.converter;
import com.dfbz.entity.City;
import org.springframework.core.convert.converter.Converter;
import org.springframework.stereotype.Component;
/**
* @author lscl
* @version 1.0
* @intro: 自定义Converter,实现String类型转换为User类型
*/
@Component
public class StringToCityConverter implements Converter<String, City> {
@Override
public City convert(String str) {
// 格式为: id=10,name=吉安
String[] strArr = str.split(",");
return new City(Integer.parseInt(strArr[0]), strArr[1]);
}
}
- 编写Controller:
@PostMapping("/test4")
@ResponseBody
public TestEntity test4(TestEntity testEntity) {
// 前端提交name为city的参数会经过StringToCityConverter(符合String转City)
// 前端提交name为date的参数会经过StringToDateConverter(符合String转Date)
return testEntity;
}
- 注册Converter:
/**
* 配置注册转换器
*
* @param registry
*/
public void addFormatters(FormatterRegistry registry) {
/*
配置文件配置格式化器:
<bean id="conversionService" class="org.springframework.format.support.FormattingConversionServiceFactoryBean">
<property name="formatters">
<set>
<bean class="com.dfbz.formatter.MyDateFormatter"/>
</set>
</property>
</bean>
<mvc:annotation-driven conversion-service="conversionService"/>
*/
// String 转 Date 转换器
StringToDateConverter stringToDateConverter = new StringToDateConverter();
// String 转 City 转换器
StringToCityConverter stringToCityConverter = new StringToCityConverter();
registry.addConverter(stringToDateConverter);
registry.addConverter(stringToCityConverter); // 添加转换器
}
- 定义表单:
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<form action="/test4" method="post">
city: <input type="text" name="city">
<hr>
date: <input type="text" name="date">
<hr>
<input type="submit">
</form>
</body>
</html>
7)配置格式化器Formatter
Formatter 与 Converter<S,T> 一样,也是一个可以将一种数据类型转换成另一种数据类型的接口。不同的是,Formatter 的源数据类型必须是 String 类型,而 Converter<S,T> 的源数据类型是任意数据类型。
- 定义一个Formatter格式化器:
package com.dfbz.formatter;
import org.springframework.format.Formatter;
import org.springframework.stereotype.Component;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Locale;
/**
* @author lscl
* @version 1.0
* @intro: Formatter, 实现String类型转换为Date类型(Formatter格式化器只能将String类型转换为任意类型)
*/
@Component
public class StringToDateFormatter implements Formatter<Date> {
private SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
@Override
public Date parse(String text, Locale locale) throws ParseException {
return sdf.parse(text);
}
@Override
public String print(Date date, Locale locale) {
return sdf.format(date);
}
}
- 注册格式化器:
/**
* 配置注册转换器
*
* @param registry
*/
public void addFormatters(FormatterRegistry registry) {
// String 转 Date 转换器
StringToDateConverter stringToDateConverter = new StringToDateConverter();
// String 转 City 转换器
StringToCityConverter stringToCityConverter = new StringToCityConverter();
// String 转 Date 格式化器
StringToDateFormatter stringToDateFormatter = new StringToDateFormatter();
registry.addConverter(stringToDateConverter);
// registry.addFormatter(stringToDateFormatter); // 添加格式化器
registry.addConverter(stringToCityConverter); // 添加转换器
}
Tips:当同时配置了Formatter和Converter时,Formatter将会替代Converter