Spring Boot
启动类
maven依赖
<parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.3.4.RELEASE</version> </parent> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> </dependencies>
@SpringBootApplication public class MainApplication{ public static void main(String[] args){ SpringApplication.run(MainApplication.class,args); } }
maven设置
<mirrors> <mirror> <id>nexus-aliyun</id> <mirrorOf>central</mirrorOf> <name>Nexus aliyun</name> <url>http://maven.aliyun.com/nexus/content/groups/public</url> </mirror> </mirrors> <profiles> <profile> <id>jdk-1.8</id> <activation> <activeByDefault>true</activeByDefault> <jdk>1.8</jdk> </activation> <properties> <maven.compiler.source>1.8</maven.compiler.source> <maven.compiler.target>1.8</maven.compiler.target> <maven.compiler.compilerVersion>1.8</maven.compiler.compilerVersion> </properties> </profile> </profiles>
了解自动配置
依赖管理
父项目做依赖管理,几乎声明了所有开发中常用的依赖的版本号,自动仲裁机制,可以修改版本号
见到很多spring-boot-starter-*
:就某种场景,只要引入starter,这个场景的多有常规需要的依赖我们都引入
见到*-spring-boot-starter
:第三方为我们提供的简化开发的场景启动器
查看spring-boot-dependencies
里面规定当前依赖的版本用的key
在当前项目里面重写配置
<properties> <mysql.version>5.1.43</mysql.version> </properties>
自动配置
自动配置好Tomcat:引入Tomcat依赖。配置Tomcat
自动配置好SpringMVC:引入springmvc全套,自动配置好了springmvc常用功能
自动配置好Web常见功能:Springboot帮我们配置好了常见的开发场景
默认的包结构:主程序所在的包及其下面的所有子包里面的组件会被默认扫描进来,想要改变扫描路径,@SpringBootApplication(scanBasePackage="")或者@ComponentScan("")指定扫描路径
各项配置有默认值:默认配置最终都是映射到MultipartProperties,配置文件的值最终会绑定到每个类上
按需加载所有自动配置项:非常多的starter,引入了哪些场景这个场景的自动配置才会开启,spring boot 的自动配置都在spring-boot-autoconfigure包里面
@Configuration
告诉SpringBoot这是一个配置类==配置文件,默认是单实例的
配置类本身也是组件
proxyBeanMethods:代理bean的方法,spring boot总会检查这个组件是否在容器中,保证组件单实例(如果为false,就不能保证单实例)组件依赖,如果没有人用这个组件,一般为false,如果别人用这个组件,一般为true,为了保证单例
@Configuration//告诉SpringBoot这是一个配置类==配置文件 public class MyConfig{ @Bean public User user01(){//给容器中添加组件,以方法名作为组件id,返回类型就是组件类型。返回的值,就是组件在容器中的实例 return new User("zhangsan",18); } }
FULL(proxyBeanMethods=true)模式和Lite(proxyBeanMethods=false)模式
配置类组件之间无依赖关系用LITE模式加速容器启动过程,减少判断
配置类组件之间有依赖关系,方法回呗调用得到之前的单实例组件,用Full模式。
@Import
可以在组件或者配置类上,给容器中创建出这两个类型的组件、默认组件的名字就是全类名
@Conditional
条件装配:满足Conditional指定的条件,则进行组件注入
@ImportResource
导入xml中的Spring配置
配置绑定@ConfigurationProperties
第一种方式:
@Componment//只有个在容器中的组件才能拥有SpringBoot的强大功能
@ConfigurationProperties(prefix="")以 prefix前缀的配置可以用在组件中
第二种方式:
在配置类上添加@EnableConfigurationProperties(类.class)//1.开启属性配置功能 2.把Car这个组件自动注册到容器中
@ConfigurationProperties(prefix="")获取配置文件中以prefix开头的属性
自动配置原理入门
引导加载自动配置类
1.@SpringBootApplication = @SpringBootConfiguration 代表当前类是一个配置类。+@EnableAutoConfiguration。+ @ComponentScan:指定扫描哪些,Spring注解
@EnableAutoConfiguration
@AutoConfigurationPackage//自动配置包 @Import(AutoConfigurationImportSelector.class) public @interface EnableAutoConfiguration {}
@AutoConfigurationPackage:
@Import(AutoConfigurationPackages.Registrar.class) //给容器中导入一个组件 public @interface AutoConfigurationPackage {} //利用Registrar给容器中导入一系列组件 //将指定的一个包下的所有组件导入进来?MainApplication 所在包下。
@Import(AutoConfigurationImportSelector.class)
1、利用getAutoConfigurationEntry(annotationMetadata);给容器中批量导入一些组件 2、调用List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes)获取到所有需要导入到容器中的配置类 3、利用工厂加载 Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader);得到所有的组件 4、从META-INF/spring.factories位置来加载一个文件。 默认扫描我们当前系统里面所有META-INF/spring.factories位置的文件 spring-boot-autoconfigure-2.3.4.RELEASE.jar包里面也有META-INF/spring.factories org.springframework,boot.autoconfigure.EnableAutoConfiguration=\ springboot中写死了一启动就会加载127个配置类
按需开启自动装配
虽然我们127个场景的所有自动配置启动的时候默认全部加载,按照条件装配规则(@Conditional),最终会按需配置
修改默认配置
@Bean @ConditionalOnBean(MultipartResolver.class) //容器中有这个类型组件 @ConditionalOnMissingBean(name = DispatcherServlet.MULTIPART_RESOLVER_BEAN_NAME) //容器中没有这个名字 multipartResolver 的组件 public MultipartResolver multipartResolver(MultipartResolver resolver) { //给@Bean标注的方法传入了对象参数,这个参数的值就会从容器中找。 //SpringMVC multipartResolver。防止有些用户配置的文件上传解析器不符合规范 // Detect if the user has created a MultipartResolver but named it incorrectly return resolver; } 给容器中加入了文件上传解析器;
总结:
springboot先加载所有的自动配置类,每个自动配置类按照条件进行生效,默认都会绑定配置文件指定的值。
生效的配置类就会给容器中装配很多的组件,只要容器中有这些组件,相当于这些功能就有了,只要用户有自己配置的,就以用户的优先。
xxxAutoConfiguration ---->组件 ----->xxxxProperties里面拿值 ----->application.properties
定制化配置:用户直接自己@Bean替换底层的组件,用户去看这个组件是获取的配置文件什么值就去修改
最佳实践
引入场景依赖: https://docs.spring.io/spring-boot/docs/current/reference/html/using.html#using.build-systems.starters
查看自动配置了哪些(选做):自己分析,引入场景对应的自动配置一般都生效了,配置文件中debug=true开启自动配置报告,Negative(不生效)\Postive(生效)、
是否需要修改:按照文档修改配置项,自定义加入或者替换组件
配置文件
文件类型:properties yaml
yaml基本语法
- key:value;kv之间有空格
- 大小写敏感
- 使用缩进表示层级关系
- 缩进不允许使用tab,只允许空格
- 缩进的空格数不重要,只要表示层级的元素左对齐就行
- '#'表示注释
- ''与""表示字符串内容,单引号会转义/双引号不会转义
配置提示
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-configuration-processor</artifactId> <optional>true</optional> </dependency> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> <configuration> <excludes> <exclude> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-configuration-processor</artifactId> </exclude> </excludes> </configuration> </plugin> </plugins> </build>
Web开发
SpringMVC自动配置
Spring Boot provides auto-configuration for Spring MVC that works well with most applications.(大多场景我们都无需自定义配置)
The auto-configuration adds the following features on top of Spring’s defaults:
● Inclusion of ContentNegotiatingViewResolver and BeanNameViewResolver beans.
○ 内容协商视图解析器和BeanName视图解析器
● Support for serving static resources, including support for WebJars (covered later in this document)).
○ 静态资源(包括webjars)
● Automatic registration of Converter, GenericConverter, and Formatter beans.
○ 自动注册 Converter,GenericConverter,Formatter
● Support for HttpMessageConverters (covered later in this document).
○ 支持 HttpMessageConverters (后来我们配合内容协商理解原理)
● Automatic registration of MessageCodesResolver (covered later in this document).
○ 自动注册 MessageCodesResolver (国际化用)
● Static index.html support.
○ 静态index.html 页支持
● Custom Favicon support (covered later in this document).
○ 自定义 Favicon
● Automatic use of a ConfigurableWebBindingInitializer bean (covered later in this document).
○ 自动使用 ConfigurableWebBindingInitializer ,(DataBinder负责将请求数据绑定到JavaBean上)
1.静态资源访问
static/,public/,resources/,META-INF/resources/,下的静态资源可以直接访问
访问:当前项目根路径/+静态资源名
原理:静态映射/**
请求今来,先去找Controller看能不能处理,不能处理的所有请求又都交给静态资源处理器。静态资源找不到就是404
2.静态资源访问前缀
静态资源是无前缀的
spring: mvc: # 会导致Favicon和welcom页失效 static-path-pattern: /res/** resources: static-locations: [classpath:/haha/]
当前项目+static-path-pattern +静态资源名 = 静态资源文件夹下找
3.webjar
自动映射 /webjars/**
https://www.webjars.org/
<dependency> <groupId>org.webjars</groupId> <artifactId>jquery</artifactId> <version>3.5.1</version> </dependency>
访问地址:http://localhost:8080/webjars/jquery/3.5.1/jquery.js 后面地址要按照依赖里面的包路径
4.欢迎页
● 静态资源路径下 index.html
○ 可以配置静态资源路径
○ 但是不可以配置静态资源的访问前缀。否则导致 index.html不能被默认访问
spring: # mvc: # static-path-pattern: /res/** 这个会导致welcome page功能失效 resources: static-locations: [classpath:/haha/]
5.自定义Favicon
favicon.ico 放在静态资源目录下即可。
spring: # mvc: # static-path-pattern: /res/** 这个会导致 Favicon 功能失效
6.静态资源配置原理
● SpringBoot启动默认加载 xxxAutoConfiguration 类(自动配置类)
● SpringMVC功能的自动配置类 WebMvcAutoConfiguration,生效
@Configuration(proxyBeanMethods = false) @ConditionalOnWebApplication(type = Type.SERVLET) @ConditionalOnClass({ Servlet.class, DispatcherServlet.class, WebMvcConfigurer.class }) @ConditionalOnMissingBean(WebMvcConfigurationSupport.class) @AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE + 10) @AutoConfigureAfter({ DispatcherServletAutoConfiguration.class, TaskExecutionAutoConfiguration.class, ValidationAutoConfiguration.class }) public class WebMvcAutoConfiguration {}
给容器中配了什么。
@Configuration(proxyBeanMethods = false) @Import(EnableWebMvcConfiguration.class) @EnableConfigurationProperties({ WebMvcProperties.class, ResourceProperties.class }) @Order(0) public static class WebMvcAutoConfigurationAdapter implements WebMvcConfigurer {}
配置文件的相关属性和xxx进行了绑定。WebMvcPropertiesspring.mvc、ResourcePropertiesspring.resources
1.配置类只有一个有参构造器
//有参构造器所有参数的值都会从容器中确定 //ResourceProperties resourceProperties;获取和spring.resources绑定的所有的值的对象 //WebMvcProperties mvcProperties 获取和spring.mvc绑定的所有的值的对象 //ListableBeanFactory beanFactory Spring的beanFactory //HttpMessageConverters 找到所有的HttpMessageConverters //ResourceHandlerRegistrationCustomizer 找到 资源处理器的自定义器。========= //DispatcherServletPath //ServletRegistrationBean 给应用注册Servlet、Filter.... public WebMvcAutoConfigurationAdapter(ResourceProperties resourceProperties, WebMvcProperties mvcProperties, ListableBeanFactory beanFactory, ObjectProvider<HttpMessageConverters> messageConvertersProvider, ObjectProvider<ResourceHandlerRegistrationCustomizer> resourceHandlerRegistrationCustomizerProvider, ObjectProvider<DispatcherServletPath> dispatcherServletPath, ObjectProvider<ServletRegistrationBean<?>> servletRegistrations) { this.resourceProperties = resourceProperties; this.mvcProperties = mvcProperties; this.beanFactory = beanFactory; this.messageConvertersProvider = messageConvertersProvider; this.resourceHandlerRegistrationCustomizer = resourceHandlerRegistrationCustomizerProvider.getIfAvailable(); this.dispatcherServletPath = dispatcherServletPath; this.servletRegistrations = servletRegistrations; }
2.资源处理的默认规则
spring: # mvc: # static-path-pattern: /res/** resources: add-mappings: false # 禁用所有静态资源规则
@ConfigurationProperties(prefix = "spring.resources", ignoreUnknownFields = false) public class ResourceProperties { private static final String[] CLASSPATH_RESOURCE_LOCATIONS = { "classpath:/META-INF/resources/", "classpath:/resources/", "classpath:/static/", "classpath:/public/" }; /** * Locations of static resources. Defaults to classpath:[/META-INF/resources/, * /resources/, /static/, /public/]. */ private String[] staticLocations = CLASSPATH_RESOURCE_LOCATIONS;
3.欢迎页的处理规则
HandlerMapping:处理器映射。保存了每一个Handler能处理哪些请求。 @Bean public WelcomePageHandlerMapping welcomePageHandlerMapping(ApplicationContext applicationContext, FormattingConversionService mvcConversionService, ResourceUrlProvider mvcResourceUrlProvider) { WelcomePageHandlerMapping welcomePageHandlerMapping = new WelcomePageHandlerMapping( new TemplateAvailabilityProviders(applicationContext), applicationContext, getWelcomePage(), this.mvcProperties.getStaticPathPattern()); welcomePageHandlerMapping.setInterceptors(getInterceptors(mvcConversionService, mvcResourceUrlProvider)); welcomePageHandlerMapping.setCorsConfigurations(getCorsConfigurations()); return welcomePageHandlerMapping; } WelcomePageHandlerMapping(TemplateAvailabilityProviders templateAvailabilityProviders, ApplicationContext applicationContext, Optional<Resource> welcomePage, String staticPathPattern) { if (welcomePage.isPresent() && "/**".equals(staticPathPattern)) { //要用欢迎页功能,必须是/** logger.info("Adding welcome page: " + welcomePage.get()); setRootViewName("forward:index.html"); } else if (welcomeTemplateExists(templateAvailabilityProviders, applicationContext)) { // 调用Controller /index logger.info("Adding welcome page template: index"); setRootViewName("index"); } }
4.favicon
浏览器会发送/favicon.ico请求获取到图标,整个session期间不再获取
请求参数处理
请求映射
@xxxMappint:
Rest风格支持(使用http请求方式动词来表示对资源的操作)
○ 以前:/getUser 获取用户 /deleteUser 删除用户 /editUser 修改用户 /saveUser 保存用户
○ 现在: /user GET-获取用户 DELETE-删除用户 PUT-修改用户 POST-保存用户
○ 核心Filter;HiddenHttpMethodFilter
■ 用法: 表单method=post,隐藏域 _method=put
■ SpringBoot中手动开启:spring.mvc.hiddenmethod.filter:true
○ 扩展:如何把_method 这个名字换成我们自己喜欢的。
@RequestMapping(value = "/user",method = RequestMethod.GET) public String getUser(){ return "GET-张三"; } @RequestMapping(value = "/user",method = RequestMethod.POST) public String saveUser(){ return "POST-张三"; } @RequestMapping(value = "/user",method = RequestMethod.PUT) public String putUser(){ return "PUT-张三"; } @RequestMapping(value = "/user",method = RequestMethod.DELETE) public String deleteUser(){ return "DELETE-张三"; } @Bean @ConditionalOnMissingBean(HiddenHttpMethodFilter.class) @ConditionalOnProperty(prefix = "spring.mvc.hiddenmethod.filter", name = "enabled", matchIfMissing = false) public OrderedHiddenHttpMethodFilter hiddenHttpMethodFilter() { return new OrderedHiddenHttpMethodFilter(); } //自定义filter @Bean public HiddenHttpMethodFilter hiddenHttpMethodFilter(){ HiddenHttpMethodFilter methodFilter = new HiddenHttpMethodFilter(); methodFilter.setMethodParam("_m"); return methodFilter; }
Rest原理(表单提交要使用REST的时候)
● 表单提交会带上_method=PUT
● 请求过来被HiddenHttpMethodFilter拦截 请求是否正常,并且是POST
■ 获取到_method的值。
■ 兼容以下请求;PUT.DELETE.PATCH
■ 原生request(post),包装模式requesWrapper重写了getMethod方法,返回的是传入的值。
■ 过滤器链放行的时候用wrapper。以后的方法调用getMethod是调用requesWrapper的。
Rest使用客户端工具:如PostMan直接发送Put、delete等方式请求,无需Filter。
spring: mvc: hiddenmethod: filter: enabled: true #开启页面表单的Rest功能
请求映射原理
SpringMVC功能分析都从 org.springframework.web.servlet.DispatcherServlet->doDispatch()
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception { HttpServletRequest processedRequest = request; HandlerExecutionChain mappedHandler = null; boolean multipartRequestParsed = false; WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request); try { ModelAndView mv = null; Exception dispatchException = null; try { processedRequest = checkMultipart(request); multipartRequestParsed = (processedRequest != request); // 找到当前请求使用哪个Handler(Controller的方法)处理 mappedHandler = getHandler(processedRequest); //HandlerMapping:处理器映射。/xxx->>xxxx
RequestMappingHandlerMapping:保存了所有@RequestMapping 和handler的映射规则。
所有的请求映射都在HandlerMapping中。
- SpringBoot自动配置欢迎页的 WelcomePageHandlerMapping 。访问 /能访问到index.html;
- SpringBoot自动配置了默认 的 RequestMappingHandlerMapping
- 请求进来,挨个尝试所有的HandlerMapping看是否有请求信息。
- 如果有就找到这个请求对应的handler
- 如果没有就是下一个 HandlerMapping
- 我们需要一些自定义的映射处理,我们也可以自己给容器中放HandlerMapping。自定义 HandlerMapping
普通参数与基本注解
注解:
@PathVariable、@RequestHeader、@ModelAttribute、@RequestParam、@MatrixVariable、@CookieValue、@RequestBody
@RestController public class ParameterTestController { // car/2/owner/zhangsan @GetMapping("/car/{id}/owner/{username}") public Map<String,Object> getCar(@PathVariable("id") Integer id, @PathVariable("username") String name, @PathVariable Map<String,String> pv, @RequestHeader("User-Agent") String userAgent, @RequestHeader Map<String,String> header, @RequestParam("age") Integer age, @RequestParam("inters") List<String> inters, @RequestParam Map<String,String> params, @CookieValue("_ga") String _ga, @CookieValue("_ga") Cookie cookie){ Map<String,Object> map = new HashMap<>(); // map.put("id",id); // map.put("name",name); // map.put("pv",pv); // map.put("userAgent",userAgent); // map.put("headers",header); map.put("age",age); map.put("inters",inters); map.put("params",params); map.put("_ga",_ga); System.out.println(cookie.getName()+"===>"+cookie.getValue()); return map; } @PostMapping("/save") public Map postMethod(@RequestBody String content){ Map<String,Object> map = new HashMap<>(); map.put("content",content); return map; } //页面开发如果cookie禁用了,session里面的内容怎么使用; //session.set(a,b)--->jsessionid--->cookie---->每次发请求携带。 //url重写:/abc;jsessionid=xxx 把cookie的值使用矩阵变量的方式进行传递 //1、语法: 请求路径:/cars/sell;low=34;brand=byd,audi,yd //2、SpringBoot默认是禁用了矩阵变量的功能 // 手动开启:原理。对于路径的处理。UrlPathHelper进行解析。 // removeSemicolonContent(移除分号内容)支持矩阵变量的 //3、矩阵变量必须有url路径变量才能被解析 // /cars/{path}?xxx=xxx& @GetMapping("/cars/{path}") public Map carsSell(@MatrixVariable("low") Integer low, @MatrixVariable("brand") List<String> brand, @PathVariable("path") String path){ Map<String,Object> map = new HashMap<>(); map.put("low",low); map.put("brand",brand); map.put("path",path); return map; } // /boss/1;age=20/2;age=10 @GetMapping("/boss/{bossId}/{empId}") public Map boss(@MatrixVariable(value = "age",pathVar = "bossId") Integer bossAge, @MatrixVariable(value = "age",pathVar = "empId") Integer empAge){ Map<String,Object> map = new HashMap<>(); map.put("bossAge",bossAge); map.put("empAge",empAge); return map; } }
Servlet API:
WebRequest、ServletRequest、MultipartRequest、 HttpSession、javax.servlet.http.PushBuilder、Principal、InputStream、Reader、HttpMethod、Locale、TimeZone、ZoneId
ServletRequestMethodArgumentResolver 以上的部分参数
@Override public boolean supportsParameter(MethodParameter parameter) { Class<?> paramType = parameter.getParameterType(); return (WebRequest.class.isAssignableFrom(paramType) || ServletRequest.class.isAssignableFrom(paramType) || MultipartRequest.class.isAssignableFrom(paramType) || HttpSession.class.isAssignableFrom(paramType) || (pushBuilder != null && pushBuilder.isAssignableFrom(paramType)) || Principal.class.isAssignableFrom(paramType) || InputStream.class.isAssignableFrom(paramType) || Reader.class.isAssignableFrom(paramType) || HttpMethod.class == paramType || Locale.class == paramType || TimeZone.class == paramType || ZoneId.class == paramType); }
复杂参数:
Map、Model(map、model里面的数据会被放在request的请求域 request.setAttribute)、Errors/BindingResult、RedirectAttributes( 重定向携带数据)、ServletResponse(response)、SessionStatus、UriComponentsBuilder、ServletUriComponentsBuilder
Map<String,Object> map, Model model, HttpServletRequest request 都是可以给request域中放数据, request.getAttribute();
Map、Model类型的参数,会返回 mavContainer.getModel();---> BindingAwareModelMap 是Model 也是Map
mavContainer.getModel(); 获取到值的
POJO封装过程
● ServletModelAttributeMethodProcessor 这个参数处理器支持
参数处理原理
● HandlerMapping中找到能处理请求的Handler(Controller.method())
● 为当前Handler 找一个适配器 HandlerAdapter; RequestMappingHandlerAdapter
● 适配器执行目标方法并确定方法参数的每一个值
0 - 支持方法上标注@RequestMapping
1 - 支持函数式编程的xxx
执行目标方法
// Actually invoke the handler. //DispatcherServlet -- doDispatch mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
mav = invokeHandlerMethod(request, response, handlerMethod); //执行目标方法 //ServletInvocableHandlerMethod Object returnValue = invokeForRequest(webRequest, mavContainer, providedArgs); //获取方法的参数值 Object[] args = getMethodArgumentValues(request, mavContainer, providedArgs);
参数解析器-HandlerMethodArgumentResolver
确定将要执行的目标方法的每一个参数的值是什么;
SpringMVC目标方法能写多少种参数类型。取决于参数解析器。
● 当前解析器是否支持解析这种参数
● 支持就调用 resolveArgument
返回值处理器
如何确定目标方法每一个参数的值:
============InvocableHandlerMethod========================== protected Object[] getMethodArgumentValues(NativeWebRequest request, @Nullable ModelAndViewContainer mavContainer, Object... providedArgs) throws Exception { MethodParameter[] parameters = getMethodParameters(); if (ObjectUtils.isEmpty(parameters)) { return EMPTY_ARGS; } Object[] args = new Object[parameters.length]; for (int i = 0; i < parameters.length; i++) { MethodParameter parameter = parameters[i]; parameter.initParameterNameDiscovery(this.parameterNameDiscoverer); args[i] = findProvidedArgument(parameter, providedArgs); if (args[i] != null) { continue; } if (!this.resolvers.supportsParameter(parameter)) { throw new IllegalStateException(formatArgumentError(parameter, "No suitable resolver")); } try { args[i] = this.resolvers.resolveArgument(parameter, mavContainer, request, this.dataBinderFactory); } catch (Exception ex) { // Leave stack trace for later, exception may actually be resolved and handled... if (logger.isDebugEnabled()) { String exMsg = ex.getMessage(); if (exMsg != null && !exMsg.contains(parameter.getExecutable().toGenericString())) { logger.debug(formatArgumentError(parameter, exMsg)); } } throw ex; } } return args; }
挨个判断所有参数解析器那个支持解析这个参数
@Nullable private HandlerMethodArgumentResolver getArgumentResolver(MethodParameter parameter) { HandlerMethodArgumentResolver result = this.argumentResolverCache.get(parameter); if (result == null) { for (HandlerMethodArgumentResolver resolver : this.argumentResolvers) { if (resolver.supportsParameter(parameter)) { result = resolver; this.argumentResolverCache.put(parameter, result); break; } } } return result; }
解析这个参数的值
调用各自 HandlerMethodArgumentResolver 的 resolveArgument 方法即可
自定义类型参数 封装POJO
ServletModelAttributeMethodProcessor 这个参数处理器支持 是否为简单类型。
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)); }
@Override @Nullable public final Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer, NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception { Assert.state(mavContainer != null, "ModelAttributeMethodProcessor requires ModelAndViewContainer"); Assert.state(binderFactory != null, "ModelAttributeMethodProcessor requires WebDataBinderFactory"); String name = ModelFactory.getNameForParameter(parameter); ModelAttribute ann = parameter.getParameterAnnotation(ModelAttribute.class); if (ann != null) { mavContainer.setBinding(name, ann.binding()); } Object attribute = null; BindingResult bindingResult = null; if (mavContainer.containsAttribute(name)) { attribute = mavContainer.getModel().get(name); } else { // Create attribute instance try { attribute = createAttribute(name, parameter, binderFactory, webRequest); } catch (BindException ex) { if (isBindExceptionRequired(parameter)) { // No BindingResult parameter -> fail with BindException throw ex; } // Otherwise, expose null/empty value and associated BindingResult if (parameter.getParameterType() == Optional.class) { attribute = Optional.empty(); } bindingResult = ex.getBindingResult(); } } if (bindingResult == null) { // Bean property binding and validation; // skipped in case of binding failure on construction. WebDataBinder binder = binderFactory.createBinder(webRequest, attribute, name); if (binder.getTarget() != null) { if (!mavContainer.isBindingDisabled(name)) { bindRequestParameters(binder, webRequest); } validateIfApplicable(binder, parameter); if (binder.getBindingResult().hasErrors() && isBindExceptionRequired(binder, parameter)) { throw new BindException(binder.getBindingResult()); } } // Value type adaptation, also covering java.util.Optional if (!parameter.getParameterType().isInstance(attribute)) { attribute = binder.convertIfNecessary(binder.getTarget(), parameter.getParameterType(), parameter); } bindingResult = binder.getBindingResult(); } // Add resolved attribute and BindingResult at the end of the model Map<String, Object> bindingResultModel = bindingResult.getModel(); mavContainer.removeAttributes(bindingResultModel); mavContainer.addAllAttributes(bindingResultModel); return attribute; }
WebDataBinder binder = binderFactory.createBinder(webRequest, attribute, name);
WebDataBinder :web数据绑定器,将请求参数的值绑定到指定的JavaBean里面
WebDataBinder 利用它里面的 Converters 将请求数据转成指定的数据类型。再次封装到JavaBean中
GenericConversionService:在设置每一个值的时候,找它里面的所有converter那个可以将这个数据类型(request带来参数的字符串)转换到指定的类型(JavaBean -- Integer)
byte -- > file
@FunctionalInterfacepublic interface Converter<S, T>
未来我们可以给WebDataBinder里面放自己的Converter;
private static final class StringToNumber<T extends Number> implements Converter<String, T>
自定义 Converter
//1、WebMvcConfigurer定制化SpringMVC的功能 @Bean public WebMvcConfigurer webMvcConfigurer(){ return new WebMvcConfigurer() { @Override public void configurePathMatch(PathMatchConfigurer configurer) { UrlPathHelper urlPathHelper = new UrlPathHelper(); // 不移除;后面的内容。矩阵变量功能就可以生效 urlPathHelper.setRemoveSemicolonContent(false); configurer.setUrlPathHelper(urlPathHelper); } @Override public void addFormatters(FormatterRegistry registry) { registry.addConverter(new Converter<String, Pet>() { @Override public Pet convert(String source) { // 啊猫,3 if(!StringUtils.isEmpty(source)){ Pet pet = new Pet(); String[] split = source.split(","); pet.setName(split[0]); pet.setAge(Integer.parseInt(split[1])); return pet; } return null; } }); } }; }
目标方法执行完成:将所有的数据都放在 ModelAndViewContainer;包含要去的页面地址View。还包含Model数据。
处理派发结果
processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
renderMergedOutputModel(mergedModel, getRequestToExpose(request), response);
InternalResourceView: @Override protected void renderMergedOutputModel( Map<String, Object> model, HttpServletRequest request, HttpServletResponse response) throws Exception { // Expose the model object as request attributes. exposeModelAsRequestAttributes(model, request); // Expose helpers as request attributes, if any. exposeHelpers(request); // Determine the path for the request dispatcher. String dispatcherPath = prepareForRendering(request, response); // Obtain a RequestDispatcher for the target resource (typically a JSP). RequestDispatcher rd = getRequestDispatcher(request, dispatcherPath); if (rd == null) { throw new ServletException("Could not get RequestDispatcher for [" + getUrl() + "]: Check that the corresponding file exists within your web application archive!"); } // If already included or response already committed, perform include, else forward. if (useInclude(request, response)) { response.setContentType(getContentType()); if (logger.isDebugEnabled()) { logger.debug("Including [" + getUrl() + "]"); } rd.include(request, response); } else { // Note: The forwarded resource is supposed to determine the content type itself. if (logger.isDebugEnabled()) { logger.debug("Forwarding to [" + getUrl() + "]"); } rd.forward(request, response); } }
暴露模型作为请求域属性 // Expose the model object as request attributes. exposeModelAsRequestAttributes(model, request);
protected void exposeModelAsRequestAttributes(Map<String, Object> model, HttpServletRequest request) throws Exception { //model中的所有数据遍历挨个放在请求域中 model.forEach((name, value) -> { if (value != null) { request.setAttribute(name, value); } else { request.removeAttribute(name); } }); }
数据响应与内容协商
响应JSON
jackson.jar+@ResponseBody
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> web场景自动引入了json场景 <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-json</artifactId> <version>2.3.4.RELEASE</version> <scope>compile</scope> </dependency>
给前端自动返回json数据;
返回值解析器:
try { this.returnValueHandlers.handleReturnValue( returnValue, getReturnValueType(returnValue), mavContainer, webRequest); }
@Override public void handleReturnValue(@Nullable Object returnValue, MethodParameter returnType, ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception { HandlerMethodReturnValueHandler handler = selectHandler(returnValue, returnType); if (handler == null) { throw new IllegalArgumentException("Unknown return value type: " + returnType.getParameterType().getName()); } handler.handleReturnValue(returnValue, returnType, mavContainer, webRequest); }
RequestResponseBodyMethodProcessor @Override public void handleReturnValue(@Nullable Object returnValue, MethodParameter returnType, ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws IOException, HttpMediaTypeNotAcceptableException, HttpMessageNotWritableException { mavContainer.setRequestHandled(true); ServletServerHttpRequest inputMessage = createInputMessage(webRequest); ServletServerHttpResponse outputMessage = createOutputMessage(webRequest); // Try even with null return value. ResponseBodyAdvice could get involved. // 使用消息转换器进行写出操作 writeWithMessageConverters(returnValue, returnType, inputMessage, outputMessage); }
返回值解析器原理
- 1、返回值处理器判断是否支持这种类型返回值 supportsReturnType
- 2、返回值处理器调用 handleReturnValue 进行处理
- 3、RequestResponseBodyMethodProcessor 可以处理返回值标了@ResponseBody 注解的。
-
- 利用 MessageConverters 进行处理 将数据写为json
- 1、内容协商(浏览器默认会以请求头的方式告诉服务器他能接受什么样的内容类型)
- 2、服务器最终根据自己自身的能力,决定服务器能生产出什么样内容类型的数据,
- 3、SpringMVC会挨个遍历所有容器底层的 HttpMessageConverter ,看谁能处理?
- 1、得到MappingJackson2HttpMessageConverter可以将对象写为json
- 2、利用MappingJackson2HttpMessageConverter将对象转为json再写出去。
SpringMVC到底支持哪些返回值:
-
ModelAndView Model View ResponseEntity ResponseBodyEmitter StreamingResponseBody HttpEntity HttpHeaders Callable DeferredResult ListenableFuture CompletionStage WebAsyncTask 有 @ModelAttribute 且为对象类型的 @ResponseBody 注解 ---> RequestResponseBodyMethodProcessor;
HTTPMessageConverter原理
HttpMessageConverter: 看是否支持将 此 Class类型的对象,转为MediaType类型的数据。
例子:Person对象转为JSON。或者 JSON转为Person
默认的MessageConverter:
0 - 只支持Byte类型的
1 - String
2 - String
3 - Resource
4 - ResourceRegion
5 - DOMSource.class \ SAXSource.class) \ StAXSource.class \StreamSource.class \Source.class
6 - MultiValueMap
7 - true
8 - true
9 - 支持注解方式xml处理的。
最终 MappingJackson2HttpMessageConverter 把对象转为JSON(利用底层的jackson的objectMapper转换的)
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 为什么说在企业级应用开发中,后端往往是效率杀手?
· 本地部署DeepSeek后,没有好看的交互界面怎么行!
· 趁着过年的时候手搓了一个低代码框架
· 推荐一个DeepSeek 大模型的免费 API 项目!兼容OpenAI接口!
· 用 C# 插值字符串处理器写一个 sscanf