爱看书的阿东

赐他一块白色石头,石头上写着新名

《从零开始的架构探险》归纳总结

《从零开始的架构探险》归纳总结

前言

去年根据这本书实现了一个基本的SpringMVC框架,作者使用SpingMVC中依赖反转和AOP这两个核心概念用自己的理解写了一个框架。个人觉得对于SpringMVC的源码阅读是非常有帮助,现在再次回顾所学的内容

起步

需求分析

框架的最终目的是为了解决现实中不断出现的某些问题,比如频繁的创建对象

  1. 用户进入“客户管理”模块,列表展示所有用户
  2. 通过”用户名称”关键字查询
  3. 单击客户列表客户名称链接,可以查看客户基本信息
  4. 单击“新增”按钮,进入“新增客户界面”, 可以增加用户信息
  5. 单击“编辑”按钮,进入“编辑客户界面”, 可以编辑当前用户信息
  6. 单击“删除”按钮,可以删除当前用户,需要给出提示

依照需求我们分析出基本用户信息为:

  1. 名称
  2. 联系人
  3. 电话号码
  4. 邮箱
  5. 备注

设计接口

  1. GET: /customer

    进入客户列表页面

  2. POST:/customer_search

    查找对应用户

  3. GET: /customer_show?id={id}

    查看用户

  4. GET:/customer_create

    创建用户页面

  5. POST:/customer_create

    创建用户操作

  6. GET:/customer_eidt?id={id}

    修改用户页面

  7. POST:/customer_eidt?id={id}

    修改用户操作

  8. GET:/customer_delete?id={id}

    删除操作

准备开发环境:

IntelinJ IDEA

现在破解比较麻烦了,个人建议直接某宝或者某鱼上买个破解码,直接破解到2089年,没有必要浪费时间在工具的破解上面

Maven

使用3.6以上的版本

JDK

使用JDK1.8的版本

Tomcat

使用Tomcat 8.5 的版本

项目地址

具体搭建流程不做演示,这里给出一个github地址作为参考

https://github.com/lazyTimes/smartframwork

这事框架地址,具体的实验放在一个chapter3的项目里面,git的地址如下:

项目结构

工具类

img

注解

image-20200211103431175

Bean

image-20200211131451516

框架处理工具

image-20200211132349636

IOC实现思路

不是由用户去new 对象,而是在初始化的时候框架拿到所有的controller, 根据controller 中扫描到 的所有的inject 注解,根据此注解拿到所有的服务类信息

最简单的方式

  1. 通过beanHelper 获取所有的Bean Map
  2. 遍历映射关系,分别取出bean类与Bean 实例,进而通过反射获取类中所有的成员变量
  3. 遍历这些成员变量,查看是否带有@Inject 注解,如果存在,则从BeanMap当中取出Bean实例,
  4. 最后通过RelectionUtil#setField 修改当前成员变量的值

如何实现控制层的加载

  1. 获取所有定义了Controller的类
  2. 反射获取所有加了Action()的方法
  3. 封装一个请求对象和处理对象
  4. 请求对象和处理对象建立一个映射关系
  5. 将映射加入Action Map

请求转发器

  1. 通过ControllerHelper 获取Handler 对象
  2. 获取Controller
  3. BeanHelper获取所有的Contoller实体对象
  4. HttpServlet获取所有的请求参数

核心代码:

IOC的核心是在框架启动的时候扫描对应的包下面的类注解,并且使用反射的方式对于类进行初始化操作

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
/**
* 类加载工具类
*/
public final class MyClassUtil {

private static final Logger LOGGER = LoggerFactory.getLogger(MyClassUtil.class);


/**
* @Description: 获取类加载器
* @Param: 无参数
* @return: 类加载器
* @Author: zhaoxudong
* @Date: 2019.9.8
*/
public static ClassLoader getClassLoader() {
return Thread.currentThread().getContextClassLoader();
}

/**
* @param className 类名
* @param isInitialized 是否初始化
* @return 实体类
* @Description: 加载class类
* @Author zhaoxudong
* @Date 2019.9.8
*/
public static Class<?> loadClass(String className, boolean isInitialized) {
Class<?> cls;
try {
cls = Class.forName(className, isInitialized, getClassLoader());
} catch (ClassNotFoundException e) {
LOGGER.error("load class failure", e);
throw new RuntimeException(e);
}
return cls;
}


/**
* 获取指定包名下面的所有类
* 最为复杂也是最为核心的代码
*
* @param packageName 包名
*/
public static Set<Class<?>> getClassesSet(String packageName) {
// 1. 创建set集合
Set<Class<?>> result = new HashSet<Class<?>>();
// 2. 获取枚举迭代器,使用递归的方式迭代所有的包
try {
Enumeration<URL> enumeration = getClassLoader().getResources(packageName.replace(".", "/"));
while (enumeration.hasMoreElements()) {
URL url = enumeration.nextElement();
if (null != url) {
// protocol 协议,获取协议
String protocool = url.getProtocol();
// 如果是一个文件
if (protocool.equals("file")) {
// 替换掉特殊符号
String urlPath = url.getPath().replaceAll("%20", "");
addClass(result, urlPath, packageName);
} else if (protocool.equals("jar")) {
// 如果是jar包,获取jar包路径
JarURLConnection jarURLConnection = (JarURLConnection) url.openConnection();
if (null != jarURLConnection) {
// 获取jar包文件
JarFile jarFile = jarURLConnection.getJarFile();
if (null != jarFile) {
Enumeration<JarEntry> entries = jarFile.entries();
// 枚举迭代获取所有的jar包类
while (entries.hasMoreElements()) {
JarEntry jarEntry = entries.nextElement();
// 获取名称
String entryName = jarEntry.getName();
if (entryName.equals(".class")) {
String className = entryName.substring(0, entryName.lastIndexOf("."))
.replaceAll("/", ".");
doAddClass(result, className);
}
}
}
}
}
}
}
} catch (IOException e) {
LOGGER.error("get class set failure", e);
throw new RuntimeException(e);
}
return result;
}

/**
* 将对应类型的文件路径加入到对应的set内部
*
* @param classSet 所有的应用宝
* @param packagePath 文件路径
* @param packageName 包名
*/
private static void addClass(Set<Class<?>> classSet, String packagePath, String packageName) {
//对于文件路径进行过滤操作
File[] files = new File(packagePath).listFiles(new FileFilter() {
public boolean accept(File file) {
// 如果是class 文件或者文件夹,允许
return (file.isFile() && file.getName().endsWith(".class")) || file.isDirectory();
}
});
for (File file : files) {
// 当前遍历目录的文件名
String fileName = file.getName();
// 如果是文件类型
if (file.isFile()) {
String className = fileName.substring(0, fileName.lastIndexOf("."));
if (MyStringUtil.isNotEmpty(className)) {
className = packageName + "." + className;
}
doAddClass(classSet, className);

} else {
// 如果不是文件,则根据路径加载文件
String subPackagePath = fileName;
if (MyStringUtil.isNotEmpty(packagePath)) {
subPackagePath = packagePath + "/" + subPackagePath;
}
String subPackageName = fileName;
if (MyStringUtil.isNotEmpty(packageName)) {
subPackageName = packageName + "." + subPackageName;
}
// 递归获取子包下面的文件
addClass(classSet, subPackagePath, subPackageName);
}
}
}

/**
* 添加类名的统一方法
* 主要为根据包名加载类
*
* @param result 类集合
* @param className 类名
*/
private static void doAddClass(Set<Class<?>> result, String className) {
Class<?> cls = loadClass(className, false);
result.add(cls);
}


}

AOP 实现思路

定义切面注解

value:表示注解类,用来指定controller这一类注解

搭建代理框架

  1. 使用链式调用的方法生产代理对象
  2. 需要写一个类,提供创建代理对象的方法,输入一个目标类和一组Proxy, 输出代理对象,用于创建所有的代理对象
  3. 使用切面来管理所有的代理对象创建,因为切面需要拿到目标方法以及被调用的前后增加逻辑,
    1. 写一个抽象类,提供模板,具体实现中扩展,定为AspectProxy
  4. 使用一个日志切面对于代理操作进行增强

加载AOP框架

整个框架使用ProxyManager 创建代理对象

将代理对象放入框架底层的Bean Map,通过IOC将被代理的对象注入到其他对象

  1. 在BeanHelper 当中增加一个添加Bean实例的方法
  2. 扩展ClassHelper , 增加一个获取某个父类的所有子类或者实现类,以及获取带有某个注解的所有类的方法

获取Aspect 注解中设置的注解类,若该注解不是Aspect 类,则可以调用ClassHelper#getClassSetByAnnotation 获取相关类, 放入目标类集合, 最终返回这些集合

需要将代理类以及目标类集合的映射关系获取,一个代理对应一个或者多个目标类,代理类指定是切面类,通过以下代码或者映射关系

代理类需要扩展AspectProxy抽象类,还需要Aspect注解,只有两者都满足,才能根据Aspect注解定义的注解属性去获取改注解对应的目标类集合,才能建立代理类与目标类的集合,才能返回映射

核心代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
public final class AopHelper {

private static final Logger LOGGER = LoggerFactory.getLogger(AopHelper.class);


static {
try {
Map<Class<?>, Set<Class<?>>> proxyMap = createProxyMap();
Map<Class<?>, List<Proxy>> targetMap = createTargetMap(proxyMap);
for (Map.Entry<Class<?>, List<Proxy>> targetEntity : targetMap.entrySet()) {
Class<?> targetClass = targetEntity.getKey();
List<Proxy> proxyList = targetEntity.getValue();
Object proxy = ProxyManager.createProxy(targetClass, proxyList);
BeanHelper.setBeanMap(targetClass, proxy);
}
} catch (Exception e) {
LOGGER.error("aop failure", e);
}

}


/**
* 获取所有Aspect 目标集合
* @param aspect
* @return
* @throws Exception
*/
private static Set<Class<?>> createTargetClassSet(Aspect aspect) throws Exception{
Set<Class<?>> targetClassSet = new HashSet<Class<?>>();
Class<? extends Annotation> annotation = aspect.value();
if(annotation != null && !annotation.equals(Aspect.class)){
targetClassSet.addAll(ClassHelper.getClassSetByAnnotation(annotation));
}
return targetClassSet;
}

/**
* 创建代理类和目标集合的映射
* @return
* @throws Exception
*/
private static Map<Class<?>, Set<Class<?>>> createProxyMap() throws Exception {
Map<Class<?>, Set<Class<?>>> proxyMap = new HashMap<Class<?>, Set<Class<?>>>();
Set<Class<?>> proxySet = ClassHelper.getClassSetBySuper(AspectProxy.class);
for (Class<?> proxyClass: proxySet) {
if(proxyClass.isAnnotationPresent(Aspect.class)){
Aspect aspect = proxyClass.getAnnotation(Aspect.class);
Set<Class<?>> targetClassSet = createTargetClassSet(aspect);
proxyMap.put(proxyClass, targetClassSet);
}
}
return proxyMap;
}

/**
* 根据代理类和目标集合,分析出目标类和代理对象之间的映射
* @return
*/
private static Map<Class<?>, List<Proxy>> createTargetMap(Map<Class<?>,Set<Class<?>>> proxyMap) throws Exception {
Map<Class<?>,List<Proxy>> targetMap = new HashMap<Class<?>, List<Proxy>>();
for (Map.Entry<Class<?>, Set<Class<?>>> proxyEntity : proxyMap.entrySet()) {
Class<?> proxyClass = proxyEntity.getKey();
Set<Class<?>> targetClassSet = proxyEntity.getValue();
for (Class<?> targetClass: targetClassSet) {
Proxy proxy = (Proxy)proxyClass.newInstance();
if(targetMap.containsKey(targetClass)){
targetMap.get(targetClass).add(proxy);
}else{
List<Proxy> proxyList = new ArrayList<Proxy>();
proxyList.add(proxy);
targetMap.put(targetClass, proxyList);
}
}
}
return targetMap;
}


}

问题汇总:

Spring是如何处理请求路径中的"/"的?

例如:"/hello""hello"效果是一样的

目前做了一个startWith()的判断操作