Name | Version |
Java | JDK 8+ |
Maven | 3.3+ |
Gradle | 6 (6.3 or later). 5.6.x is also supported but in a deprecated form |
Name | Servlet Version |
Tomcat 9.0 | 4.0 |
Jetty 9.4 | 3.1 |
Undertow 2.0 | 4.0 |
org.springframework.boot spring-boot-starter-parent 2.4.3
org.springframework.boot spring-boot-starter-web
. ____ _ __ _ _/\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \\\/ ___)| |_)| | | | | || (_| | ) ) ) )' |____| .__|_| |_|_| |_\__, | / / / /=========|_|==============|___/=/_/_/_/:: Spring Boot :: (v2.4.3)2021-02-24 23:08:21.251 INFO 3240 --- [ main] per.fkrobin.study.App : Starting App using Java 1.8.0_281 on DESKTOP-SKF5ODM with PID 3240 (D:\Projects\javaProjects\SpringBootStudy\HelloSpringBoot\target\classes started by 22136 in D:\Projects\javaProjects\SpringBootStudy)
2021-02-24 23:08:21.254 INFO 3240 --- [ main] per.fkrobin.study.App : No active profile set, falling back to default profiles: default
2021-02-24 23:08:21.824 INFO 3240 --- [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat initialized with port(s): 8080 (http)
浏览器打开 http://localhost:8080
每一个 Spring boot 应用都会有一个 parent 父工程
org.springframework.boot spring-boot-starter-parent 2.4.3
spring-boot-starter-parent的父工程
org.springframework.boot spring-boot-dependencies 2.4.3
spring-boot-dependencies这个工程中定义几乎所有的 常用工具包版本及其依赖,如 spring-boot-starter-web :
org.springframework.boot spring-boot-starter-web 2.4.3
所以我们使用 spring-boot-starter-web 不再需要书写版本号,如果需要更换版本号,可以查看 spring-boot-dependencies 中对应依赖的版本书写定义,然后在自己项目中 进行重新赋值
在此类初始化时,就会去 spring-boot、
spring-boot-auto-configure 加载 spring-factories 文件,文件中定义了 所有的 Spring 场景下的工厂,如 各种 xxxAutoConfiguration 类、Listener、Filter 等等,并缓存下来。在接下来调用 run 运行时,会根据条件注入、Filter 等过滤掉其中不需要用到的 工厂 、bean。下面是主要代码
try {ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);ConfigurableEnvironment environment = prepareEnvironment(listeners, bootstrapContext, applicationArguments);configureIgnoreBeanInfo(environment);Banner printedBanner = printBanner(environment);context = createApplicationContext();context.setApplicationStartup(this.applicationStartup);prepareContext(bootstrapContext, context, environment, listeners, applicationArguments, printedBanner);// 实例化 bean 到容器之中的过程都是在此完成的refreshContext(context);afterRefresh(context, applicationArguments);stopWatch.stop();if (this.logStartupInfo) {new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch);}listeners.started(context);callRunners(context, applicationArguments);
}
catch (Throwable ex) {handleRunFailure(context, ex, listeners);throw new IllegalStateException(ex);
}
其中 实例化 bean 到容器中华的一些列操作都是 springframework.beans 包中的相关类完成的,需要了解其原理才能看懂代码
查看此注解可以发现,在去掉一些定义即文档配置后,此注解实际就是 @SpringBootConfiguration , @EnableAutoConfiguration , @ComponentScan 结合:
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),@Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
public @interface SpringBootApplication{}
@Configuration
public @interface SpringBootConfiguration {boolean proxyBeanMethods() default true;
}
和 Spring 中一样,用于包扫描
所有自动配置的核心,实际上都和此注解有关
@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {}
public @interface Import {Class>[] value();
}
在容器中创建 value 数组指定的所有 类的实例,默认名字为 全限定类型(包路径+类名)
将properties文件中的内容翻译为一个 Java Bean 的过程被称为 配置绑定
目前官方文档已经取消了介绍自定义网页图标的部分,但功能依然存在
Spring boot 支持 RestFul 的设计风格,支持 GET、POST、DELETE、PUT、PATCH 等请求方式。由于表单只支持 GET、POST 方式,因此 Spring boot 使用了一个 HiddenHttpMethodFilter 的过滤器来实现请求方法的过滤、更改,流程:
因此,如果我们想自定义相关的操作,可以自己添加一个 HiddenHttpMethodFilter 的 bean,并修改相关属性,如 methdoParam
相关注解:@PathVariable、@RequestHeader、@ModelAttribute、@RequestParam、@MatrixVariable、@CookieValue、@RequestBody
List
Map
public @interface RequestParam {@AliasFor("name")String value() default "";@AliasFor("value")String name() default "";// 参数是否是必须的。如果为是,参数缺失时将会导致异常;否则将传入 nullboolean required() default true;String defaultValue() default ValueConstants.DEFAULT_NONE;
}
@RequestMapping("/rp1")
public String testRequestParam(@RequestParam String name, @RequestParam Integer id, @RequestParam("hobby")List hobbies) {return name + " " + id + "hobbies: " + hobbies;
}
Map
public @interface PathVariable {@AliasFor("name")String value() default "";@AliasFor("value")String name() default "";boolean required() default true;}
例子:
@RequestMapping("/param/{id}")
public String testPathVariable(@PathVariable Integer id) {return "testPathVariable: " + id;
}@RequestMapping("/param/{id}/owner/{username}")
public String testPathVariable2(@PathVariable Integer id, @PathVariable String username) {return String.format("testPathVariable2: id: %d, username: %s", id, username);
}@RequestMapping("/param/{id}/owner/{username}/{age}")
public String testPathVariable3(@PathVariable Map map) {return String.format("testPathVariable2: %s", map.toString());
}
public @interface RequestHeader {@AliasFor("name")String value() default "";@AliasFor("value")String name() default "";boolean required() default true;String defaultValue() default ValueConstants.DEFAULT_NONE;}
@RequestMapping("/param/rh1")
public String testRequestHeader(@RequestHeader("user-Agent") String userAgent) {return String.format("testRequestHeader: user-Agent: %s", userAgent);
}@RequestMapping("/param/rh2")
public String testRequestHeaders(@RequestHeader Map headers) {return String.format("testRequestHeader: %s", headers.toString());
}
public @interface CookieValue {@AliasFor("name")String value() default "";@AliasFor("value")String name() default "";boolean required() default true;String defaultValue() default ValueConstants.DEFAULT_NONE;
}
例子:
@RequestMapping("/cookie")
public String testCookie(@CookieValue("Idea-80024e6a") String idea) {return idea;
}
@RequestMapping("/cookie2")
public String testCookie2(@CookieValue("Idea-80024e6a") Cookie cookie) {return cookie.getName() + " " + cookie.getValue();
}
例子
@RequestMapping("/rb1")
public String testRequestBody(@RequestBody String body) {return body;
}
public @interface RequestAttribute {@AliasFor("name")String value() default "";@AliasFor("value")String name() default "";boolean required() default true;}
例子:
@GetMapping("/attribute1")
public void testRequestAttribute0(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {request.setAttribute("name", "ZhangSan");request.getRequestDispatcher("/ra1").forward(request, response);
}@RequestMapping("/ra1")
public String testRequestAttribute1(@RequestAttribute String name) {return name;
}
public @interface MatrixVariable {@AliasFor("name")String value() default "";@AliasFor("value")String name() default "";String pathVar() default ValueConstants.DEFAULT_NONE;boolean required() default true;String defaultValue() default ValueConstants.DEFAULT_NONE;}
例子:
@ResponseBody
@RequestMapping("/mv/{sell}")
public String testMatrixVar(@MatrixVariable("low") Integer low,@MatrixVariable("brand") List brads) {return brads.toString() + " " + low;
}
存在多个相同名字的 key 时,可以使用 pathVar 指定是哪一个路径变量下的变量
@ResponseBody
@RequestMapping("/mv2/{boss}/{emp}")
public String testMatrixVar2(@MatrixVariable(name = "age", pathVar = "boss") Integer age1,@MatrixVariable(name = "age", pathVar = "emp") Integer age2) {return age1.toString() + " " + age2;
}
RestFul是一种程序编程风格和开发方式,基于 HTTP
REST 指的是一组架构约束条件和原则。满足这些约束条件和原则的应用程序或设计就是 RESTful。
Web 应用程序最重要的 REST 原则是,客户端和服务器之间的交互在请求之间是无状态的。从客户端到服务器的每个请求都必须包含理解请求所必需的信息。如果服务器在请求之间的任何时间点重启,客户端不会得到通知。此外,无状态请求可以由任何可用服务器回答,这十分适合云计算之类的环境。客户端可以缓存数据以改进性能。
在服务器端,应用程序状态和功能可以分为各种资源。资源是一个有趣的概念实体,它向客户端公开。资源的例子有:应用程序对象、数据库记录、算法等等。每个资源都使用 URI (Universal Resource Identifier) 得到一个唯一的地址。所有资源都共享统一的接口,以便在客户端和服务器之间传输状态。使用的是标准的 HTTP 方法,比如 GET、PUT、POST 和 DELETE。Hypermedia 是应用程序状态的引擎,资源表示通过超链接互联。
eg: 同样访问 /user 的 URL,不同的请求方式对应不同的操作:
使用例子:
@RequestMapping("/request0")
public String testRequest(Map map, Model model, HttpServletRequest request) {map.put("map", "v1");model.addAttribute("model", "v2");request.setAttribute("request", "v3");return "forward:/param/request1";
}@ResponseBody
@RequestMapping("/request1")
public Map testRequest1(HttpServletRequest request) {Map map = new HashMap<>();map.put("map", request.getAttribute("map"));map.put("model", request.getAttribute("model"));map.put("request", request.getAttribute("request"));return map;
}
// 获取 handler
mappedHandler = getHandler(processedRequest);
// 获取 handlerAdapter
HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
//拦截器preHandle方法
preHandle(processRequest, response);
//调用处理器
handle(processedRequest, response, mappedHandler.getHandler());
//拦截器 postHandle 方法
postHandle(processedRequest, response, mv);
//处理分发结果,包括 ModelAndView 或 Exception
processDispatchResul(processedRequest, response, mappedHandler, mv, dispatchException);// 渲染给定的 ModelAndView,这是请求处理的最后一步render(mv, request, response);// 视图解析,循环调用所有配置的视图解析器解析视图 InternalResourceViewview = resolveViewName(viewName, mv.getModelInternal(), locale, request);// 视图渲染,view.render();// 合并静态属性、Model、pathVarscreateMergedOutputModel(model, request, response);// 实际实现渲染的方法renderMergedOutputModel(mergedModel, getRequestToExpose(request), response);// 将合并后的 Model 中的所有属性全部转移到 Request 中exposeModelAsRequestAttributes(model, request);// 分发请求RequestDispatcher rd = getRequestDispatcher(request, dispatcherPath);//rd.include(request, response);rd.forward(request, response);// 拦截器 afterCompletion 方法mappedHandler.triggerAfterCompletion(request, response, null);
//循环调用所有预设的 HandlerMapping 的 getHandler 方法,并返回第一个不为 null 的值
HandlerExecutionChain handler = mapping.getHandler(request);getHandler(HttpServletRequest request);getHandlerInternal(request);// 寻找能够处理请求的方法,可能存在多个,如果多个方法映射到同一路径,Spring boot 会直接抛出异常lookupHandlerMethod(lookupPath, request);return bestMatch.getHandlerMethod();// 将 String 类型的 bean 实例化为一个实际对象return handlerMethod.createWithResolvedBean();// 执行链,包含 handler method 和所有的拦截器HandlerExecutionChain executionChain = getHandlerExecutionChain(handler, request);return executionChain;
return handler;
// 获取 handler 对应的适配器
getHandlerAdapter(Object handler);adapter.supports(handler);return (handler instanceof HandlerMethod && supportsInternal((HandlerMethod) handler));
RequestMappingHandlerAdapter 的 handle 方法的实现:
handle(HttpServletRequest request, HttpServletResponse response, Object handler);return handleInternal(request, response, (HandlerMethod) handler);ModelAndView mav = invokeHandlerMethod(request, response, handlerMethod);ServletInvocableHandlerMethod invocableMethod = createInvocableHandlerMethod(handlerMethod);invocableMethod.setHandlerMethodArgumentResolvers(this.argumentResolvers);invocableMethod.setHandlerMethodReturnValueHandlers(this.returnValueHandlers);invocableMethod.invokeAndHandle(webRequest, mavContainer);Object returnValue = invokeForRequest(webRequest, mavContainer, providedArgs);returnValueHandlers.handleReturnValue(returnValue, getReturnValueType(returnValue), mavContainer, webRequest);return getModelAndView(mavContainer, modelFactory, webRequest);return mav;
invokeForRequest(webRequest, mavContainer, providedArgs);// 获取所有方法参数值Object[] args = getMethodArgumentValues(request, mavContainer, providedArgs);MethodParameter[] parameters = getMethodParameters();// 用于获取参数名称parameter.initParameterNameDiscovery(this.parameterNameDiscoverer);args[i] = this.resolvers.resolveArgument(parameter, mavContainer, request, this.dataBinderFactory);HandlerMethodArgumentResolver resolver = getArgumentResolver(parameter);// 迭代 Listresolver.supportsParameter(parameter);return resolver;return resolver.resolveArgument(parameter, mavContainer, webRequest, binderFactory);return doInvoke(args);
例子
RequestHeaderMapMethodArgumentResolver :
public boolean supportsParameter(MethodParameter parameter) {// 只要保证有 @RequestHeader 注解,并且参数类型为 Map 及其子类return (parameter.hasParameterAnnotation(RequestHeader.class) &&Map.class.isAssignableFrom(parameter.getParameterType()));
}resolveArgument(parameter, mavContainer, webRequest, binderFactory);//MultiValueMap result;// 因此参数 Map 必须是 String,String 类型Map result = new LinkedHashMap<>();
public boolean supportsParameter(MethodParameter parameter) {return (parameter.hasParameterAnnotation(ModelAttribute.class) ||(this.annotationNotRequired && !BeanUtils.isSimpleProperty(parameter.getParameterType())));
}public static boolean isSimpleProperty(Class> type) {Assert.notNull(type, "'type' must not be null");return isSimpleValueType(type) || (type.isArray() && isSimpleValueType(type.getComponentType()));
}public static boolean isSimpleValueType(Class> type) {return (Void.class != type && void.class != type &&(ClassUtils.isPrimitiveOrWrapper(type) ||Enum.class.isAssignableFrom(type) ||CharSequence.class.isAssignableFrom(type) ||Number.class.isAssignableFrom(type) ||Date.class.isAssignableFrom(type) ||Temporal.class.isAssignableFrom(type) ||URI.class == type ||URL.class == type ||Locale.class == type ||Class.class == type));
}// ModelAttributeMethodProcessor
resolveArgument(parameter, mavContainer, webRequest, binderFactory);// 创建一个空的 domain 对象attribute = createAttribute(name, parameter, binderFactory, webRequest);WebDataBinder binder = binderFactory.createBinder(webRequest, attribute, name);bindRequestParameters(binder, webRequest);ServletRequestDataBinder servletBinder = (ServletRequestDataBinder) binder;// 底层在对无法绑定的属性、错误属性等剔除后,将数据类型转换后绑定到 attribute 上servletBinder.bind(servletRequest);// 将请求中的所有参数全部转换为 PropertyValue,并加入 List 保存MutablePropertyValues mpvs = new MutablePropertyValues(request.getParameterMap());doBind(mpvs);// DataBinder 移除缺失的必填字段,并抛出错误doBind(mpvs);applyPropertyValues(mpvs);getPropertyAccessor().setPropertyValues(mpvs, isIgnoreUnknownFields(), isIgnoreInvalidFields());// 迭代 mpvs 中的所有 PropertyValue //setPropertyValue(pv); 调用重载方法setPropertyValue(pv.getName(), pv.getValue());nestedPa.setPropertyValue(tokens, new PropertyValue(propertyName, value));processLocalProperty(tokens, pv);valueToApply = convertForProperty(tokens.canonicalName, oldValue, originalValue, ph.toTypeDescriptor());ph.setValue(valueToApply);