第四十七章 Spring之假如让你来写MVC——闪存管理器篇
Spring源码阅读目录
第一部分——IOC篇
第一章 Spring之最熟悉的陌生人——IOC
第二章 Spring之假如让你来写IOC容器——加载资源篇
第三章 Spring之假如让你来写IOC容器——解析配置文件篇
第四章 Spring之假如让你来写IOC容器——XML配置文件篇
第五章 Spring之假如让你来写IOC容器——BeanFactory和FactoryBean
第六章 Spring之假如让你来写IOC容器——Scope和属性填充
第七章 Spring之假如让你来写IOC容器——属性填充特别篇:SpEL表达式
第八章 Spring之假如让你来写IOC容器——拓展篇
第九章 Spring之源码阅读——环境搭建篇
第十章 Spring之源码阅读——IOC篇
第二部分——AOP篇
第十一章 Spring之不太熟的熟人——AOP
第十二章 Spring之不得不了解的内容——概念篇
第十三章 Spring之假如让你来写AOP——AOP联盟篇
第十四章 Spring之假如让你来写AOP——雏形篇
第十五章 Spring之假如让你来写AOP——Joinpoint(连接点)篇
第十六章 Spring之假如让你来写AOP——Pointcut(切点)篇
第十七章 Spring之假如让你来写AOP——Advice(通知)上篇
第十八章 Spring之假如让你来写AOP——Advice(通知)下篇
第十九章 Spring之假如让你来写AOP——番外篇:Spring早期设计
第二十章 Spring之假如让你来写AOP——Aspect(切面)篇
第二十一章 Spring之假如让你来写AOP——Weaver(织入器)篇
第二十二章 Spring之假如让你来写AOP——Target Object(目标对象)篇
第二十三章 Spring之假如让你来写AOP——融入IOC容器篇
第二十四章 Spring之源码阅读——AOP篇
第三部分——事务篇
第二十五章 Spring之曾经的老朋友——事务
第二十六章 Spring之假如让你来写事务——初稿篇
第二十七章 Spring之假如让你来写事务——铁三角篇
第二十八章 Spring之假如让你来写事务——属性篇
第二十九章 Spring之假如让你来写事务——状态篇
第三十章 Spring之假如让你来写事务——管理篇
第三十一章 Spring之假如让你来写事务——融入IOC容器篇
第三十二章 Spring之源码阅读——事务篇
第四部分——MVC篇
第三十三章 Spring之梦开始的地方——MVC
第三十四章 Spring之假如让你来写MVC——草图篇
第三十五章 Spring之假如让你来写MVC——映射器篇
第三十六章 Spring之假如让你来写MVC——拦截器篇
第三十七章 Spring之假如让你来写MVC——控制器篇
第三十八章 Spring之假如让你来写MVC——适配器篇
第三十九章 Spring之假如让你来写MVC——番外篇:类型转换
第四十章 Spring之假如让你来写MVC——ModelAndView篇
第四十一章 Spring之假如让你来写MVC——番外篇:数据绑定
第四十二章 Spring之假如让你来写MVC——视图篇
第四十三章 Spring之假如让你来写MVC——上传文件篇
第四十四章 Spring之假如让你来写MVC——异常处理器篇
第四十五章 Spring之假如让你来写MVC——国际化篇
第四十六章 Spring之假如让你来写MVC——主题解析器篇
第四十七章 Spring之假如让你来写MVC——闪存管理器篇
第四十八章 Spring之假如让你来写MVC——请求映射视图篇
第四十九章 Spring之假如让你来写MVC——番外篇:属性操作
第五十章 Spring之假如让你来写MVC——融入IOC容器篇
第五十一章 Spring之源码阅读——MVC篇
文章目录
前言
对于Spring一直都是既熟悉又陌生,说对它熟悉吧,平时用用没啥问题,但面试的时候被问的一脸懵逼,就很尴尬,都不好意思在简历上写着熟悉Spring了
所以决定花点时间研究研究Spring的源码。主要参考的书籍是:《Spring源码深度解析(第2版)》、《Spring揭秘》、《Spring技术内幕:深入解析Spring架构与设计原理(第2版)》
书接上回,在上篇 第四十六章 Spring之假如让你来写MVC——主题解析器篇 中,A君 已经完成了 主题解析器 部分的功能了。接下来看看 A君 会有什么骚操作吧
尝试动手写IOC容器
出场人物:A君(苦逼的开发)、老大(项目经理)
背景:老大 要求 A君在一周内开发个简单的 IOC容器
前情提要:A君 已经完成了 主题解析器 部分的功能了 。。。
第四十一版 闪存管理器
“A君,就快结束了,今天在完成一个组件吧。” 老大 说道
“啊,还没玩啊,还有啥组件?” A君 都快崩溃了
“哈哈哈,不多了,就剩两个组件了。” 老大 笑道
“那今天是什么?” A君 问道
“就是重定向和转发,转发过程中需要存储的数据,去渲染视图。” A君 问道
“那要如何实现呢?” A君 疑惑道
“其实也不难,保留在内存中就行了,等过期后在失效掉。不过,看了下代码,你之前似乎并未实现 重定向视图,这部分内容你也补充下吧!” 老大 解释道
“好吧。” A君 无奈应到,只能默默回去干活了
闪存数据结构
走出办公室后,A君 径直回到了自己的工位上,开始琢磨 闪存 的事情了。按照 老大 的说法,闪存 存在着过期时间,那就不能是简单的Map
能解决的,除了过期时间,闪存 也可能存在着一个key
,多个value
的情况,如:复选框。同时也可以根据目标请求路径存储数据。基于上述这么多情况,自然就得定义个单独的类来进行处理了。A君 新增FlashMap
类,代码如下:
/**
* 闪存
*/
public final class FlashMap extends HashMap<String, Object> implements Comparable<FlashMap> {
private final MultiValueMap<String, String> targetRequestParams = new LinkedMultiValueMap<>(3);
/**
* 目标请求路径
*/
private String targetRequestPath;
/**
* 过期时间
*/
private long expirationTime = -1;
public void startExpirationPeriod(int timeToLive) {
this.expirationTime = System.currentTimeMillis() + timeToLive * 1000;
}
public FlashMap addTargetRequestParams(MultiValueMap<String, String> params) {
if (params != null) {
params.forEach((key, values) -> {
for (String value : values) {
addTargetRequestParam(key, value);
}
});
}
return this;
}
public FlashMap addTargetRequestParam(String name, String value) {
if (StringUtils.hasText(name) && StringUtils.hasText(value)) {
this.targetRequestParams.add(name, value);
}
return this;
}
public MultiValueMap<String, String> getTargetRequestParams() {
return this.targetRequestParams;
}
public boolean isExpired() {
return (this.expirationTime != -1 && System.currentTimeMillis() > this.expirationTime);
}
@Override
public int compareTo(FlashMap other) {
int thisUrlPath = (this.targetRequestPath != null ? 1 : 0);
int otherUrlPath = (other.targetRequestPath != null ? 1 : 0);
if (thisUrlPath != otherUrlPath) {
return otherUrlPath - thisUrlPath;
} else {
return other.targetRequestParams.size() - this.targetRequestParams.size();
}
}
//省略其他代码。。。
}
闪存管理器
闪存数据结构 定义好之后,接着就是如何管理它了,直接操作FlashMap
对象也不是不行,但是毕竟还是太过麻烦了,也不好管理。与其如此,不如直接定义个管理者,让它进行统一操作。管理器也简单,就干两件事:增加、更新。A君 新增FlashMapManager
接口,代码如下:
/**
* 闪存管理器
*/
public interface FlashMapManager {
/**
* 清除闪存
*
* @param request
* @param response
* @return
*/
FlashMap retrieveAndUpdate(HttpServletRequest request, HttpServletResponse response);
/**
* 保存闪存
*
* @param flashMap
* @param request
* @param response
*/
void saveOutputFlashMap(FlashMap flashMap, HttpServletRequest request, HttpServletResponse response);
}
接下来就是抽象类了,哪些可以提取出来呢?A君 开动脑瓜开始思考:清理要怎么清理?保存要怎么保存?
-
清理:首当其冲的自然是过期数据了,这个不用怀疑。还有就是每个
url
需要保留多少数据?对于一次请求来说,请求结束也意味着这些数据已经失效,其生命周期很短,这就意味着 闪存 不可能存储很多数据。那就定一个规则:每次都拉出一个倒霉鬼进行清理,目标url
为空的优先,参数多的优先谁叫他占内存呢(笑)? -
保存:这就更简单了,获取已有数据和现有数据合并即可了
思绪整理完毕后,A君 新增AbstractFlashMapManager
类,代码如下:
public abstract class AbstractFlashMapManager implements FlashMapManager {
private static final Object DEFAULT_FLASH_MAPS_MUTEX = new Object();
/**
* 180s
*/
private int flashMapTimeout = 180;
private UrlPathHelper urlPathHelper = UrlPathHelper.defaultInstance;
@Override
public final FlashMap retrieveAndUpdate(HttpServletRequest request, HttpServletResponse response) {
List<FlashMap> allFlashMaps = retrieveFlashMaps(request);
if (CollectionUtils.isEmpty(allFlashMaps)) {
return null;
}
/**
*获取过期数据
*/
List<FlashMap> mapsToRemove = getExpiredFlashMaps(allFlashMaps);
/**
* targetPath、参数多的会优先被清理
*/
FlashMap match = getMatchingFlashMap(allFlashMaps, request);
if (match != null) {
mapsToRemove.add(match);
}
if (!mapsToRemove.isEmpty()) {
Object mutex = getFlashMapsMutex(request);
if (mutex != null) {
synchronized (mutex) {
//清除数据
allFlashMaps = retrieveFlashMaps(request);
if (allFlashMaps != null) {
allFlashMaps.removeAll(mapsToRemove);
updateFlashMaps(allFlashMaps, request, response);
}
}
} else {
allFlashMaps.removeAll(mapsToRemove);
updateFlashMaps(allFlashMaps, request, response);
}
}
return match;
}
@Override
public final void saveOutputFlashMap(FlashMap flashMap, HttpServletRequest request, HttpServletResponse response) {
if (CollectionUtils.isEmpty(flashMap)) {
return;
}
/**
* 修正地址
*/
String path = decodeAndNormalizePath(flashMap.getTargetRequestPath(), request);
flashMap.setTargetRequestPath(path);
//设置开始时间
flashMap.startExpirationPeriod(getFlashMapTimeout());
/**
* 获取对象锁
*/
Object mutex = getFlashMapsMutex(request);
if (mutex != null) {
synchronized (mutex) {
/**
* 获取老数据
*/
List<FlashMap> allFlashMaps = retrieveFlashMaps(request);
allFlashMaps = (allFlashMaps != null ? allFlashMaps : new CopyOnWriteArrayList<>());
/**
* 添加并更新数据
*/
allFlashMaps.add(flashMap);
updateFlashMaps(allFlashMaps, request, response);
}
} else {
List<FlashMap> allFlashMaps = retrieveFlashMaps(request);
allFlashMaps = (allFlashMaps != null ? allFlashMaps : new ArrayList<>(1));
allFlashMaps.add(flashMap);
updateFlashMaps(allFlashMaps, request, response);
}
}
protected abstract List<FlashMap> retrieveFlashMaps(HttpServletRequest request);
protected abstract void updateFlashMaps(
List<FlashMap> flashMaps, HttpServletRequest request, HttpServletResponse response);
//省略其他代码。。。
}
抽象类已经把大部分逻辑提取出来了,剩下的部分就是从哪里获取数据?从哪里更新数据?这个有可能是Cookie
,也有可能是Session
。老样子,这里还是以Session
为例,这个很简单,直接从Session
中的获取值就行了。A君 新增SessionFlashMapManager
类,代码如下:
public class SessionFlashMapManager extends AbstractFlashMapManager {
private static final String FLASH_MAPS_SESSION_ATTRIBUTE = SessionFlashMapManager.class.getName() + ".FLASH_MAPS";
@Override
protected List<FlashMap> retrieveFlashMaps(HttpServletRequest request) {
HttpSession session = request.getSession(false);
return (session != null ? (List<FlashMap>) session.getAttribute(FLASH_MAPS_SESSION_ATTRIBUTE) : null);
}
@Override
protected void updateFlashMaps(List<FlashMap> flashMaps, HttpServletRequest request, HttpServletResponse response) {
WebUtils.setSessionAttribute(request, FLASH_MAPS_SESSION_ATTRIBUTE, (!flashMaps.isEmpty() ? flashMaps : null));
}
@Override
protected Object getFlashMapsMutex(HttpServletRequest request) {
return WebUtils.getSessionMutex(request.getSession());
}
}
重定向视图
闪存管理器 是定义好了,可是在哪里用呢?A君 不经一阵挠头。总不能在DispatcherServlet
中弄吧。又思索了一阵,A君 忽然发现,不论 重定向 还是 转发,其实都是在返回值里边定义,转发 还好说,直接通过分发器就行了,接着就是 重定向 了,不如定一个 视图 来进行处理。实现是解决了,但是还有个问题:这个 视图 要干什么?A君 又想了下,所谓 重定向 无非就是uri
的定义罢了。重定向 到哪里?要不要带参数?要带哪些参数?这个就是 重定向视图 要处理的重点了。好啦,一切都敲定了。A君 新增RedirectView
类,代码如下:
/**
* 重定向视图
*/
public class RedirectView extends AbstractUrlBasedView implements SmartView {
/**
* 是否是绝对路径
*/
private boolean contextRelative = false;
/**
* 是否兼容 HTTP/1.0
*/
private boolean http10Compatible = true;
/**
* 暴露模型属性
*/
private boolean exposeModelAttributes = true;
/**
* URL 编码
*/
private String encodingScheme;
/**
* 响应码
*/
private HttpStatus statusCode;
/**
* 是否将当前请求的查询参数传递到目标 URL
*/
private boolean propagateQueryParams = false;
/**
* 主机名
*/
private String[] hosts;
@Override
protected void renderMergedOutputModel(Map<String, Object> model, HttpServletRequest request,HttpServletResponse response) throws IOException {
String targetUrl = createTargetUrl(model, request);
targetUrl = updateTargetUrl(targetUrl, model, request, response);
//保存到闪存中
RequestContextUtils.saveOutputFlashMap(targetUrl, request, response);
// 重定向
sendRedirect(request, response, targetUrl, this.http10Compatible);
}
/**
* 构建重定向url
*
* @param model
* @param request
* @return
* @throws UnsupportedEncodingException
*/
protected final String createTargetUrl(Map<String, Object> model, HttpServletRequest request)
throws UnsupportedEncodingException {
StringBuilder targetUrl = new StringBuilder();
if (this.contextRelative && getUrl().startsWith("/")) {
targetUrl.append(getContextPath(request));
}
targetUrl.append(getUrl());
String enc = this.encodingScheme;
/**
* 编码为空,使用请求编码
*/
if (enc == null) {
enc = request.getCharacterEncoding();
}
/**
* 请求编码还是为空,使用默认编码
*/
if (enc == null) {
enc = WebUtils.DEFAULT_CHARACTER_ENCODING;
}
/**
* 是否追加参数
*/
if (isPropagateQueryProperties()) {
appendCurrentQueryParams(targetUrl, request);
}
/**
* 是否暴露模型数据
*/
if (this.exposeModelAttributes) {
appendQueryProperties(targetUrl, model, enc);
}
return targetUrl.toString();
}
//省略其他方法。。。
}
这个类基本上就是在构建url
,拼接参数
修改UrlBasedViewResolver
之前在写UrlBasedViewResolver
时候,A君 并没有支持 重定向、转发,这回就可以加上了。改动的点也比较简单,在渲染的时候,做个判断就行了。A君 修改createView
方法,改动如下:
@Override
protected View createView(String viewName, Locale locale) throws Exception {
/**
* 是否能够处理
*/
if (!canHandle(viewName, locale)) {
return null;
}
/**
* 重定向
*/
if (viewName.startsWith(REDIRECT_URL_PREFIX)) {
String redirectUrl = viewName.substring(REDIRECT_URL_PREFIX.length());
RedirectView view = new RedirectView(redirectUrl,
isRedirectContextRelative(), isRedirectHttp10Compatible());
String[] hosts = getRedirectHosts();
if (hosts != null) {
view.setHosts(hosts);
}
return applyLifecycleMethods(REDIRECT_URL_PREFIX, view);
}
/**
* 转发
*/
if (viewName.startsWith(FORWARD_URL_PREFIX)) {
String forwardUrl = viewName.substring(FORWARD_URL_PREFIX.length());
InternalResourceView view = new InternalResourceView(forwardUrl);
return applyLifecycleMethods(FORWARD_URL_PREFIX, view);
}
return super.createView(viewName, locale);
}
修改DispatcherServlet
现在组件基本准备好了,既然是组件,那么DispatcherServlet
就当之无愧了。首先需要的是初始化该组件,A君 新增initFlashMapManager
方法,代码如下:
protected void initStrategies() {
/**
*1.初始化文件上传
*/
initMultipartResolver();
/**
* 2.初始化国际化
*/
initLocaleResolver();
/**
* 3.初始化主题
*/
initThemeResolver();
/**
* 4.初始化url处理器
*/
initHandlerMappings();
/**
* 5.初始化适配器
*/
initHandlerAdapters();
/**
* 6.初始化异常处理
*/
initHandlerExceptionResolvers();
/**
* 初始化视图
*/
initViewResolvers();
/**
* 初始化闪存管理器
*/
initFlashMapManager();
}
private void initFlashMapManager() {
this.flashMapManager = new SessionFlashMapManager();
}
还有一个问题:谁去清理 闪存?谁去创建 闪存 对象?当下 A君 对doService
方法进行修改,改动如下:
protected void doService(HttpServletRequest request, HttpServletResponse response) throws Exception {
Map<String, Object> attributesSnapshot = null;
if (WebUtils.isIncludeRequest(request)) {
//清理include
attributesSnapshot = new HashMap<>();
Enumeration<?> attrNames = request.getAttributeNames();
while (attrNames.hasMoreElements()) {
String attrName = (String) attrNames.nextElement();
if (this.cleanupAfterInclude || attrName.startsWith(DEFAULT_STRATEGIES_PREFIX)) {
attributesSnapshot.put(attrName, request.getAttribute(attrName));
}
}
}
//设置国际化处理器
request.setAttribute(LOCALE_RESOLVER_ATTRIBUTE, this.localeResolver);
//设置主题处理器
request.setAttribute(THEME_RESOLVER_ATTRIBUTE, this.themeResolver);
/**
* 存在闪存管理器
*/
if (this.flashMapManager != null) {
FlashMap inputFlashMap = this.flashMapManager.retrieveAndUpdate(request, response);
if (inputFlashMap != null) {
/**
* 已存在,转成不可修改的
*/
request.setAttribute(INPUT_FLASH_MAP_ATTRIBUTE, Collections.unmodifiableMap(inputFlashMap));
}
//不存在,建立新的缓存
request.setAttribute(OUTPUT_FLASH_MAP_ATTRIBUTE, new FlashMap());
request.setAttribute(FLASH_MAP_MANAGER_ATTRIBUTE, this.flashMapManager);
}
doDispatch(request, response);
}
就新增了对于 闪存 的处理,并无太多东西
测试
好了,现在一切都完成了,又可以开始愉快地测试了。这次测试主要就集中在 控制器 上,其他地方基本就不用改动。A君 新增HelloController
代码:
@Controller
public class HelloController {
// 首页视图
@RequestMapping("/index")
public String home(Model model) {
// 将 Flash 属性添加到重定向的请求中
model.addAttribute("message", "Form submitted successfully!");
return "redirect:hello";
}
@RequestMapping("/hello")
public String hello(String message, Model model) {
model.addAttribute("message", message);
return "hello";
}
}
添加一个hello.jsp
,代码如下:
<%@page isELIgnored="false"%>
<html>
<body>
<h2>This message is: ${requestScope.message}</h2>
</body>
</html>
然后就可以添加测试代码,如下:
@Test
public void v41() throws LifecycleException {
System.out.println("############# 第四十一版: 闪存管理器篇 #############");
Tomcat tomcat = new Tomcat();
//设置端口
tomcat.setPort(8082);
//设置静态资源路径
String webApp = new File("src/main/resources/v41").getAbsolutePath();
Context context = tomcat.addWebapp("/test/", webApp);
tomcat.start();
//挂起
tomcat.getServer().await();
}
测试结果如下:
可以看到,经过 重定向 后,url
后边也拼上参数。OK,现在 A君 也可以下班了
总结
正所谓树欲静而风不止,欲知后事如何,请看下回分解(✪ω✪)
原文地址:https://blog.csdn.net/weixin_42789334/article/details/144970305
免责声明:本站文章内容转载自网络资源,如侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!