15【SpringMVC的注解开发】

二、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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

緑水長流*z

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值