建议先精读springmvc零配置原理
pom文件 依赖
<dependency>
<groupId>org.springframeworkgroupId>
<artifactId>spring-webmvcartifactId>
<version>5.0.8.RELEASEversion>
dependency>
<dependency>
<groupId>org.apache.tomcat.embedgroupId>
<artifactId>tomcat-embed-coreartifactId>
<version>8.5.5version>
dependency>
项目启动类-启动tomcat,回调onStartup初始化spring及mvc环境
public class MySpringApplication {
public static void main(String[] args) throws Exception {
MySpringApplication.run();
}
public static void run() throws Exception {
Tomcat tomcat = new Tomcat();
tomcat.setPort(80);
//addContext ,不是web项目,不会回调SpringServletContainerInitializer的onStartup方法
Context context = tomcat.addContext("/", System.getProperty("java.io.tmpdir"));
//添加LifecycleListener回调SpringServletContainerInitializer的onStartup方法
context.addLifecycleListener((LifecycleListener)
Class.forName(tomcat.getHost().getConfigClass()).newInstance());
tomcat.start();
tomcat.getServer().await();
}
}
初始化spring及mvc环境
public class LryWebApplicationInitializer implements WebApplicationInitializer {
//启动tomcat的时候会调用这里
@Override
public void onStartup(ServletContext servletCxt) {
//spring环境
AnnotationConfigWebApplicationContext ac = new AnnotationConfigWebApplicationContext();
ac.register(AppConfig.class);
//DispatcherServlet,mvc环境
DispatcherServlet servlet = new DispatcherServlet(ac);
ServletRegistration.Dynamic registration = servletCxt.addServlet("app", servlet);
registration.setLoadOnStartup(1);
registration.addMapping("/");
}
}
AppConfig配置文件
@Configuration 加了这个注解是spring全配置类,会被cglib代理,为什么会被代理我猜测是他要对这个类做逻辑增强防止某些非法事件发生,见下例
@ComponentScan("com.lry")
public class AppConfig {
}
解释@Configuration 为什么要被cglib代理
public class A {
public A(){
System.out.println("A");
}
}
public class B {
public B(){
System.out.println("B");
}
}
@Configuration
@ComponentScan("com.lry")
public class AppConfig {
@Bean
public A getA(){
return new A();
}
@Bean
public B getB(){
getA();
return new B();
}
}
在加上@Configuration的情况下A,B分别打印一次(一次A被cglib过滤了)
当你不加@Configuration,A会被打印两次
controller文件–实现controller的三种方式
方式一,@Controller注解
@Controller
public class HelloController {
@GetMapping("hello")
@ResponseBody
public String hello(){
return "hello SpringMVC";
}
}
方式二,实现Controller接口
访问/implementsController这个路径时会调用handleRequest方法,一个类只能处理一个请求,和原始servlet一样
@Component("/implementsController")
public class C1 implements Controller {
@Override
public ModelAndView handleRequest(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse) throws Exception {
return null;
}
}
方式三,实现HttpRequestHandler接口
访问/implementsHttpRequestHandler这个路径时会调用handleRequest方法,一个类只能处理一个请求,和原始servlet一样
@Component("/implementsHttpRequestHandler")
public class C2 implements HttpRequestHandler {
@Override
public void handleRequest(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse){
return;
}
}
方式二和方式三都过于笨重,目前基本不用
入口:
FrameworkServlet#doGet或者doPost---->processRequest---->DispatcherServlet#doService---->doDispatch
所有的请求都会走到DispatcherServlet的doDispatch方法
doDispatch方法核心代码如下
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
HttpServletRequest processedRequest = request;
//拿到对应的处理器
//大致可以这样理解,比如访问/hello,mappedHandler就是HelloController类和hello方法封装的一个对象
HandlerExecutionChain mappedHandler = this.getHandler(processedRequest);
//拿到处理器对应的处理器适配器,为什么要拿适配器,直接处理不好吗?
//我们知道有三种类型的controller,不同的controller处理方式都不同,按照我们常规写法就是
//if(@Controller的controller){处理逻辑,反射调用hello方法}
//else if(实现Controller接口的controller){处理逻辑,回调接口的handleRequest方法}
//这种写法很明显的弊端就是违反了开闭原则,当我们要处理第四种controller的时候必须新增一个elseif
//mvc采取适配器模式解决了这种问题,稍后会详细讲解并一个媒体播放的例子说明适配器模式
HandlerAdapter ha = this.getHandlerAdapter(mappedHandler.getHandler());
//真正的去调用hello方法或handleRequest方法,不同的适配器处理不同的controller
ModelAndView mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
}
getHandler核心代码如下
protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
//this.handlerMappings里有两个HandlerMapping
//一个是RequestMappingHandlerMapping,处理@Controller这种controller
//另一个是BeanNameUrlHandlerMapping,处理实现Controller和HttpRequestHandler接口的controller
//可以发现这里没有处理静态资源的HandlerMapping,spring boot就是实现HandlerMapping接口扩展了类来实现静态资源的读取
//从spring boot的扩展可以看出这里的扩展性是很强的,如果是常规代码,肯定又是
//if(@Controller的controller){} else if(实现Controller||实现HttpRequestHandler){}
//可以看出spring非常遵循开闭原则,可扩展性极强
//还有一个问题:this.handlerMappings是哪里来的,我们没有看到过这个东西在哪里add的
for(HandlerMapping hm:this.handlerMappings){
//遍历所有的handlerMapping,找到具体的controller(HelloController的hello||handlerRequest方法)
HandlerExecutionChain handler = hm.getHandler(request);
if (handler != null) {
return handler;
}
}
return null;
}
this.handlerMappings是哪里来的
this.handlerMappings是从DispatcherServlet#initStrategies这里来的,initStrategies就不继续往上追了,否则要追到tomcat源码,这就偏离了本文主题
initStrategies如下----省略部分非主要的内容
protected void initStrategies(ApplicationContext context) {
this.initHandlerMappings(context);//初始化this.handlerMappings
this.initHandlerAdapters(context);//初始化this.handlerAdapters
}
下面我们主要看看 this.initHandlerMappings(context);是怎样初始化this.handlerMappings的
private void initHandlerMappings(ApplicationContext context) {
this.handlerMappings = null;
//detectAllHandlerMappings默认是true,是否查找所有的HandlerMapping
if (this.detectAllHandlerMappings) {
//从spring容器拿出所有类型为HandlerMapping的bean,但是一般情况都是返回null
//因为spring没有把RequestMappingHandlerMapping和BeanNameUrlHandlerMapping放到spring容器,
//除非我们自己实现HandlerMapping写一个扩展类并@Component放入spring容器
//但是这种扩展方式有问题吧?这样一来handlerMappings就不是null了,
//找RequestMappingHandlerMapping和BeanNameUrlHandlerMapping的逻辑也就不会执行了
Map<String, HandlerMapping> matchingBeans = BeanFactoryUtils.beansOfTypeIncludingAncestors(context, HandlerMapping.class, true, false);
if (!matchingBeans.isEmpty()) {
this.handlerMappings = new ArrayList(matchingBeans.values());
AnnotationAwareOrderComparator.sort(this.handlerMappings);
}
} else {
//只查找beanName为handlerMapping的HandlerMapping
try {
HandlerMapping hm = (HandlerMapping)context.getBean("handlerMapping", HandlerMapping.class);
this.handlerMappings = Collections.singletonList(hm);
} catch (NoSuchBeanDefinitionException var3) {
;
}
}
//上面的代码可以先忽略,核心在下面
if (this.handlerMappings == null) {
//找到RequestMappingHandlerMapping和BeanNameUrlHandlerMapping
this.handlerMappings = this.getDefaultStrategies(context, HandlerMapping.class);
}
}
protected <T> List<T> getDefaultStrategies(ApplicationContext context, Class<T> strategyInterface) {
String key = strategyInterface.getName();//拿到HandlerMapping的全路径名
//defaultStrategies是一个Properties文件,在DsipatcherServlet的静态块里初始化
String value = defaultStrategies.getProperty(key);
//省略
}
}
static {
ClassPathResource resource = new ClassPathResource("DispatcherServlet.properties", DispatcherServlet.class);
defaultStrategies = PropertiesLoaderUtils.loadProperties(resource);
}
DispatcherServlet.properties关于HandlerMapping的配置如下:
org.springframework.web.servlet.HandlerMapping=org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping,\
org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping
hm.getHandler(request)
核心代码如下:
public final HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
Object handler = this.getHandlerInternal(request);//核心方法
if (handler == null) {
handler = this.getDefaultHandler();
}
if (handler == null) {
return null;
} else {
//把handler封装成HandlerExecutionChain
HandlerExecutionChain executionChain = this.getHandlerExecutionChain(, request);
return executionChain;
}
}
this.getHandlerInternal(request)
这个方法要看this是谁,如果this是BeanNameUrlHandlerMapping,则调用的是AbstractUrlHandlerMapping的getHandlerInternal,如果this是RequestMappingHandlerMapping,则调用的是AbstractHandlerMethodMapping的getHandlerInternal。
在本篇博客我只分析后者也就是@Controller方式
核心代码如下:
protected HandlerMethod getHandlerInternal(HttpServletRequest request) throws Exception {
String lookupPath = this.getUrlPathHelper().getLookupPathForRequest(request);
this.mappingRegistry.acquireReadLock();
HandlerMethod var4;
try {
//核心代码
HandlerMethod handlerMethod = this.lookupHandlerMethod(lookupPath, request);
var4 = handlerMethod != null ? handlerMethod.createWithResolvedBean() : null;
} finally {
this.mappingRegistry.releaseReadLock();
}
return var4;
}
this.lookupHandlerMethod(lookupPath, request)
核心代码如下
protected HandlerMethod lookupHandlerMethod(String lookupPath, HttpServletRequest request) throws Exception {
List<AbstractHandlerMethodMapping<T>.Match> matches = new ArrayList();
//你可以这样理解mappingRegistry,他就是存了url到controller之间映射的map
//<"/hello",HelloController#hello>
//this.mappingRegistry什么时候初始化的,什么时候把我们的映射对添加进去的?
List<T> directPathMatches = this.mappingRegistry.getMappingsByUrl(lookupPath);//核心代码
if (directPathMatches != null) {
//把directPathMatches加到matches里
this.addMatchingMappings(, matches, request);
}
if (!matches.isEmpty()) {
AbstractHandlerMethodMapping<T>.Match bestMatch = (AbstractHandlerMethodMapping.Match)matches.get(0);
return bestMatch.handlerMethod;
}
}
解释mappingRegistry什么时候初始化
AbstractAutowireCapableBeanFactory#doCreateBean
AbstractAutowireCapableBeanFactory#initializeBean
AbstractAutowireCapableBeanFactory#invokeInitMethods
RequestMappingHandlerMapping#afterPropertiesSet
AbstractHandlerMethodMapping#afterPropertiesSet
AbstractHandlerMethodMapping#initHandlerMethods
AbstractHandlerMethodMapping#detectHandlerMethods
AbstractHandlerMethodMapping#registerHandlerMethod
MappingRegistry#register
照着这些跟,其实也就是populateBean之后的InitializeBean的afterPropertiesSet做的处理
在说这个例子之前我想向大家先介绍一个demo,媒体播放
public interface MediaPlayer {
/**
* 支持哪种格式
* @param format 格式
* @return
*/
boolean supports(String format);
void play();
}
public class Mp3Player implements MediaPlayer {
@Override
public boolean supports(String format) {
if(".mp3".equals(format))
return true;
return false;
}
@Override
public void play() {
System.out.println("mp3播放");
}
}
public class Mp4Player implements MediaPlayer {
@Override
public boolean supports(String format) {
if(".mp4".equals(format))
return true;
return false;
}
@Override
public void play() {
System.out.println("mp4播放");
}
}
public class Main {
public static List<MediaPlayer> handlerAdapters = new ArrayList<>();
static {
handlerAdapters.add(new Mp3Player());
handlerAdapters.add(new Mp4Player());
}
public static void main(String[] args) {
MediaPlayer mediaPlayer = getHandlerAdapter(".mp3");
mediaPlayer.play();
mediaPlayer = getHandlerAdapter(".mp4");
mediaPlayer.play();
mediaPlayer = getHandlerAdapter(".mp4ds");
mediaPlayer.play();
}
public static MediaPlayer getHandlerAdapter(String format){
for (MediaPlayer mediaPlayer:handlerAdapters){
if(mediaPlayer.supports(format)){
return mediaPlayer;
}
}
throw new RuntimeException("没有找到合适的适配器");
}
}
看完这个例子,看源码秒懂
核心代码如下:
protected HandlerAdapter getHandlerAdapter(Object handler) throws ServletException {
//this.handlerAdapters有三个,分别是
//HttpRequestHandlerAdapter处理实现HttpRequestHandler的controller
//SimpleControllerHandlerAdapter处理实现Controller的controller
//RequestMappingHandlerAdapter处理@Controller的controller
///this.handlerAdapters何时初始化就不说了,和handlerMapping几乎一样
for(HandlerAdapter ha:this.handlerAdapters){
if (ha.supports(handler)) {
return ha;
}
}
throw new ServletException("No adapter for handler [" + handler + "]: The DispatcherServlet configuration needs to include a HandlerAdapter that supports this handler");
}
三种adapter的support如下:
实现HttpRequestHandler的controller
public boolean supports(Object handler) {
return handler instanceof HttpRequestHandler;
}
实现Controller的controller
public boolean supports(Object handler) {
return handler instanceof Controller;
}
@Controller的controller
public final boolean supports(Object handler) {
return handler instanceof HandlerMethod && this.supportsInternal((HandlerMethod)handler);
}
本小节只介绍handle方法,即真正去处理逻辑的代码,视图解析就不说了,因为现在都是基于rest的风格开发,试图解析几乎没用了。
上一小姐说到了HandlerAdapter,不同的Adapter处理不同的controller
ha.handle有四个实现,如下
AbstractHandlerMethodAdapter 处理@Controller实现的controller
HttpRequestHandlerAdapter 处理实现HttpRequestHandler接口的controller
SimpleControllerHandlerAdapter 处理实现Controller接口的 controller
SimpleServletHandlerAdapter 处理实现Servlet接口的 controller(猜想,并没有验证)
后三者都是实现接口,都是回调接口方法,即可,没什么好说的,下面我们着重说AbstractHandlerMethodAdapter
DispatcherServlet # ha.handle(processedRequest, response, mappedHandler.getHandler());---->
AbstractHandlerMethodAdapter # handle---->
RequestMappingHandlerAdapter # handleInternal(){
//核心方法只有这行
ModelAndView mav = this.invokeHandlerMethod(request, response, handlerMethod);
}---->
RequestMappingHandlerAdapter # invokeHandlerMethod(){
//核心方法只有这行
invocableMethod.invokeAndHandle(webRequest, mavContainer, new Object[0]);
}---->
ServletInvocableHandlerMethod # invokeAndHandle(){
//invoke反射调用我们controller的mapping方法
Object returnValue = this.invokeForRequest(webRequest, mavContainer, providedArgs);
//用response响应数据(@ResponseBody)
this.returnValueHandlers.handleReturnValue(returnValue, this.getReturnValueType(returnValue), mavContainer, webRequest);
}---->
InvocableHandlerMethod # invokeForRequest(){
//处理controller方法的参数,和自定义ArgumentResovler,稍后举例说明
Object[] args = this.getMethodArgumentValues(request, mavContainer, providedArgs);
//拿到参数反射调用方法
Object returnValue = this.doInvoke(args);
return returnValue;
}---->
InvocableHandlerMethod # getMethodArgumentValues(){
MethodParameter[] parameters = this.getMethodParameters();
Object[] args = new Object[parameters.length];
for(int i = 0; i < parameters.length; ++i) {
MethodParameter parameter = parameters[i];
parameter.initParameterNameDiscovery(this.parameterNameDiscoverer);
args[i] = this.resolveProvidedArgument(parameter, providedArgs);
if (args[i] == null) {
//又是一个support,mvc相当喜欢用这个设计模式,支持才做某某事,可以少些很多if else,并且扩展性也是大大提高,看来读源码确实非常提升内功,这些细小方法就不扣了,有兴趣自己看
if (this.argumentResolvers.supportsParameter(parameter)) {
args[i] = this.argumentResolvers.resolveArgument(parameter, mavContainer, request, this.dataBinderFactory);
}
}
}
return args;
}
自定义ArgumentResolver实例
pom文件增加fastjson依赖
<dependency>
<groupId>com.alibabagroupId>
<artifactId>fastjsonartifactId>
<version>1.2.47version>
dependency>
@User
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Target({ElementType.PARAMETER})
public @interface User {
}
LoginUser实体
public class LoginUser {
private int id;
private String name;
public LoginUser(int id,String name){
this.id = id;
this.name = name;
}
//get set toString 省略
}
MyArgumentResolver
支持supportsParameter才做resolveArgument
@Component
public class MyArgumentResolver implements HandlerMethodArgumentResolver {
@Override
public boolean supportsParameter(MethodParameter parameter) {
return parameter.getParameterType().isAssignableFrom(LoginUser.class)
&&
parameter.hasParameterAnnotation(User.class);
}
@Override
public Object resolveArgument(MethodParameter methodParameter, ModelAndViewContainer modelAndViewContainer, NativeWebRequest nativeWebRequest, WebDataBinderFactory webDataBinderFactory) throws Exception {
return new LoginUser(1,"lry");
}
}
AppConfig
@Configuration
@ComponentScan("com.lry")
public class AppConfig extends WebMvcConfigurationSupport{
@Override
public void addArgumentResolvers(List<HandlerMethodArgumentResolver> resolvers) {
resolvers.add(new MyArgumentResolver());
}
@Override
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
converters.add(new FastJsonHttpMessageConverter());
}
}
HelloController
@RequestMapping("user")
@ResponseBody
//MyArgumentResolver 的resolveArgument方法的返回值会给user这个参数
public LoginUser user(@User LoginUser user){
return user;
}