源码分析技巧
源码分析的关键点
栈中方法的调用 + 方法调用图 + 类图 + debug方法栈
类图
方法调用
调用栈 + 断点
找到栈中进入源码的第一个方法,依次进入下一个方法,分析需要什么,执行了什么
源码分析常见单词
源码中常见的英语单词一定要查,早晚要查,知道意思对看源码很有帮助
Candidate 候选的
Wrapper 包装器
Customizer 定制器 RedisCacheManagerBuilderCustomizer
其他技巧
idea 方法标识
根据源码自定义实现
几种方式
自定义 bean 注入
实现源码中的接口
自定义 redis cache 实现
关键部分
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(RedisConnectionFactory.class)
@AutoConfigureAfter(RedisAutoConfiguration.class)
@ConditionalOnBean(RedisConnectionFactory.class)
@ConditionalOnMissingBean(CacheManager.class)
@Conditional(CacheCondition.class)
class RedisCacheConfiguration {
@Bean
//可以自定义个 RedisCacheManager 注入,需要的代码直接 copy 源码
// 不是 自定义 这里方法的参数 都可以实现自定义,具体要看逻辑
RedisCacheManager cacheManager(CacheProperties cacheProperties, CacheManagerCustomizers cacheManagerCustomizers,
ObjectProvider<org.springframework.data.redis.cache.RedisCacheConfiguration> redisCacheConfiguration,
ObjectProvider<RedisCacheManagerBuilderCustomizer> redisCacheManagerBuilderCustomizers,
RedisConnectionFactory redisConnectionFactory, ResourceLoader resourceLoader) {
RedisCacheManagerBuilder builder = RedisCacheManager.builder(redisConnectionFactory).cacheDefaults(
determineConfiguration(cacheProperties, redisCacheConfiguration, resourceLoader.getClassLoader()));
List<String> cacheNames = cacheProperties.getCacheNames();
if (!cacheNames.isEmpty()) {
builder.initialCacheNames(new LinkedHashSet<>(cacheNames));
}
//这里有个接口 RedisCacheManagerBuilderCustomizer ,可以实现这个接口自定义
redisCacheManagerBuilderCustomizers.orderedStream().forEach((customizer) -> customizer.customize(builder));
return cacheManagerCustomizers.customize(builder.build());
}
自定义 bean 注入
@Configuration
public class CacheConfig{
@Bean
public RedisCacheConfiguration cacheConfiguration() {
return RedisCacheConfiguration.defaultCacheConfig()
.entryTtl(Duration.ofMinutes(60))
.disableCachingNullValues()
.serializeValuesWith(SerializationPair.fromSerializer(new GenericJackson2JsonRedisSerializer()));
}
}
@Configuration
public class CacheConfig{
@Bean
public RedisCacheManagerBuilderCustomizer redisCacheManagerBuilderCustomizer() {
//这里相当于自定义实现了一个 RedisCacheManagerBuilderCustomizer
return (builder) -> builder
.withCacheConfiguration(DemoConstant.STATE_KEY_FORMAT,
RedisCacheConfiguration.defaultCacheConfig()
.entryTtl(Duration.ofMinutes(5))
.disableCachingNullValues()
//变双冒号为单冒号
.computePrefixWith(name -> name + ":")
.serializeValuesWith(SerializationPair.fromSerializer(new Jackson2JsonRedisSerializer<>(UserStateInfo.class))));
}
}
实现源码中的接口
@Configuration
public class CacheConfig implements RedisCacheManagerBuilderCustomizer{
@Override
public void customize(RedisCacheManager.RedisCacheManagerBuilder builder) {
builder.withCacheConfiguration(DemoConstant.STATE_KEY_FORMAT,
RedisCacheConfiguration.defaultCacheConfig()
.entryTtl(Duration.ofMinutes(5))
.disableCachingNullValues()
//变双冒号为单冒号
.computePrefixWith(name -> name + ":")
.serializeValuesWith(SerializationPair.fromSerializer(new Jackson2JsonRedisSerializer<>(UserStateInfo.class))));
}
}
springboot请求映射源码分析
web应用访问一个请求路径,一定会从DispatcherServlet类中的doDispatcher方法开始
相关代码
前端欢迎页index.html
index.html放在 resources/static文件夹,即可作为欢迎页。注意不要自定义配置静态资源的访问前缀。否则导致 index.html不能被默认访问
spring:
# mvc:
# static-path-pattern: /res/** 这个会导致welcome page功能失效
resources:
static-locations: [classpath:/haha/]
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<h1>韩书哲,永远的韩书哲</h1>
<p>测试Rest风格</p>
<form action="/user" method="get">
<input value="Rest-get 提交" type="submit">
</form>
<form action="/user" method="post">
<input value="Rest-post 提交" type="submit">
</form>
<form action="/user" method="post">
<input name="_m" type="hidden" value="delete">
<input value="Rest-delete 提交" type="submit">
</form>
<form action="/user" method="post">
<input name="_m" type="hidden" value="put">
<input value="Rest-put 提交" type="submit">
</form>
</body>
</html>
MyConfig
@Configuration(proxyBeanMethods = false)
public class MyConfig {
@Bean
public HiddenHttpMethodFilter hiddenHttpMethodFilter(){
HiddenHttpMethodFilter hiddenHttpMethodFilter = new HiddenHttpMethodFilter();
hiddenHttpMethodFilter.setMethodParam("_m");
return hiddenHttpMethodFilter;
}
}
controller
@RestController
@RequestMapping("/user")
public class HelloController {
@GetMapping
public String get(){
return "get";
}
@PostMapping
public String post(){
return "post";
}
@PutMapping
public String put(){
return "put";
}
@DeleteMapping
public String delete(){
return "delete";
}
}
源码分析
springboot 底层还是 springmvc,所有的请求都会到 DispatcherServlet,它是所有请求的开始,打开 DispatcherServlet 的继承结构
一个Servlet必然会有doGet,doPost方法,我们在父类HttpServlet中找doGet,doPost方法,commend + F12 搜索方法
HttpServletBean中找doGet,doPost方法,发现没有,那么HttpServletBean的子类们可能重写了,在FrameworkServlet中找到了重写的方法
注意:向上的箭头表示重写,水平向右的箭头表示直接继承没有重写
再到底层DispatcherServlet中找,dispatcherServlet也没有重写,是直接使用的父类FrameworkServlet的doget方法
发现最底层FrameworkServlet的doget方法调用的是processRequest方法,点进去
发现核心的部分是doService,点进去
发现是一个抽象方法,没有实现,去子类里找实现方法,在子类中找到了实现方法
点进核心方法doDispatcher,才是真正有功能的方法,然后我们给这个方法打断点,再启动项目调试
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
HttpServletRequest processedRequest = request;//原生request
HandlerExecutionChain mappedHandler = null;//执行链
boolean multipartRequestParsed = false;//是不是一个文件上传请求,默认false
WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);//请求有没有异步
try {
try {
ModelAndView mv = null;
Object dispatchException = null;
try {
processedRequest = this.checkMultipart(request);//检查是否文件上传请求
multipartRequestParsed = processedRequest != request;
// 找到当前请求使用哪个Handler(Controller的方法)处理
mappedHandler = this.getHandler(processedRequest);
if (mappedHandler == null) {
this.noHandlerFound(processedRequest, response);
return;
}
HandlerAdapter ha = this.getHandlerAdapter(mappedHandler.getHandler());
String method = request.getMethod();
boolean isGet = HttpMethod.GET.matches(method);
if (isGet || HttpMethod.HEAD.matches(method)) {
long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
if ((new ServletWebRequest(request, response)).checkNotModified(lastModified) && isGet) {
return;
}
}
if (!mappedHandler.applyPreHandle(processedRequest, response)) {
return;
}
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
if (asyncManager.isConcurrentHandlingStarted()) {
return;
}
this.applyDefaultViewName(processedRequest, mv);
mappedHandler.applyPostHandle(processedRequest, response, mv);
} catch (Exception var20) {
dispatchException = var20;
} catch (Throwable var21) {
dispatchException = new NestedServletException("Handler dispatch failed", var21);
}
this.processDispatchResult(processedRequest, response, mappedHandler, mv, (Exception)dispatchException);
} catch (Exception var22) {
this.triggerAfterCompletion(processedRequest, response, mappedHandler, var22);
} catch (Throwable var23) {
this.triggerAfterCompletion(processedRequest, response, mappedHandler, new NestedServletException("Handler processing failed", var23));
}
} finally {
if (asyncManager.isConcurrentHandlingStarted()) {
if (mappedHandler != null) {
mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);
}
} else if (multipartRequestParsed) {
this.cleanupMultipart(processedRequest);
}
}
}
总结
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
protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
if (this.handlerMappings != null) {
for (HandlerMapping mapping : this.handlerMappings) {
HandlerExecutionChain handler = mapping.getHandler(request);
if (handler != null) {
return handler;
}
}
}
return null;
}