15【SpringMVC的注解开发】

news2024/11/19 11:24:11

文章目录

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/112520.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

短视频播放量超10w后,流量变少的问题解决方案

短视频播放量超10w后&#xff0c;流量变少的问题解决方案 上一篇我们聊了视频播放超10w后&#xff0c;会遇到流量变少的问题并分析了可能的原因&#xff0c;既然知道了原因&#xff0c;那么我们就可以针对性的去解决了。 今天给大家聊一聊在我赢助手跟超200名短视频创作者沟通…

Allegro如何设置差分动态等长规则操作指导

Allegro如何设置差分动态等长规则操作指导 Allegro上可以对差分设置动态等长规则,让差分对在任意一段距离上都是满足等长误差的,尤其是在差分对走线较长的情况下 以下面这两对线为例 具体操作如下 打开constraint Manage选择Physical规则

Linux 管理联网 设置主机名( nmtui图形化 和 hostnamectl命令 )

设置主机名 # 常用的有两种方式&#xff0c;一种是 nmtui 图形化界面的方式来设置&#xff0c; 一种是 hostnamectl 命令的方式来设置。 nmtui 直接在命令行 输入 nmtui 便进入 图形化界面 >>> 最后一选项&#xff08; 红底&#xff09; 便是 设置主机名~&#x…

微导纳米科创板上市:市值125亿 无锡首富王燕清再敲钟

雷递网 雷建平 12月23日江苏微导纳米科技股份有限公司&#xff08;简称&#xff1a;“微导纳米”&#xff0c;股票代码为&#xff1a;“688147”&#xff09;今日在科创板上市。微导纳米此次发行4544.55万股&#xff0c;发行价为24.21元&#xff0c;募资总额为11亿元。微导纳米…

react笔记_11 redux

目录redux定义使用时机redux基本概念StoreStateActionreducerredux工作原理语法[1] 创建StorecreateStorecombineReducers[2]创建并分发actiondispatchapplyMiddleware语法举例说明- 做一个加法运算执行原理redux-thunk中间件使用[3]创建reducer语法渲染过程[4]getState[5]subs…

基于meanshift算法的目标聚类和目标跟踪matlab仿真

目录 1.算法描述 2.仿真效果预览 3.MATLAB核心程序 4.完整MATLAB 1.算法描述 meanshift算法其实通过名字就可以看到该算法的核心&#xff0c;mean&#xff08;均值&#xff09;&#xff0c;shift&#xff08;偏移&#xff09;&#xff0c;简单的说&#xff0c;也就是有一个…

Web前端105天-day63-HTML5_CORE

HTML5CORE03 目录 前言 一、复习 二、SVG 三、Echarts 四、Webworker 五、回调地狱 六、Promise 七、promiseajax 八、promise_axios 九、async_await 总结 前言 HTML5CORE03学习开始 一、复习 跨域 浏览器的同源策略限定: 网页中利用 AJAX 请求数据, 必须访问同源…

【CSS】flex布局用法解析,快速上手flex布局,flex:1是什么意思?肯定看的懂好吧?

一、flex布局 flex 是 flexible box 的缩写&#xff0c;意为"弹性布局"&#xff0c;用来为盒状模型提供最大的灵活性。 任何一个容器都可以指定为 flex 布局。 采用 flex 布局的元素&#xff0c;称为 flex 容器&#xff08;flex container&#xff09;&#xff0c;…

如何解决跨越

解决跨域我想在坐的各位都会听说过几个解决跨域的方法&#xff1a; 1.有什么 cors后端配置&#xff08;加几个请求头 2.jsonp&#xff08;利用script的src属性&#xff09; 3.还有vue-cli前端配置。 跨域其实就是违背了浏览器的一种策略&#xff0c;这种策略就是同源策略&…

公司刚来的阿里p8,看完我构建的springboot框架,甩给我一份文档

前言&#xff1a; 我们刚开始学习 JavaWeb 的时候&#xff0c;使用 Servlet/JSP 做开发&#xff0c;一个接口搞一个 Servlet &#xff0c;很头大&#xff0c;后来我们通过隐藏域或者反射等方式&#xff0c;可以减少 Servlet 的创建&#xff0c;但是依然不方便&#xff0c;再后…

【Transformer】医学分割领域的应用与扩展(论文阅读)(二) || DETR

声明:仅学习使用~ 目录 1. Transformer学习2. DETR1. Transformer学习 前篇指路:【Transformer】医学分隔领域的应用与扩展(论文阅读)(一) 继续… 关于Self-Attention的公式: 原来是 m x m 是2D的,现在变成1 x m了,是1D的了。 下图中。左图是传统的Transformer,右…

【python圣诞树的实现】

&#x1f935;‍♂️ 个人主页老虎也淘气 个人主页 ✍&#x1f3fb;作者简介&#xff1a;Python学习者 &#x1f40b; 希望大家多多支持我们一起进步&#xff01;&#x1f604; 如果文章对你有帮助的话&#xff0c; 欢迎评论 &#x1f4ac;点赞&#x1f44d;&#x1f3fb; 收藏…

拒不外传,阿里内部耗重金找人总结出这份并发编程手册(全彩版)

并发世界很有趣&#xff0c;不要错过 前言&#xff1a; 时间飞逝&#xff0c;转眼间毕业七年多&#xff0c;从事 Java 开发也六年了。我在想&#xff0c;也是时候将自己的 Java 整理成一套体系。 这一次的知识体系面试题涉及到 Java 知识部分、性能优化、微服务、并发编程、开…

python常用模块

time模块 常用操作 1.直接获取时间 time.time() #获取结果是秒数&#xff0c;即从1970年1月1日8:00起计#1671856010.9592516 2.获取结构化时间 time.localtime() #获取本地时间&#xff0c;中国为东八区&#xff0c;为上海时间 time.gmtime() …

C++控制台圣诞树

前言 有是一年圣诞节&#xff0c;先祝大家圣诞节快乐&#xff0c;所以本蒟蒻在AFO之后决定回来更新一篇打印字符圣诞树的教程 &#xff08;呃好吧我承认我就是想嫖奖品&#xff09; 效果展示 呃我知道这有点拉&#xff0c;但是……蒟蒻能有什么坏心思呢&#xff0c;他只不过想…

客户端服务端交互实现

问题 客户端业务逻辑如何实现&#xff1f; 与服务设备具体交互细节如何设计&#xff1f; 客户端业务逻辑实现 用户输入处理 字符串空格处理&#xff0c;分割获取命令与参数 服务信息处理 字符串预处理&#xff0c;分割获取服务命令存储服务命令与设备地址之间的映射(命令字…

Java Socket实现NIO通信

文章目录一.简单介绍通道&#xff08;Channel&#xff09;多路复用器&#xff08;Selector&#xff09;二.代码实现.客户端服务端运行结果一.简单介绍 NIO 很多人也称之为 Non-block I/O&#xff0c;即非阻塞 I/O&#xff0c;因为这样叫&#xff0c;更能体现它的特点。 为什么…

kdump功能

kdump功能前言1 kdump流程2 kdump配置2.1 kexec、makedumpfile编译2.2 系统内核2.3 捕获内核3 kdump测试4 kdump的不足前言 kdump 是一种先进的基于 kexec 的内核崩溃转储机制。当系统崩溃时&#xff0c;kdump会将内存导出为vmcore保存到磁盘。 在kernel1运行的时候&#xff…

RabbitMQ——延迟队列

目录 一、延迟队列的应用场景 1. 场景&#xff1a;"订单下单成功后&#xff0c;15分钟未支付自动取消" ① 传统处理超时订单 ② RabbitMQ延时队列方案 二、延迟队列中的消息投递和消息消费 1.TTL 和 DLX ① TTL ② DLX和死信队列 ③ 延迟队列 ④ 开发步骤 …

spring mvc 通过异常封装 验证 方法

正常情况 我们先演示一下正常情况下我们验证的方法。 首先定义一个LoginBean Data public class LoginBean {// Blank 不允许保存空格&#xff0c;空格不算内容NotBlank(message "用户名不能为空")String username;// Empty 允许保留空格&#xff0c;是空格也算内容…