《从零开始的架构探险》归纳总结
前言
去年根据这本书实现了一个基本的SpringMVC框架,作者使用SpingMVC中依赖反转和AOP这两个核心概念用自己的理解写了一个框架。个人觉得对于SpringMVC的源码阅读是非常有帮助,现在再次回顾所学的内容
起步
需求分析
框架的最终目的是为了解决现实中不断出现的某些问题,比如频繁的创建对象
- 用户进入“客户管理”模块,列表展示所有用户
- 通过”用户名称”关键字查询
- 单击客户列表客户名称链接,可以查看客户基本信息
- 单击“新增”按钮,进入“新增客户界面”, 可以增加用户信息
- 单击“编辑”按钮,进入“编辑客户界面”, 可以编辑当前用户信息
- 单击“删除”按钮,可以删除当前用户,需要给出提示
依照需求我们分析出基本用户信息为:
- 名称
- 联系人
- 电话号码
- 邮箱
- 备注
设计接口
GET: /customer
进入客户列表页面
POST:/customer_search
查找对应用户
GET: /customer_show?id={id}
查看用户
GET:/customer_create
创建用户页面
POST:/customer_create
创建用户操作
GET:/customer_eidt?id={id}
修改用户页面
POST:/customer_eidt?id={id}
修改用户操作
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的地址如下:
项目结构
工具类
注解
Bean
框架处理工具
IOC实现思路
不是由用户去new 对象,而是在初始化的时候框架拿到所有的controller, 根据controller 中扫描到 的所有的inject 注解,根据此注解拿到所有的服务类信息
最简单的方式
- 通过
beanHelper
获取所有的Bean Map
- 遍历映射关系,分别取出bean类与Bean 实例,进而通过反射获取类中所有的成员变量
- 遍历这些成员变量,查看是否带有@Inject 注解,如果存在,则从
BeanMap
当中取出Bean
实例,
- 最后通过
RelectionUtil#setField
修改当前成员变量的值
如何实现控制层的加载
- 获取所有定义了
Controller
的类
- 反射获取所有加了
Action()
的方法
- 封装一个请求对象和处理对象
- 请求对象和处理对象建立一个映射关系
- 将映射加入
Action Map
请求转发器
- 通过
ControllerHelper
获取Handler
对象
- 获取
Controller
类
BeanHelper
获取所有的Contoller
实体对象
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这一类注解
搭建代理框架
- 使用链式调用的方法生产代理对象
- 需要写一个类,提供创建代理对象的方法,输入一个目标类和一组Proxy, 输出代理对象,用于创建所有的代理对象
- 使用切面来管理所有的代理对象创建,因为切面需要拿到目标方法以及被调用的前后增加逻辑,
- 写一个抽象类,提供模板,具体实现中扩展,定为AspectProxy
- 使用一个日志切面对于代理操作进行增强
加载AOP框架
整个框架使用ProxyManager 创建代理对象
将代理对象放入框架底层的Bean Map,通过IOC将被代理的对象注入到其他对象
- 在BeanHelper 当中增加一个添加Bean实例的方法
- 扩展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()
的判断操作