---------------------------------------------------------------------
@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文件中配置属性值---------------------------4、配置绑定-------------通过@ImportResource注解注入到容器中----------------- @ImportResource("classpath:bean.xml") -------------------main--结果为第一张运行图---------------------- boolean pet = run.containsBean("pet"); System.out.println("容器中是否有该组件:"+pet);
使用场景:将实体类中的属性值通过配置文件(.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
configurations = this.getCandidateConfigurations(annotationMetadata, attributes);
- 利用加载工厂得到所有的组件
Map
> 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; } -
-
- 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(表示已经生效的自动配置类)
-
-
静态资源目录
- 只要静态资源放在类路径下:当前类路径下的这四个包中–> /static—/public—/resources—/META-INF.resources 。可以直接通过项目根路径 /+静态资源名—>即可访问
- 原理:底层规定静态资源映射:
@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
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; } 测试首页的基本注解:
-
传参
- @PathVariable(路径变量)
- @RequestHeader(获取请求头)
- @RequestParam(获取请求参数)
- @CookieValue(获取Cookie值)
- @RequestBody(获取请求体)
- @MatrixVariable(获取矩阵变量)
- @RequestAttribute(获取Request域属性)
在页面开发中,如果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}”)
// https://blog.csdn.net/cars/sell;low=34;brand=byd,audi,yd @GetMapping("/cars/{path}") public Map carSell(@MatrixVariable("low") Integer low, @MatrixVariable("brand")Listbrand, @PathVariable("path") String path){ Map map = new HashMap<>(); map.put("low",low); map.put("brand",brand); map.put("path",path); return map; } //url路径变量 // https://blog.csdn.net/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 map = new HashMap<>(); map.put("age",bossAge); map.put("age",empAge); return map; } one two(矩阵变量) @MatrixVariable(矩阵变量)/boss/{bossId}/{empId}
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 APIWebRequest、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
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(); } MapbindingResultModel = 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上



