关于静态资源的访问和实现原理
1.首先我们分析默认静态资源在哪里?
解答 : 静态资源默认放在WebMvcConfiguration中的资源处理器中的一个内部类中WebMvcConfigurationAdapter
在WebMvcConfigurationAdapter中的资源处理器中有很多方法,这些方法哪里来呢?哪些方法处理哪些资源呢?
@Configuration(
proxyBeanMethods = false
)
@Import({EnableWebMvcConfiguration.class})
@EnableConfigurationProperties({WebMvcProperties.class, WebProperties.class})
@Order(0)
public static class WebMvcAutoConfigurationAdapter implements WebMvcConfigurer, ServletContextAware
故可以看到这个静态内部类里面实现了WebMvcConfigurer这个接口,里面大部分方法都来自于WebMvcConfigurer这个接口中,
这个接口很重要,如果我们要自己实现一套配置就要实现这个接口,如果要使用里面的默认配置就不添加@EnbaleWebMvc,反之添加这个注解
查看接口 :
方法如下 :
default void configurePathMatch(PathMatchConfigurer configurer) // 路径匹配
default void configureContentNegotiation(ContentNegotiationConfigurer configurer) // 内容协商,
default void configureAsyncSupport(AsyncSupportConfigurer configurer) // 异步请求
default void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) // 默认处理器
default void addFormatters(FormatterRegistry registry) // 格式转换器
default void addInterceptors(InterceptorRegistry registry) // 拦截器
default void addResourceHandlers(ResourceHandlerRegistry registry) // 这个方法用于处理资源,是webmvc的资源处理器
default void addCorsMappings(CorsRegistry registry) // 这个我也没了解过
default void addViewControllers(ViewControllerRegistry registry) // 视图解析器
default void configureViewResolvers(ViewResolverRegistry registry) // 视图解析器的配置器
default void addArgumentResolvers(List<HandlerMethodArgumentResolver> resolvers) // 参数解析器
default void addReturnValueHandlers(List<HandlerMethodReturnValueHandler> handlers) // 返回值处理器
default void configureMessageConverters(List<HttpMessageConverter<?>> converters) // 信息转换
default void extendMessageConverters(List<HttpMessageConverter<?>> converters) // 协助内容协商的一个转换器
default void configureHandlerExceptionResolvers(List<HandlerExceptionResolver> resolvers) // 异常处理器配置器
default void extendHandlerExceptionResolvers(List<HandlerExceptionResolver> resolvers)
故看到上面的资源处理器,我们进资源处理器进行查看,
一、 public void addResourceHandlers(ResourceHandlerRegistry registry) {
// 用于检查是否开启了静态资源映射,可以点进去查看,默认值是打开的
if (!this.resourceProperties.isAddMappings()) {
logger.debug("Default resource handling disabled");
} else {
// 这个用于处理webjars下面的资源,WebJars 是用于管理前端库的一个工具,它的路径可以点进去看 private String webjarsPathPattern = "/webjars/**";
this.addResourceHandler(registry, this.mvcProperties.getWebjarsPathPattern(), "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});
}
});
}
}
二、如下 :
private void addResourceHandler(ResourceHandlerRegistry registry, String pattern, Consumer<ResourceHandlerRegistration> customizer) {
if (!registry.hasMappingForPattern(pattern)) {
// 添加资源到静态资源里面去
ResourceHandlerRegistration registration = registry.addResourceHandler(new String[]{pattern});
customizer.accept(registration);
// 下面是处理缓存的 , 由此可以看出静态资源里面是有缓存的
// 这个请求未修改,在多少时间内可以走缓存
registration.setCachePeriod(this.getSeconds(this.resourceProperties.getCache().getPeriod()));
// 设置缓存控制器
registration.setCacheControl(this.resourceProperties.getCache().getCachecontrol().toHttpCacheControl());
// 最后一次修改时间
registration.setUseLastModified(this.resourceProperties.getCache().isUseLastModified());
this.customizeResourceHandlerRegistration(registration);
}
}
三、回到第一步上面getStaticLocations这个方法中去,我们去这个方法里面去找静态资源,一直往下点
这个类也是WebProperties的内部类
@ConfigurationProperties("spring.web")
public class WebProperties {
public static class Resources {
// 默认的静态资源常量,如果没有指定就会是这个常量赋值给下下面的staticLocations
private static final String[] CLASSPATH_RESOURCE_LOCATIONS = new String[]{"classpath:/META-INF/resources/", "classpath:/resources/", "classpath:/static/", "classpath:/public/"};
private String[] staticLocations;
// 资源映射
private boolean addMappings;
private boolean customized;
// 执行器链
private final Chain chain;
// 缓存,刚刚上面提到了静态资源有缓存的问题
private final Cache cache;
故找到了默认静态资源
2.静态资源的该如何访问?
关于如何访问这个问题非常的简单,大家也应该都会,在未修改的前提下,放在默认的包下面自动就会进行静态资源扫描
private static final String[] CLASSPATH_RESOURCE_LOCATIONS = new String[]{"classpath:/META-INF/resources/", "classpath:/resources/", "classpath:/static/", "classpath:/public/"};
3.有哪几种方式添加静态资源?
目前我知道的方式 :
第一种 :配置文件中进行修改
怎么修改呢?springboot的提供了很多类与配置文件进行绑定,我们只需要找到了这些类的绑定类,查看前缀
进行修改,每个类上面都有属于自己的前缀名称
因为在WebProperties下面,可以查看这类的资源路径
故修改方式就是 :
spring.web.resources.static-locations=
注意 : 这种方式会覆盖原本的静态资源路径,导致原本的静态资源路径失效
第二种 :实现接口的方式进行修改
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
public class StaticResourceNotes implements WebMvcConfigurer {
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
// 保留了父类的配置,也就是上面提到的四个常量依旧有用,而且可以进行自己添加一些额外的静态资源
WebMvcConfigurer.super.addResourceHandlers(registry);
registry.addResourceHandler("/**").addResourceLocations("classpath:/a/" , "classpath:/b/");
}
}
第三种方式 : 采取Bean注入的方式,匿名实现类因为这个WebMvcConfiguer必须被注入到容器中才能使用web
@Configuration
public class StaticResourceNotes {
@Bean
public WebMvcConfigurer webMvcConfigurer(){
return new WebMvcConfigurer() {
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
WebMvcConfigurer.super.addResourceHandlers(registry);
registry.addResourceHandler("/**").addResourceLocations("classpath:/a/" , "classpath:/b/");
}
};
}
}
4.实现原理?
实现原理如第一问分析
关于各种参数接收和实现原理
1.有哪些方式进行参数接收?
方式一、使用原生态的Servlet进行参数接收
提供的模拟用户表单提交的HTML,下面的所有演示方法只需要改action里面的地址栏内容。
<form action="/getUserParameters" method="post">
id:<input type="text" name="id"/><br/>
name:<input type="text" name="name"/><br/>
age:<input type="text" name="age"/><br/>
<input type="submit" value="提交">
</form>
@RestController
public class UserController {
@RequestMapping("/getUserParameters")
// 疑问 : 这个request怎么来的?
public User getUserParameters(HttpServletRequest request){
String id = request.getParameter("id");
String name = request.getParameter("name");
String age = request.getParameter("age");
return new User(Integer.parseInt(id) , name , age);
}
}
方式二、使用@RequestParam
下面将讲三个一样值的注解来获取不同的参数值
@RequestParam()
可以通过设置形参的方式,但是如果前端参数和后端参数要求不一致,可使用里面进行设置里面的值,要求前端和注解里面的值一致
属性 :
1.value : 表示该参数的参数名 , 默认值是空字符串
2.required : 表示value这个属性是否是必填项 , 默认值true
3.defaultValue : 如果是空字符串 或者 参数名不匹配 , 或者不包含这个参数名 那么就会触发默认值
@RequestHeader
可以获取请求头里面的参数值 , 使用方法和上面的一致,当然如果想要获取全部请求头,可以封装在一个map中
@CookieValue
用来获取cookie的一个注解,每当第一次发送会话请求后,响应域就会返回一个cookieid , 通过cookieid可以获取到当前的session对象,需要注意的是不一定cookie的参数值就是JSESSIONID根据实际而定
注意 : 以上的三个注解拥有三个一样的属性值
演示 : 通过@RequestParam
@RequestMapping("/getUserParametersByAnnotation")
public User getUserParametersByAnnotation(@RequestParam("id")Integer id , @RequestParam("name")String name , @RequestParam("age")String age){
return new User(id , name , age);
}
演示 : 通过@RequestHeader获取请求头,只要知道请求头里面的名字就可以根据名字获取对应的值
@RequestMapping("getUserParametersByHeader")
public String getUserParametersByHeader(@RequestHeader("Accept")String content){
return content;
}
方式三、使用DispatcherServlet自动绑定参数的形式进行参数注入
数据绑定是 Spring MVC 的一项重要功能,它允许将请求中的参数自动绑定到 Java 对象的属性上,从而省去了手动解析参数的步骤。Spring MVC 使用适配器(HandlerAdapter)来执行数据绑定操作。
具体步骤如下:
1.用户提交表单,浏览器向服务器发送 HTTP 请求。
2Spring MVC 的 DispatcherServlet 接收到请求,根据请求的 URL 查找对应的处理器(Controller)。
3找到匹配的处理器后,DispatcherServlet 通过适配器(HandlerAdapter)调用处理器的方法来处理请求。
4当处理器方法被调用时,Spring MVC 会根据方法的参数列表和请求中的参数自动进行数据绑定。
5Spring MVC 使用数据绑定的规则将请求中的参数映射到方法的参数或 Java 对象的属性上。默认情况下,Spring MVC 支持多种数据绑定方式,包括:
表单参数到方法参数的绑定:将请求中的参数自动绑定到方法的参数上。
表单参数到 Java 对象的绑定:将请求中的参数自动绑定到 Java 对象的属性上。
路径变量绑定:将 URL 中的路径变量自动绑定到方法的参数上。
处理器方法执行完成后,Spring MVC 将返回的数据(如视图名称、模型数据等)封装成 HTTP 响应,发送回给浏览器。
@RequestMapping("getUserParameterByDispatcherServlet")
public User getUserParameterByDispatcherServlet(User user){
return user;
}
方式四、使用占位符的方式进行参数注入
通过上面的的DispatcherServlet这种方式,使用参数注入,这个通过表单是无法进行直接进行占位符的方式进行参数注入的
自己提供URL http://localhost:8080/getUserParameterByPathParameter/1/张三/男
@RequestMapping("getUserParameterByPathParameter/{id}/{name}/{age}")
public User getUserParameterByPathParameter(@PathVariable("id")Integer id , @PathVariable("name")String name , @PathVariable("age")String age){
return new User(id ,name , age);
}
2.关于参数处理的实现原理
第一步 : protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
// 省略
.
.
.
// 上文也提到过DispatcherServlet 通过适配器(HandlerAdapter)调用处理器的方法来处理请求。
HandlerAdapter ha = this.getHandlerAdapter(mappedHandler.getHandler());
// 省略
.
.
.
// 这里用处理器进行处理
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
// 省略
.
.
.
}
第二步:点进上文提到过的getHandlerAdapter这个方法进行查看
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");
}
在这里进行查找合适的处理器适配器来进行方法处理,这里有四个处理器适配器
this.handlerAdapters = {ArrayList@6214} size = 4
0 = {RequestMappingHandlerAdapter@6958}
1 = {HandlerFunctionAdapter@6959}
2 = {HttpRequestHandlerAdapter@6960}
3 = {SimpleControllerHandlerAdapter@6961}
返回合适的处理器适配器
第三步:通过上文选择合适的处理器适配器来进行处理mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
@Nullable
public final ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
// 在这里选择的方法选择合适的模型和视图,关于这里在springmvc中已经提到了
return this.handleInternal(request, response, (HandlerMethod)handler);
}
第四步:点进这个方法this.handleInternal(request, response, (HandlerMethod)handler);
protected ModelAndView handleInternal(HttpServletRequest request, HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {
this.checkRequest(request);
ModelAndView mav;
// 不管怎么样都会调用方法处理器来对当前的请求和响应进行处理
if (this.synchronizeOnSession) {
HttpSession session = request.getSession(false);
// 这是为了怕session中有数据而不能合理的处理
if (session != null) {
Object mutex = WebUtils.getSessionMutex(session);
synchronized(mutex) {
mav = this.invokeHandlerMethod(request, response, handlerMethod);
}
} else {
mav = this.invokeHandlerMethod(request, response, handlerMethod);
}
} else {
mav = this.invokeHandlerMethod(request, response, handlerMethod);
}
if (!response.containsHeader("Cache-Control")) {
if (this.getSessionAttributesHandler(handlerMethod).hasSessionAttributes()) {
this.applyCacheSeconds(response, this.cacheSecondsForSessionAttributeHandlers);
} else {
this.prepareResponse(response);
}
}
return mav;
}
第五步: mav = this.invokeHandlerMethod(request, response, handlerMethod);
@Nullable
protected ModelAndView invokeHandlerMethod(HttpServletRequest request, HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {
ServletWebRequest webRequest = new ServletWebRequest(request, response);
WebDataBinderFactory binderFactory = this.getDataBinderFactory(handlerMethod);
ModelFactory modelFactory = this.getModelFactory(handlerMethod, binderFactory);
ServletInvocableHandlerMethod invocableMethod = this.createInvocableHandlerMethod(handlerMethod);
if (this.argumentResolvers != null) {
invocableMethod.setHandlerMethodArgumentResolvers(this.argumentResolvers);
}
if (this.returnValueHandlers != null) {
invocableMethod.setHandlerMethodReturnValueHandlers(this.returnValueHandlers);
}
// 这一块是在进行参数设置
invocableMethod.setDataBinderFactory(binderFactory);
invocableMethod.setParameterNameDiscoverer(this.parameterNameDiscoverer);
ModelAndViewContainer mavContainer = new ModelAndViewContainer();
mavContainer.addAllAttributes(RequestContextUtils.getInputFlashMap(request));
modelFactory.initModel(webRequest, mavContainer, invocableMethod);
mavContainer.setIgnoreDefaultModelOnRedirect(this.ignoreDefaultModelOnRedirect);
AsyncWebRequest asyncWebRequest = WebAsyncUtils.createAsyncWebRequest(request, response);
asyncWebRequest.setTimeout(this.asyncRequestTimeout);
WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
asyncManager.setTaskExecutor(this.taskExecutor);
asyncManager.setAsyncWebRequest(asyncWebRequest);
asyncManager.registerCallableInterceptors(this.callableInterceptors);
asyncManager.registerDeferredResultInterceptors(this.deferredResultInterceptors);
if (asyncManager.hasConcurrentResult()) {
Object result = asyncManager.getConcurrentResult();
mavContainer = (ModelAndViewContainer)asyncManager.getConcurrentResultContext()[0];
asyncManager.clearConcurrentResult();
LogFormatUtils.traceDebug(this.logger, (traceOn) -> {
String formatted = LogFormatUtils.formatValue(result, !traceOn);
return "Resume with async result [" + formatted + "]";
});
invocableMethod = invocableMethod.wrapConcurrentResult(result);
}
// 方法处理器
invocableMethod.invokeAndHandle(webRequest, mavContainer, new Object[0]);
return asyncManager.isConcurrentHandlingStarted() ? null : this.getModelAndView(mavContainer, modelFactory, webRequest);
}
第六步:点进去看这个方法处理器怎么执行的
public void invokeAndHandle(ServletWebRequest webRequest, ModelAndViewContainer mavContainer, Object... providedArgs) throws Exception {
// 这个用于请求的参数按照处理器方法的参数列表进行数据绑定,即将请求参数自动映射到处理器方法的参数上
Object returnValue = this.invokeForRequest(webRequest, mavContainer, providedArgs);
省略
.
.
.
}
第七步:点进这个方法
public Object invokeForRequest(NativeWebRequest request, @Nullable ModelAndViewContainer mavContainer, Object... providedArgs) throws Exception {
// 获取参数值
Object[] args = this.getMethodArgumentValues(request, mavContainer, providedArgs);
if (logger.isTraceEnabled()) {
logger.trace("Arguments: " + Arrays.toString(args));
}
// 执行这个方法调度器,将参数放进去
return this.doInvoke(args);
}
第八步:点进这个doInvoke()
protected Object doInvoke(Object... args) throws Exception {
// 获取方法
Method method = this.getBridgedMethod();
try {
// 调用method.invoke(this.getBean(), args)进行参数处理
return KotlinDetector.isSuspendingFunction(method) ? this.invokeSuspendingFunction(method, this.getBean(), args) : method.invoke(this.getBean(), args);
} catch (IllegalArgumentException var8) {
this.assertTargetBean(method, this.getBean(), args);
String text = var8.getMessage() != null && !(var8.getCause() instanceof NullPointerException) ? var8.getMessage() : "Illegal argument";
throw new IllegalStateException(this.formatInvokeError(text, args), var8);
} catch (InvocationTargetException var9) {
Throwable targetException = var9.getCause();
if (targetException instanceof RuntimeException runtimeException) {
throw runtimeException;
} else if (targetException instanceof Error error) {
throw error;
} else if (targetException instanceof Exception exception) {
throw exception;
} else {
throw new IllegalStateException(this.formatInvokeError("Invocation failure", args), targetException);
}
}
}
第九步:点金这个method.invoke(this.getBean() ,args);
public Object invoke(Object obj, Object... args)
throws IllegalAccessException, IllegalArgumentException,
InvocationTargetException
{
if (!override) {
Class<?> caller = Reflection.getCallerClass();
checkAccess(caller, clazz,
Modifier.isStatic(modifiers) ? null : obj.getClass(),
modifiers);
}
MethodAccessor ma = methodAccessor; // read volatile
if (ma == null) {
ma = acquireMethodAccessor();
}
// 方法执行,方法执行器,类似于jdk动态代理,这地下就用的代理模式,完全交给DelegatingMethodAccessorImpl这个类来进行的,点进去可以看到这个类里面就是类似于动态代理
return ma.invoke(obj, args);
}