SpringBoot
一、基本注解
1、@configration详解
/**
* 1、配置类里面使用@Bean标注在方法上给容器注册组件,默认为单实例
* 2、配置类本身也是组件
* 3、proxyBeanMethods:
* full(proxyBeanMethods = true):全局 会保存代理对象,外部多次调用配置类中组件的方法,获取的值都是之前在容器中注册的单实例
* lite(proxyBeanMethods = false):轻量级 不会保存代理对象,外部多次调用配置类中组件的方法,每次调用都会产生一个新的对象
* 使用场景为:组件依赖
*
* 如何使用:1、如果只是给容器中注册组件,类组件中没有依赖关系,一般调成false,加快springboot的启动速度(lite模式)
* 2、类组件中有依赖,调成true,保证依赖的组件就为容器中的组件(full模式)
* 4、@Import({user.class, DBHelper.class})
* 给容器中自动创建出这俩个类型的组件、默认组件的名字就是全类名
*/
---------------------------------------------------------------------
@Configuration(proxyBeanMethods = true) //被该注解声明的类为配置类 == 配置文件(beans.xml)
@Import({user.class, DBHelper.class})
public class MyConfig {
@Bean //给容器中添加组件,以方法名作为组件ID,返回类型为组件类型,返回值就是容器中的对象(实例)
public user user01(){
user us = new user("小蔡", 20);
us.setPet(userPet());
// System.out.println("宠物是==>"+userPet());
return us;
}
@Bean("My")
public Pet userPet(){
return new Pet("哈士奇","宠物狗");
}
}
-------------------main--结果为第一张运行图----------------------
user user01 = run.getBean("user01", user.class);
Pet bean = run.getBean("My", Pet.class);
System.out.println("用户的宠物:"+(user01.getPet() == bean));
-------------------main--结果为第二张运行图----------------------
//通过import组件导入的俩个组件进行获取组件
String[] beanNamesForType = run.getBeanNamesForType(user.class);
for (String beng : beanNamesForType) {
System.out.println("实体类的组件名称:"+beng);
}
DBHelper helper = run.getBean(DBHelper.class);
System.out.println("组件名称:"+helper);
----------------------------------------------------------------
2、@Conditional详解
@ConditionalOnBean(name = "My") //该注解加载到类上,表明下列组件中没有“My”组件,则所有组件都不生效
@Configuration(proxyBeanMethods = true) //被该注解声明的类为配置类 == 配置文件(beans.xml)
public class MyConfig {
// 加载到某一个具体的方法上时,表名如果没有该组件,则该方法不被加载
@ConditionalOnBean(name = "My")
//给容器中添加组件,以方法名作为组件ID,返回类型为组件类型,返回值就是容器中的对象(实例)
@Bean
public user user01(){
user us = new user("小蔡", 20);
us.setPet(userPet());
// System.out.println("宠物是==>"+userPet());
return us;
}
// @Bean("My")
public Pet userPet(){
return new Pet("哈士奇","宠物狗");
}
}
-------------------main--结果为第一张运行图----------------------
boolean my = run.containsBean("My");
System.out.println("容器中是否存在该组件:"+my);
boolean containsBean = run.containsBean("user01");
System.out.println("容器中是否存在该组件:"+containsBean);
3、@ImportResource详解
使用场景:在配置文件中导入过多的组件,想迁移成注解的方式。就可以用该注解,重新解析
-------------------xml文件中配置属性值---------------------------
<bean id="pet" class="parent.pojo.Pet">
<property name="name" value="谢逊"></property>
<property name="petType" value="金毛狮王"></property>
</bean>
-------------通过@ImportResource注解注入到容器中-----------------
@ImportResource("classpath:bean.xml")
-------------------main--结果为第一张运行图----------------------
boolean pet = run.containsBean("pet");
System.out.println("容器中是否有该组件:"+pet);
4、配置绑定
使用场景:将实体类中的属性值通过配置文件(.properties/.yaml)进行赋值,通过@ConfigurationProperties(prefix = “mypet”)绑定前缀,并且通过添加组件的注解,把其加载到容器中去
方式一:@Component+@ConfigurationProperties(prefix = "mypet")
@Component
@ConfigurationProperties(prefix = "mypet")//绑定配置文件中的属性值
@Data
public class Pet {
private String name;
private String PetType;
}
--------------(.properties/.yaml)配置文件----------------------
mypet.name=ynu
mypet.PetType=mi
-------------------main-----------------------------------------
@RestController
public class FirstController {
@Autowired
Pet pet;
@RequestMapping("/go")
public Pet pet(){
return pet;
}
}
----------------------------方式二-------------------------------
在配置类(Config)中开启自动绑定,使用该注解就无须在实体类上添加注明添加组件的注解。
主要用到:实体类可能是第三方包,并没有在类上添加组件的注解
@EnableConfigurationProperties(Pet.class)
//1、开启Pet配置绑定功能
//2、把Pet组件自动注入到容器中去
//同时需要关联实体类中的属性
@ConfigurationProperties(prefix = "mypet")
二、自动配置原理
1、引导加载自动配置类
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(
excludeFilters = {@Filter(
type = FilterType.CUSTOM,
classes = {TypeExcludeFilter.class}
), @Filter(
type = FilterType.CUSTOM,
classes = {AutoConfigurationExcludeFilter.class}
)}
)
public @interface SpringBootApplication {}
-
@SpringBootConfiguration
-
@Configuration:代表当前是一个配置类
-
@ComponentScan:指定扫描包
-
@EnableAutoConfiguration —>这三个注解就相当于**@SpringBootConfiguration** 一个注解
-
@AutoConfigurationPackage @Import({AutoConfigurationImportSelector.class}) public @interface EnableAutoConfiguration {}
-
@AutoConfigurationPackage:自动配置包
-
@Import({Registrar.class}) //给容器中导入组件 public @interface AutoConfigurationPackage {} //利用Registrar给容器中导入一系列组件 //将指定包下的所有的组件导入容器中。这就解释了为什么默认的包路径是Main程序所在的包
@Import({AutoConfigurationImportSelector.class})(核心)
-
该方法给容器中导入批量的组件
AutoConfigurationImportSelector.AutoConfigurationEntry autoConfigurationEntry = this.getAutoConfigurationEntry(annotationMetadata);
-
调用该方法获取到所有需要导入到容器中的组件(134个)
List<String> configurations = this.getCandidateConfigurations(annotationMetadata, attributes);
- 利用加载工厂得到所有的组件
Map<String, List<String>> loadSpringFactories(ClassLoader classLoader)
- 从META-INF/spring.factories位置来加载文件
Enumeration urls = classLoader.getResources("META-INF/spring.factories");
默认扫描我们当前系统中所有META-INF/spring.factories位置的文件,其核心文件就是spring-boot-autoconfigure中的spring.factories
- 按需开启自动配置项
- 134个场景的自动配置组件,在启动的时候默认全部加载
- 但最终按照条件装配注解,@ConditionalOnClass该注解就是来确定包下是否有某一个需要加载的类,@ConditionalOnBean确定组件
- 修改默认配置
@Bean //容器中有这个类型的组件 @ConditionalOnBean({MultipartResolver.class}) @ConditionalOnMissingBean( //容器中没有这个名字multipartResolver的组件时 name = {"multipartResolver"}) //调用multipartResolver方法,传入参数,并且将参数返回出去,并且将返回值放在容器中 public MultipartResolver multipartResolver(MultipartResolver resolver) { //如果@Bean注解下的方法中有对象参数,这个参数值名称就会从容器中去找,即使用户配置的文件上传解析器名称不是multipartResolver,也会从容器中去找,然后返回出去 return resolver; }
-
-
2、自动配置流程总结
- SpringBoot会通过@SpringBootApplication注解下的@EnableAutoConfiguration中的@Import({AutoConfigurationImportSelector.class})加载所有的自动配置类
- 每个自动配置类按照条件注解@ConditionalOnClass或者@ConditionalOnMissingClass来决定是否生效,并且默认绑定xxxProperties.class类中指定的值和配置文件(springboot工程下的application.properties)进行绑定
- 生效的配置类就会默认给容器中装配组件:比如导入springmvc依赖就会有DispathcherServlet的配置类
- 定制化配置
- 用户通过在自定义的@Configuration类中,通过@Bean注解添加相应的组件,就会代替底层自动装配的组件
- 用户通过获取该配置文件的配置前缀,去修改对应的配置,比如修改缓存:prefix = “spring.cache”—>拿到前缀即可修改
- 查看自动配置装配了哪些
- 可以在配置文件中配置debug=true,开启自动配置报告。Negative matches(表示没生效的自动配置类)、Positive matches(表示已经生效的自动配置类)
-
三、Web开发
1、静态资源访问
-
静态资源目录
- 只要静态资源放在类路径下:当前类路径下的这四个包中–> /static—/public—/resources—/META-INF.resources 。可以直接通过项目根路径 /+静态资源名—>即可访问
- 原理:底层规定静态资源映射:/**—>请求进来,先去找Controller能否处理当前请求,能处理则处理,不能处理就将不能处理的所有请求交给静态资源处理器处理,如果静态资源处理器能够处理则处理,不能处理则返回404错误
-
静态资源访问前缀
-
默认是没有前缀,通过项目根路径 /+静态资源名—>即可访问
spring.mvc.static-path-pattern= /res/**
当前项目 + static-path-pattern定义的前缀 +静态资源名 = 静态资源文件
-
-
欢迎页支持
-
静态资源路径下的index.html
-
在配置静态资源路径时,配置访问前缀时,访问index.html页面也同时需要加上访问前缀,否则报错
spring: mvc: static-path-pattern: /gos/** #静态资源访问路径前缀 web: resources: static-locations: [classpath:/Welcome/] #修改默认静态资源访问包
-
自定义Favicon小图标
spring: mvc: static-path-pattern: /gos/** #静态资源访问路径前缀 #同时会影响小图标的展示
-
-
2、静态资源原理
1、流程
- SpringBoot启动默认加载134个自动配置类
- SpringMVC功能的自动配置类WebMvcAutoConfiguration,配置类是否生效
@ConditionalOnClass({Servlet.class, DispatcherServlet.class, WebMvcConfigurer.class})//是否有这几个类,加载SpringMVC依赖就有
@ConditionalOnMissingBean({WebMvcConfigurationSupport.class})//容器中没有该组件就不生效
- 给容器中配置了什么:配置文件的相关属性和xxx进行绑定
WebMvcProperties=====spring.mvc//(前缀)--->在yaml或者properties配置文件中均可配置相关属性
ResourceProperties=====spring.resources(前缀)//(前缀)--->在yaml或者properties配置文件中均可配置相关属性
//新增WebProperties
WebProperties====spring.web//(前缀)--->在yaml或者properties配置文件中均可配置相关属性
扩展:一个类中只有一个有参构造器—>有参构造器所有参数的值都会从容器中确定
//ResourceProperties resourceProperties:获取所有和spring.resources绑定的所有值的对象
//WebMvcProperties mvcProperties:获取所有和spring.mvc绑定的所有的值的对象
//ListableBeanFactory beanFactory:找到IOC,找到spring的beanFactory
//HttpMessageConverters:找到系统里的所有HttpMessageConverters
//resourceHandlerRegistrationCustomizer:找到资源处理器的自定义器
//DispatcherServletPath:找到DispatcherServlet可以处理的路径
//ServletRegistrationBean:给应用注册原生的Servlet、Filter、lisener....
//WebProperties:找到
public WebMvcAutoConfigurationAdapter(ResourceProperties resourceProperties, WebProperties webProperties, WebMvcProperties mvcProperties, ListableBeanFactory beanFactory, ObjectProvider<HttpMessageConverters> messageConvertersProvider, ObjectProvider<WebMvcAutoConfiguration.ResourceHandlerRegistrationCustomizer> resourceHandlerRegistrationCustomizerProvider, ObjectProvider<DispatcherServletPath> dispatcherServletPath, ObjectProvider<ServletRegistrationBean<?>> servletRegistrations) {
this.resourceProperties = (Resources)(resourceProperties.hasBeenCustomized() ? resourceProperties : webProperties.getResources());
this.mvcProperties = mvcProperties;
this.beanFactory = beanFactory;
this.messageConvertersProvider = messageConvertersProvider;
this.resourceHandlerRegistrationCustomizer = (WebMvcAutoConfiguration.ResourceHandlerRegistrationCustomizer)resourceHandlerRegistrationCustomizerProvider.getIfAvailable();
this.dispatcherServletPath = dispatcherServletPath;
this.servletRegistrations = servletRegistrations;
this.mvcProperties.checkConfiguration();
}
2、静态资源处理的默认规则
public void addResourceHandlers(ResourceHandlerRegistry registry) {
if (!this.resourceProperties.isAddMappings()) {
logger.debug("Default resource handling disabled");
} else {
//webjars的规则
this.addResourceHandler(registry, "/webjars/**", "classpath:/META-INF/resources/webjars/");
this.addResourceHandler(registry, this.mvcProperties.getStaticPathPattern(), (registration) -> {
registration.addResourceLocations(this.resourceProperties.getStaticLocations());
if (this.servletContext != null) {
ServletContextResource resource = new ServletContextResource(this.servletContext, "/");
registration.addResourceLocations(new Resource[]{resource});
}
});
-----------------静态资源欢迎页的处理----------------------------
WelcomePageHandlerMapping(TemplateAvailabilityProviders templateAvailabilityProviders, ApplicationContext applicationContext, Resource welcomePage, String staticPathPattern) {
//欢迎页请求必须为'/**'
if (welcomePage != null && "/**".equals(staticPathPattern)) {
logger.info("Adding welcome page: " + welcomePage);
this.setRootViewName("forward:index.html");
} else if (this.welcomeTemplateExists(templateAvailabilityProviders, applicationContext)) {
logger.info("Adding welcome page template: index");
this.setRootViewName("index");
}
}
-----------------静态资源默认能够存储的四个位置-------------------
private static final String[] CLASSPATH_RESOURCE_LOCATIONS = new String[]{"classpath:/META-INF/resources/", "classpath:/resources/", "classpath:/static/", "classpath:/public/"};
spring:
web:
resources:
add-mappings: true
cache:
period: 11120 #做缓存处理,时间以秒为单位,不用每次请求都去访问服务器,降低服务器的开销
3、Rest风格处理请求及其原理
-
请求映射
Rest原理(表单提交需带上name = “_method”)
-
用户发起请求会被HiddenHttpMethodFilter拦截
- 请求是否正常,并且是否为相应值,如:PUT、DEIETE
- 获取"_method"值
- 判断是否为Rest风格
- 原生request的请求方式只有两种(get、post),而包装模式HttpServletRequestWrapper继承了原生的HttpServletRequest接口,requestWrapper重写了getMethod方法,返回的是传入的值。过滤器放行的时候使用Wrapper。之后的方法调用的是requestWrapper中重写后的getMethod方法
- 请求是否正常,并且是否为相应值,如:PUT、DEIETE
-
Rest风格支持
//必须要在表单中添加'_method'标记 public static final String DEFAULT_METHOD_PARAM = "_method";
<input name="_method" type="hidden" value="Put"/>
- 必须在SpringBoot配置文件中手动开启
spring.mvc.hiddenmethod.filter.enabled=开true/on|关off/false
- 因为SpringBoot底层在自动配置的时候,默认是不加载的,需要手动开启
@Bean @ConditionalOnMissingBean({HiddenHttpMethodFilter.class}) @ConditionalOnProperty( prefix = "spring.mvc.hiddenmethod.filter", name = {"enabled"} ) public OrderedHiddenHttpMethodFilter hiddenHttpMethodFilter() { return new OrderedHiddenHttpMethodFilter(); }
- Controller层实现
@PutMapping("/user") public String TestPut(){return "Put";}
- 底层如何知道请求是Delete请求,因为在请求方法之前需经过Filter方法,而Filter中重写了getMethod,当请求经过filter时,Controller获取的则是重写getMethod方法,并且以delete方式输出
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { HttpServletRequest requestToUse = request; if ("POST".equals(request.getMethod()) && request.getAttribute("javax.servlet.error.exception") == null) { String paramValue = request.getParameter(this.methodParam); if (StringUtils.hasLength(paramValue)) { String method = paramValue.toUpperCase(Locale.ENGLISH); if (ALLOWED_METHODS.contains(method)) { requestToUse = new HiddenHttpMethodFilter.HttpMethodRequestWrapper(request, method); } } } ------------------重写的getMethod方法----------------------- public String getMethod() { return this.method; }
-
-
自定义HiddenHttpMethodFilter中的Rest参数
- 在容器中添加自定义的HiddenHttpMethodFilter方法,并且调用其setMethodParam()就可以更改其属性值
@Configuration(proxyBeanMethods = false)
public class WebConfig {
@Bean
public HiddenHttpMethodFilter hiddenHttpMethodFilter(){
HiddenHttpMethodFilter methodFilter = new HiddenHttpMethodFilter();
methodFilter.setMethodParam("_Tan");
return methodFilter;
}
}
4、请求映射原理
1、DispatcherServlet
-
定义:处理所有请求的开始,继承FrameworkServlet
-
分析:
- SpringMVC功能都从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 {
try {
ModelAndView mv = null;
Object dispatchException = null;
try {
processedRequest = this.checkMultipart(request);
multipartRequestParsed = processedRequest != request;
mappedHandler = this.getHandler(processedRequest);
if (mappedHandler == null) {
this.noHandlerFound(processedRequest, response);
return;
}
//找到当前请求使用哪个Handler(Controller方法处理)
HandlerAdapter ha = this.getHandlerAdapter(mappedHandler.getHandler());
- 首先会得到五个处理器映射
mappedHandler = this.getHandler(processedRequest);
//该方法中加载处理器映射
if (this.handlerMappings != null) {}
-
RequestMappingHandlerMapping:保存了所有@RequestMapping注解和Handler的映射机制
-
该方法中加载所有的处理器映射
-
SpringBoot自动配置欢迎页的WelcomePageHandlerMapping。直接访问’/'请求即可访问到index.html
-
SpringBoot自动配置了默认RequestMappingHandlerMapping,所有请求经过RequestMappingHandlerMapping找到相应的HandlerMapping,如果没有则报错。
//五个处理器
1、@RequestMappingHandlerMapping 2、@WelcomePageHandlerMapping
3、@BeanNameUrlHandlerMapping 4、@RouterFunctionMapping
5、@SimpleUrlHandlerMapping
5、普通参数与基本注解
1、注解
-
@PathVariable @RequestHeader @ModelAttribute @RequestParam @MatrixVariable @CookieValue @RequestBody
2、测试提交
// 定义Url传参
@GetMapping("/user/{id}/owner/{username}")
public Map<String, Object> testOwner(
// 路径变量
@PathVariable("id") Integer id,
@PathVariable("username") String name,
// 将传入的路径变量的值封装到map中
@PathVariable Map<String,String> pm,
@RequestHeader("Accept") String accept,
//拿到所有的请求头信息,封装到map中
@RequestHeader Map<String,String> Header,
@RequestParam("age") Integer age,
@RequestParam("items") String items,
@RequestParam Map<String,String> Param
){
Map<String, Object> map = new HashMap<>();
map.put("id",id);
map.put("name",name);
map.put("pm",pm);
map.put("Accept",accept);
map.put("age",age);
map.put("items",items);
map.put("param",Param);
//map.put("Headers",Header);
// 将map中的数据返回给页面
return map;
}
--------------------拿到表单提交的内容---------------------------
@PostMapping("/save")
public Map testBody(
@RequestBody String content
){
Map<String, Object> map = new HashMap<>();
map.put("content",content);
return map;
}
------------------------测试@RequestAttribute注解----------------
/*
测试 @RequestAttribute
*/
@GetMapping("/goTo")
public String TestRequestAttribute(
HttpServletRequest request
){
request.setAttribute("msg","成功了.....");
request.setAttribute("code","200");
// 请求转发到当前项目的路径上
return "forward:/success";
}
@ResponseBody
@GetMapping("/success") // 转发到该路径上
public Map GoToPage(@RequestAttribute("msg") String msg,
@RequestAttribute("code") Integer code,
HttpServletRequest request){
// 原生request中取出值
Object msg1 = request.getAttribute("msg");
Object code1 = request.getAttribute("code");
Map<String, Object> map = new HashMap<>();
// 拿到原生request中的值,存放到map中,并且展示出来
map.put("ReqMeth_msg",msg1);
map.put("ReqMeth_code",code1);
// 通过注解的方式拿到request中的值,存放到map中,并且展示出来
map.put("Annotation",msg);
map.put("Annotation",code);
return map;
}
测试首页的基本注解:
<ul>
<a href="/user/1/owner/王永江?age=18&items=cl&items=dk">传参</a>
<li>@PathVariable(路径变量)</li>
<li>@RequestHeader(获取请求头)</li>
<li>@RequestParam(获取请求参数)</li>
<li>@CookieValue(获取Cookie值)</li>
<li>@RequestBody(获取请求体)</li>
<li>@MatrixVariable(获取矩阵变量)</li>
<li>@RequestAttribute(获取Request域属性)</li>
</ul>
------------------------表单提交的内容---------------------------
<form action="/save" method="post">
用户名:<input name="userName"/></br>
密 码:<input name="passWord"/></br>
<input type="submit" value="登录"/>
</form>
-------------------------矩阵变量--------------------------------
1、问题
在页面开发中,如果Cookie被禁用了,session里面的内容该怎么使用?
答:①:在没禁用之前–>每次发起请求,cookie都会带上JsessionId,服务器按照JsessionId找到具体的session对象,然后调用session的get方法就能得到session中的内容
②:被禁用之后–>因为JsessionId存放在cookie中,就无法通过cookie拿到JsessionId,拿不到JsessionId那么服务器就找不到对应的session对象,从而获取不到session对象中的属性值
③:被禁用之后如何使用session中的内容—>可以通过矩阵变量的方式带上JsessionId(也称为 “路径重写” ),也就是把cookie的值通过矩阵变量的方式进行传递
6、矩阵变量与路径帮助器
-
主要用于:cookie被禁用的情况下,通过get方式进行传参
-
原理:
- SpringBoot对于路径的处理,通过UrlPathHelper进行解析,其中removeSemicolonContent(移除分号内容)属性支持矩阵变量,默认为true,默认是禁用矩阵变量的功能,必须手动开启,改为false
-
使用**@Bean注解往容器中添加组件WebMvcConfigurer或者实现WebMvcConfigurer接口,并且重写configurePathMatch**方法
-
------------------------实现接口的方法------------------------- @Override // configurePathMatch配置路径映射规则 public void configurePathMatch(PathMatchConfigurer configurer) { UrlPathHelper pathHelper = new UrlPathHelper(); // 将true改为false:不移除':'分号后的内容,矩阵变量生效 pathHelper.setRemoveSemicolonContent(false); // UrlPathHelper路径帮助器,默认是禁用掉矩阵变量的使用 configurer.setUrlPathHelper(pathHelper); } -------------------------添加组件的方法------------------------ @Bean public WebMvcConfigurer webMvcConfigurer(){ return new WebMvcConfigurer() { @Override public void configurePathMatch(PathMatchConfigurer configurer) { UrlPathHelper help = new UrlPathHelper(); // 设置成false:不移除';'分号后的内容,矩阵变量生效 help.setRemoveSemicolonContent(false); configurer.setUrlPathHelper(help); } }; }
-
重点:在使用矩阵变量的时候,一定要绑定在路径变量上@GetMapping(“/cars/{path}”)
// /cars/sell;low=34;brand=byd,audi,yd
@GetMapping("/cars/{path}")
public Map carSell(@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;
}
//url路径变量
// /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("age",bossAge);
map.put("age",empAge);
return map;
}
<a href="/cars/sell;low=34;brand=byd,audi,yd">one</a>
<a href="/cars/sell;low=34;brand=byd;brand=audi;brand=yd">two(矩阵变量)</a>
<a href="/boss/1;age=20/2;age=10">@MatrixVariable(矩阵变量)/boss/{bossId}/{empId}</a>
7、参数处理原理
1、如何处理
-
HandlerMapping中找到能处理请求的Handler,也就是Controller方法
-
为当前Handler找到一个适配器HandlerAdapter,而最终决定由哪个HandlerAdapter进行处理,是该方法作为判断,如果可以处理直接返回给DispathcerServlet下面的HandlerAdapter
//DispatcherServlet -- doDispatch 首先进入该方法 mv = ha.handle(processedRequest, response, mappedHandler.getHandler()); // 执行目标方法 -->在该类中InvocableHandlerMethod Object returnValue = this.invokeForRequest(webRequest, mavContainer, providedArgs); // 获取当前请求方法的参数值 Object[] args = this.getMethodArgumentValues(request, mavContainer, providedArgs); ------//该方法进行判断当前请求需要哪个适配器进行处理------------ public final boolean supports(Object handler) { return handler instanceof HandlerMethod && this.supportsInternal((HandlerMethod)handler); } ------------//--------返回给该方法进行处理--------------------- protected HandlerAdapter getHandlerAdapter(Object handler) throws ServletException { if (this.handlerAdapters != null) { Iterator var2 = this.handlerAdapters.iterator(); while(var2.hasNext()) { HandlerAdapter adapter = (HandlerAdapter)var2.next(); if (adapter.supports(handler)) { return adapter; } } } throw new ServletException("No adapter for handler [" + handler + "]: The DispatcherServlet configuration needs to include a HandlerAdapter that supports this handler"); }
四种适配器:
第0个:支持方法上标注@RequestMapping注解的(常用)
第1个:支持函数式编程的(常用)
2、参数解析器
1)SpringMVC的目标方法能写多少种参数类型的参数,取决于底层的参数解析器接口中规定了多少种参数
// 该方法中规定的27种参数解析器 if (this.argumentResolvers != null) { invocableMethod.setHandlerMethodArgumentResolvers(this.argumentResolvers); }
2)参数解析器接口中的方法判断
public interface HandlerMethodArgumentResolver {
// 判断当前是否支持需要处理的参数
boolean supportsParameter(MethodParameter var1);
// 支持则调用该方法进行解析
@Nullable
Object resolveArgument(MethodParameter var1, @Nullable ModelAndViewContainer var2, NativeWebRequest var3, @Nullable WebDataBinderFactory var4) throws Exception;
}
3)参数返回值处理器
if (this.returnValueHandlers != null) {
// 规定了15种处理器
invocableMethod.setHandlerMethodReturnValueHandlers(this.returnValueHandlers);
}
3、如何确定目标方法每一个参数的值
1)底层原理
//==========代码出处:--->InvocableHandlerMethod<----------------
protected Object[] getMethodArgumentValues(NativeWebRequest request, @Nullable ModelAndViewContainer mavContainer, Object... providedArgs) throws Exception {
// 先获取到所有方法的参数声明
MethodParameter[] parameters = this.getMethodParameters();
// 判断参数是否为空,没有参数的话,直接返回
if (ObjectUtils.isEmpty(parameters)) {
return EMPTY_ARGS;
} else {
// 通过Object数组对参数进行存储,长度随着参数个数变化
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) {
// 重点:判断当前解析器是否支持该参数类型
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 var10) {
if (logger.isDebugEnabled()) {
String exMsg = var10.getMessage();
if (exMsg != null && !exMsg.contains(parameter.getExecutable().toGenericString())) { logger.debug(formatArgumentError(parameter, exMsg));
}
}
throw var10;
}
}
}
return args;
}
}
----------------//重点:判断当前解析器是否支持该参数类型---------
@Nullable
private HandlerMethodArgumentResolver getArgumentResolver(MethodParameter parameter) {
HandlerMethodArgumentResolver result = (HandlerMethodArgumentResolver)this.argumentResolverCache.get(parameter);
if (result == null) {
// 将参数赋值给迭代器,然后依次遍历27个参数解析器,寻找是否有解析器能够解析传入的参数
Iterator var3 = this.argumentResolvers.iterator();
while(var3.hasNext()) {
HandlerMethodArgumentResolver resolver = (HandlerMethodArgumentResolver)var3.next();
if (resolver.supportsParameter(parameter)) {
result = resolver;
// 通过遍历,找到相应的参数解析器之后,放入缓存中,方便下次取,不用再从新遍历
this.argumentResolverCache.put(parameter, resolver);
break;
}
}
}
return result;
}
4、Servlet API
WebRequest、ServletRequest、MultipartRequest、 HttpSession、javax.servlet.http.PushBuilder、Principal、InputStream、Reader、HttpMethod、Locale、TimeZone、ZoneId
ServletRequestMethodArgumentResolver 以上的部分参数
能够处理的API
@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);
}
5、复杂参数
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(); 获取到值的
8、自定义类型(封装POJO)参数处理原理
ServletModelAttributeMethodProcessor:
---------------------------判断----------------------------------
private HandlerMethodArgumentResolver getArgumentResolver(MethodParameter parameter) {
HandlerMethodArgumentResolver result = (HandlerMethodArgumentResolver)this.argumentResolverCache.get(parameter);
if (result == null) {
Iterator var3 = this.argumentResolvers.iterator();
while(var3.hasNext()) {
HandlerMethodArgumentResolver resolver = (HandlerMethodArgumentResolver)var3.next();
// 判断解析器支持哪些参数
if (resolver.supportsParameter(parameter)) {
result = resolver;
this.argumentResolverCache.put(parameter, resolver);
break;
}
}
}
return result;
}
------------------------进入方法--------------------------------
public boolean supportsParameter(MethodParameter parameter) {
// 首先判断改参数是否有@ModelAttribute注解
return parameter.hasParameterAnnotation(ModelAttribute.class) ||
// 判断这个注解是不是必须的"require = true/false"
this.annotationNotRequired &&
// 判断是不是简单属性,“!:表示非简单属性” 返回true表示不是简单类型
!BeanUtils.isSimpleProperty(parameter.getParameterType());
}
------------------------进入方法:简单类型判断--------------------
public static boolean isSimpleValueType(Class<?> type) {
return Void.class != type && Void.TYPE != 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);
}
--------------------------------核心----------------------------
@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 = (ModelAttribute)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 {
try {
// 先创建一个空的实例对象,然后通过 WebDataBinder绑定器进行绑定
attribute = this.createAttribute(name, parameter, binderFactory, webRequest);
} catch (BindException var10) {
if (this.isBindExceptionRequired(parameter)) {
throw var10;
}
if (parameter.getParameterType() == Optional.class) {
attribute = Optional.empty();
} else {
attribute = var10.getTarget();
}
bindingResult = var10.getBindingResult();
}
}
if (bindingResult == null) {
// web数据绑定器,将请求参数的值绑定到指定的JavaBean中(也就是attribute中,而attribute在绑定数据之前就已经创建了一个空的attribute实例)。封装到binder里,而binder还有conversionService,里面有converters = 125个转换器。
// 为什么会有125个转换器呢?
// 因为请求是HTTP(超文本传输协议),认为万物皆文本,通过文本传输数字时,必须转换成java形式的Interget,这个时候就用到其中的转换器
WebDataBinder binder = binderFactory.createBinder(webRequest, attribute, name);
if (binder.getTarget() != null) {
if (!mavContainer.isBindingDisabled(name)) {
// 该方法如何进行绑定在下面
this.bindRequestParameters(binder, webRequest);
}
this.validateIfApplicable(binder, parameter);
if (binder.getBindingResult().hasErrors() && this.isBindExceptionRequired(binder, parameter)) {
throw new BindException(binder.getBindingResult());
}
}
if (!parameter.getParameterType().isInstance(attribute)) {
attribute = binder.convertIfNecessary(binder.getTarget(), parameter.getParameterType(), parameter);
}
bindingResult = binder.getBindingResult();
}
Map<String, Object> bindingResultModel = bindingResult.getModel();
mavContainer.removeAttributes(bindingResultModel);
mavContainer.addAllAttributes(bindingResultModel);
return attribute;
}
------------------------如何绑定的核心方法------ -----------------
protected void bindRequestParameters(WebDataBinder binder, NativeWebRequest request) {
// 1、先获取原生的request请求
ServletRequest servletRequest = (ServletRequest)request.getNativeRequest(ServletRequest.class);
// 2、判断请求不为空
Assert.state(servletRequest != null, "No ServletRequest");
// 3、拿到(WebDataBinder)web数据绑定器,转换成ServletRequestDataBinder,拿到request中的数据
ServletRequestDataBinder servletBinder = (ServletRequestDataBinder)binder;
// 4、再调用bind方法,将原生的request传进来进行绑定
servletBinder.bind(servletRequest);
}
核心原理:WebDataBinder(web数据绑定器)会最终把请求中的所有数据利用这125个转换器进行转换,再通过反射绑定到空的JavaBean上