# MySimpleSpring **Repository Path**: mlming/my-simple-spring ## Basic Information - **Project Name**: MySimpleSpring - **Description**: 从0开始搭建一个具有IOC、AOP、Web等核心功能的简易版Spring自研框架 - **Primary Language**: Java - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2022-05-26 - **Last Updated**: 2022-06-08 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # DefaultAspectAspectListExecutorMySimpleSpring ## 介绍 从0开始搭建一个具有IOC、AOP、Web等核心功能的简易版Spring自研框架 ## Spring核心模块整体感知: ![img](http://img.mukewang.com/szimg/6290b1bb0001992219201080-500-284.jpg) ![img](http://img.mukewang.com/szimg/6290b46f00014e0519201080-500-284.jpg) ![img](http://img.mukewang.com/szimg/6290b4850001c72b19201080-500-284.jpg) ![img](http://img.mukewang.com/szimg/6290b4a90001a8cd19201080-500-284.jpg) ![img](http://img.mukewang.com/szimg/6290b4dc00019b0519201080-500-284.jpg) ![img](http://img.mukewang.com/szimg/6290b5d900019ff019201080-500-284.jpg)![img](http://img.mukewang.com/szimg/6290b5e30001862d19201080-500-284.jpg) ## 自研框架的整体架构: ![img](http://img.mukewang.com/szimg/6290bc54000152af19201080-500-284.jpg) ​ 所以本自研框架是一个基于Servlet实现的**Web项目** ​ 各组件作用如下: ​ IOC: 实现简易版的IOC容器 ​ AOP: 实现简易版的AOP功能 ​ Parser: 用于解析各个配置文件从而配置Bean ​ MVC: 基于Servlet实现的简易版的SpringMVC ## 自研框架雏形: * 概述: 因为上面说了, 本项目是一个基于Servlet实现的**Web项目** 所以我们可以把项目构建为一个Web工程, 并引入Servlet依赖(同时为了更直观测试功能,我们引入JSP来展示成果) 来进行初始化 * 实现: * 构建一个Maven的Web工程 ![image-20220527201659135](\mdImage\image-20220527201659135.png) ![image-20220527202423483](\mdImage\image-20220527202423483.png) * pom文件中引入Servlet与JSP的依赖: ```xml javax.servlet javax.servlet-api 3.1.0 javax.servlet.jsp jsp-api 2.2.1-b03 ``` * 创建一个Servlet类以及一个jsp: 项目目录: ![image-20220527203424381](\mdImage\image-20220527203424381.png) HelloServlet.java: ```java @WebServlet("/hello") public class HelloServlet extends HttpServlet { @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { String name = "Hello SimpleSpring!"; // 设置属性 req.setAttribute("name",name); // 转发到jsp页面 req.getRequestDispatcher("/WEB-INF/jsp/hello.jsp").forward(req,resp); } } ``` hello.jsp: ```java <%@ page contentType="text/html;charset=UTF-8" language="java" %> Title

你好!

${name}

``` * IDEA配置运行环境: ![image-20220527203247591](\mdImage\image-20220527203247591.png) ![image-20220527203305357](\mdImage\image-20220527203305357.png) * 启动项目, 浏览器访问localhost:8080/hello -> HelloServlet -> hello.jsp ![image-20220527203348709](\mdImage\image-20220527203348709.png) * 至此,项目初始化成功! ## 业务系统架子的构建【自研框架的起源】 * 概述: 为了进一步明确我们自研框架的作用, 那我们就得用原生的脱离了Spring框架的JavaWeb方法去实现一个业务系统。 这样依赖,之后我们的自研框架就有了更多的出发点和落足点。 * 业务系统的需求分析与基本设计: * 考虑到这只是一个启发式的小系统,所以我们无需太完善,比如链接数据库等操作可以直接省略, 我们只需要关心业务相关的**模块之间的交互 以及 类的管理** * 需求: ![image-20220527224145765](\mdImage\image-20220527224145765.png) * 由此需求,可以分析出只需要两个表:一个是头条表,一个是商铺表,同时也对应两个实体类。 由于我们此处不会去关心数据库层面的,所以只需要设计两个实体类即可: ![image-20220527224310289](\mdImage\image-20220527224310289.png) * 同时,因为是业务系统,所以我们尽量采取MVC架构来进行开发(其实更多指的是三层架构): * Controller: 接收请求,并调用service层进行业务处理,并响应给前端 * Service:业务处理 * Dao:持久层的相关操作 * Entity:实体类,一般对应数据库表 * 代码实现: * pom.xml文件中引入 slf4j 和 lombok依赖: ```xml org.slf4j slf4j-log4j12 1.7.28 org.projectlombok lombok 1.18.10 ``` * entity包下创建两个实体类: 店铺实体类: ```java /** * 店铺实体类 */ @Data public class ShopCategory { private Long shopCategoryId; private String shopCategoryName; private String shopCategoryDesc; private String shopCategoryImg; private Integer priority; private Date createTime; private Date lastEditTime; private ShopCategory parent; } ``` 头条实体类: ```java /** * 头条实体类 */ @Data public class HeadLine { private Long lineId; private String lineName; private String lineLink; private String lineImg; private Integer priority; private Integer enableStatus; private Date createTime; private Date lastEditTime; } ``` * dto包下,创建用于响应给前端的响应信息的通用类: ```java @Data public class Result { //本次请求结果的状态码,200表示成功 private int code; //本次请求结果的详情 private String msg; //本次请求返回的结果集 private T data; } ``` * dao层: 由于本业务系统不考虑数据库,所以为了省事,就不考虑dao层了 * service包下,对应着业务需求, 创建业务处理对应的Service: 头条相关业务处理的Service: ```java public interface HeadLineService { Result addHeadLine(HeadLine headLine); Result removeHeadLine(int headLineId); Result modifyHeadLine(HeadLine headLine); Result queryHeadLineById(int headLineId); Result>queryHeadLine(HeadLine headLineCondition, int pageIndex, int pageSize); } ``` 店铺相关业务处理的Service: ```java public interface ShopCategoryService { Result addShopCategory(ShopCategory shopCategory); Result removeShopCategory(int shopCategoryId); Result modifyShopCategory(ShopCategory shopCategory); Result queryShopCategoryById(int shopCategoryId); Result> queryShopCategory(ShopCategory shopCategoryCondition, int pageIndex, int pageSize); } ``` 合并两个Service,用于实现返回首页相关信息的Service: ```java public interface HeadLineShopCategoryCombineService { Result getMainPageInfo(); } ``` 再次强调, 由于本项目只关心模块之间的联系以及类的处理, 所以并不会具体实现这些方法, 所以这里就不一一展示对应的impl实现类了, 这里只贴其中一个实现类: HeadLineShopCategoryCombineService的实现类: ```java public class HeadLineShopCategoryCombineServiceImpl implements HeadLineShopCategoryCombineService { private HeadLineService headLineService; // 头条Service private ShopCategoryService shopCategoryService;// 店铺Service @Override public Result getMainPageInfo() { // 1.获取头条列表 HeadLine headLineCondition = new HeadLine(); headLineCondition.setEnableStatus(1); Result> HeadLineResult = headLineService.queryHeadLine(headLineCondition, 1, 4); // 2.获取店铺类别列表 ShopCategory shopCategoryCondition = new ShopCategory(); Result> shopCategoryResult =shopCategoryService.queryShopCategory(shopCategoryCondition, 1, 100); // 3.合并两者并返回 Result result = mergeMainPageInfoResult(HeadLineResult, shopCategoryResult); return result; } private Result mergeMainPageInfoResult(Result> headLineResult, Result> shopCategoryResult) { return null; } } ``` **此处其实就能发现, 我们出现了Service之间的包含关系,即: 一个类有其他类作为其成员变量 ** **此时我们肯定要对他们进行初始化的, 否则就是null, 无法使用,这里由于这个系统的业务简单, 嵌套关系并不深, 只不过是new一下就能解决 ** **如果在稍微大型的项目里面,一个Service用到了多个Service, 一个Service下面可能还有多个dao成员, 那么此时手动去初始化就会变得非常非常复杂** **此刻我们是否需要一个可以帮我们实现自动化注入(初始化), 来帮我们自动管理类与类之间关系 的工具呢? ** * controller层: **此时有一个特别需要注意的点:** **之前开发我们一直用Spring封装好的Controller类, 我们可以随意在Controller里面通过@RequestMapping给各个controller方法指定其对应的请求方法和请求路径, 这样就很容易实现 一个Controller类对应其模块的所有请求 --- 例如: StudentController可以在这个类里面一下子设置所有的请求操作: 请求学生列表,请求单个学生信息, 修改学生信息等等, 我们只要设置好@RequestMapping就可以随意在一个Controller类里面放入各种请求处理** * **但是,此时我们是原生Servlet, 如果照葫芦画瓢, 我们直接用Servlet充当Controller类 来 处理对应的一个模块 行不行? ** **答案是不行! 因为一个Servlet只能对应一个路径, 一个路径下只有 doGet/doPost/doPut...的方法区别. 我们无法实现像Controller类那样的随便往里面放入请求处理, 例如: 学生模块需要两个Get方法: 一个是请求学生列表, 一个是请求单个学生信息, 这两个方法, 如果用一个StudentServlet来处理, 很明显是不能处理的, 因为StudentServlet只能有一个路径, 且只有一个doGet方法, 所以顶多只能实现其中的一个需求, 所以: 直接用Servlet来处理一个模块是行不通的** * **但是Servlet有doGet,doPost,doPut等方法, 不是刚好对应了一个实体的增删改查操作吗? 那我们改一下思路: 一个Servlet对应处理一个实体类, 行不行? ** **答案也是不行的! 首先当一个项目大起来, 可能会有一堆表, 按照一个表对应一个实体的一般原则, 那么就会有一堆的实体类, 那么就会有一堆的Servlet, 这样肯定是不行的, 其次还是回到刚刚那个需求, 我们如果要请求学生列表怎么办呢? 这种思想也是很难实现的** * **那Servlet对应一个页面的请求处理行不行? 也不行, 还是刚刚那个例子: 一个页面上可能会要求查询单个学生, 也会要求查询所有学生列表, 那么这种情况Servlet也是无法处理的** * (写到这的时候, 我突然兴起, 去看了以前大二学JavaWeb时期的项目, 居然是: 一个Servlet对应一个请求然后对应一个Service!!??, 我差点蚌埠住了, 包括我刚刚想了这么多种方案选型, 都根本想不到这么离谱的做法哈哈哈, 只能说过了一年了, 我果然是在进步的哈哈哈 ) 总结上述这几种Servlet无法应对的情况, 归根结底就两个原因: * Servlet太多不好 * Servlet只能对应一个路径, 且只有一个doGet,一个doPost...无法实现Controller类的那种模块化统一请求处理 那么, 我们应该怎么样实现 **Controller类的那种模块化统一请求处理** 呢? **既然都是Servlet的错, 那我们干脆不用Servlet不就行了吗? ** **羊毛出在羊身上, 我们参考一下SpringMVC的做法 -- 用一个Servlet类DispatcherServlet类, 然后用多个普通Java类作为Controller类, 由DispatcherServlet类接收所有的请求, 根据请求的路径以及方法, 对应着调用普通Controller类来进行处理。** **这样一来,我们尽管往Controller类里面添加方法,因为他没有了Servlet的限制, 这样就能实现一个模块的请求全部放在一个类里面处理了, 同时因为Controller类只是一个普普通通的Java类, 那么就算添加再多Controller类, 也不会有Servlet太多的顾虑** 我们来定义几个Controller类: 首页模块对应的Controller类: ```java public class MainPageController { private HeadLineShopCategoryCombineService headLineShopCategoryCombineService; public Result getMainPageInfo(HttpServletRequest req, HttpServletResponse resp){ return headLineShopCategoryCombineService.getMainPageInfo(); } public void throwException(){ throw new RuntimeException("抛出异常测试"); } } ``` 头条操作模块对应的: ```java public class HeadLineOperationController { private HeadLineService headLineService; public Result addHeadLine(HttpServletRequest req,HttpServletResponse resp) { //TODO:参数校验以及请求参数转化 HeadLine headLine = new HeadLine(); Result result = headLineService.addHeadLine(headLine); return result; } public void removeHeadLine(){ System.out.println("删除HeadLine"); } public Result modifyHeadLine(HttpServletRequest req, HttpServletResponse resp){ //TODO:参数校验以及请求参数转化 return headLineService.modifyHeadLine(new HeadLine()); } public Result queryHeadLineById(HttpServletRequest req, HttpServletResponse resp){ //TODO:参数校验以及请求参数转化 return headLineService.queryHeadLineById(1); } public Result>queryHeadLine(){ return headLineService.queryHeadLine(null, 1, 100); } } ``` 店铺操作模块对应的: ```java public class ShopCategoryOperationController { private ShopCategoryService shopCategoryService; public Result addShopCategory(HttpServletRequest req, HttpServletResponse resp){ //TODO:参数校验以及请求参数转化 return shopCategoryService.addShopCategory(new ShopCategory()); } public Result removeShopCategory(HttpServletRequest req, HttpServletResponse resp){ //TODO:参数校验以及请求参数转化 return shopCategoryService.removeShopCategory(1); } public Result modifyShopCategory(HttpServletRequest req, HttpServletResponse resp){ //TODO:参数校验以及请求参数转化 return shopCategoryService.modifyShopCategory(new ShopCategory()); } public Result queryShopCategoryById(HttpServletRequest req, HttpServletResponse resp){ //TODO:参数校验以及请求参数转化 return shopCategoryService.queryShopCategoryById(1); } public Result> queryShopCategory(HttpServletRequest req, HttpServletResponse resp){ //TODO:参数校验以及请求参数转化 return shopCategoryService.queryShopCategory(null, 1, 100); } } ``` **DispatcherServlet: 接收所有的请求, 然后根据请求路径以及请求方法, 调用对应的Controller类中的方法进行处理** ```java /** * 请求分发器 */ @WebServlet("/") // 接收所有的请求 public class DispatcherServlet extends HttpServlet { @Override protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { // 1) 获得请求路径以及请求方法 String requestPath = req.getServletPath(); String requestMethod = req.getMethod(); // 2) 根据请求路径以及请求方法, 调用对应的Controller类进行 if("frontend/getmainpageinfo".equals(requestPath) && "GET".equals(requestMethod)) { new MainPageController().getMainPageInfo(req,resp); } else if("superadmin/addheadline".equals(requestPath) && "POST".equals(requestMethod)) { new HeadLineOperationController().addHeadLine(req,resp); } } } ``` **此时我们发现, 上面的代码都是写死的! 当项目大起来, 会有以下缺点:** * **每一次新增一个模块对应的Controller类时, 我们都必须得手动去DispatcherServlet的if-else链上面添加一个判断, 长期下去, 非常不利于维护与扩展** * **Controller中也包含了Service类成员变量, 这个问题在service层时就探讨过了, 总之就是手动初始化(注入)会很麻烦** * **Controller过于原生, 我们发现Controller里面传入的都是req,resp, 我们无论是对参数的获取还是响应参数, 都必须使用最原生的api手动去实现, 非常繁琐, 浪费精力** **我们是否需要一个可以直接把新增的Controller自动注册到DispatcherServlet中的工具呢? 是否需要一个自动从req获取参数, 同时更方便我们响应结果的工具呢? ** * 从业务系统的开发中发现的问题总结: * **我们是否需要一个可以帮我们实现自动化注入(初始化), 来帮我们自动管理类与类之间关系 的工具呢? ** * **我们是否需要一个可以直接把新增的Controller自动注册到DispatcherServlet中的工具呢?** * **我们的Controller类是否需要一个自动从req获取参数, 同时更方便我们响应结果的工具呢? ** ...... **我们的自研框架, 能不能实现上述以及更多的需求, 使得我们的项目开发更简单呢?** ## 自研框架IOC实现前奏[实现自研框架IOC核心功能的前置知识] ### 工厂设计模式的思想: 我们从上面通过原生JavaWeb实现业务系统的过程中,提出的需求是:**需要一个自动管理类与类之间关系、自动初始化(注入)的工具** 当提及这样的需求时, 我们很容易想到一个设计模式:**工厂模式**, **因为工厂模式最大的特点就是:可以忽略对象创建的具体细节,由工厂负责创建并返回需要的对象** 但是普通的工厂模式也有好几个,所以,我们先来分析一下,有什么普通的工厂模式符合我们的需求的: (下列代码均在demo.factory包下) * 简单工厂模式: * 概念:定义一个工厂类以及多个具有共同父类或接口的产品类,客户端**通过工厂类,传入不同的参数,并得到对应的产品类:** * 大致代码思想如下: ​ 工厂类: ![image-20220528204615073](\mdImage\image-20220528204615073.png) ​ 测试: ![image-20220528204625528](\mdImage\image-20220528204625528.png) * 缺点: * 所有的产品类对象的创建均在一个工厂类中,如果各个对象的创建都比较复杂,该工厂类就会显得特别臃肿,违背了单一职责原则 * 我们会发现,该工厂类是通过类似于if-else等方式进行判断从而创建不同的产品类对象, 如果我们此时要多加一个产品类,那么就必须手动修改工厂类的代码才能实现新功能的添加,毫无疑问违背了开闭原则。 * 工厂方法模式: * 概念:定义多个具有共同父类或接口的工厂类 以及 多个具有共同父类或接口的产品类, ​ **每一个工厂类对应一个产品类,客户端通过调用不同的工厂类获得不同的产品类对象** * 大致代码思想如下: 工厂类: ![image-20220528205650974](\mdImage\image-20220528205650974.png) ![image-20220528205640673](\mdImage\image-20220528205640673.png) ![image-20220528205700036](\mdImage\image-20220528205700036.png) 测试: ![image-20220528205710652](\mdImage\image-20220528205710652.png) * 优点: * 把简单工厂中的单一工厂创建所有的产品类的职责下沉并分散给各个子类,一个工厂类只负责一个产品类的创建,符合单一职责 * 当新增一个产品类时,只需要新增对应的一个工厂类,然后客户端调用新工厂类即可,无需修改其他原有的代码,符合开闭原则 * 缺点: * 一个产品类对应一个工厂类,产品过多时,类会太多 * 同一类工厂类,只能创建同一类的产品类,不能创建多种产品类 * 抽象工厂模式: * 概念:上述的无论是简单工厂还是工厂方法模式,都是:一种工厂类对应一种产品类,一种工厂类无法创建多种产品类 ​ **而抽象工厂模式,可以实现 一个工厂类创建一系列相关的产品族, 可以认为是可以生产多种相关的产品的工厂方法模式。** * 大致代码思想如下: 工厂类: ![image-20220528210628738](\mdImage\image-20220528210628738.png) ![image-20220528210652414](\mdImage\image-20220528210652414.png) ![image-20220528210703388](\mdImage\image-20220528210703388.png) 测试: ![image-20220528210717271](\mdImage\image-20220528210717271.png) * 优点: * 在工程模式基础上,实现了一个工厂类可以创建一系列的产品类 ​ 至此,我们已经把普通的工厂模式相关的都说完了,那么哪个工厂模式可以实现我们的需求呢? ​ 答案是:光靠工厂模式我们是无法实现我们的需求的 ​ 仔细想想我们的需求:我们**自动管理**类与类的关系,要**自动**实现初始化, ​ 但是我们的**这些普通的工厂模式里,都是手动写死地去new**,毫无疑问是无法实现我们要求的**自动**的需求的,因为当我们新增一个类时,必须又得去手写实现, ​ 注意,我们这里说的无法实现,只是**特指上述我们所说的普通的工厂模式(即:简单工厂,工厂方法,抽象工厂)无法实现我们的需求,** ​ 但是,毫无疑问**工厂模式**也将是**我们要实现的功能的一个必要的设计思想**:**客户端只需要从工厂里面取出想要的对象,而对象的创建由工厂内部完成** ​ 只不过因为普通的工厂模式的内部都是写死new来创建对象,不符合我们的自动需求罢了, ​ 那么:**我们Java有什么技术可以不写死地用new,而是动态地来创建对象呢?** ​ 我们应该第一时间想到:**反射!** ​ **如果我们可以通过反射实现动态地创建对象,那么我们新增一个类时,就无需手写来实现其实例对象的创建,也就离我们的自动需求更近一步了!** ### **反射**: * 概念:反射机制就有点像**获得一份class字节码的抽象**,我们可以通过三种方式(.class, getClass, Class.forName)获得Class对象, ​ 并通过Class对象获得这个类其中的Field成员变量,Method成员方法,Constructor构造方法, ​ **从而动态地去实例化对象,设置变量值,调用方法等** * 作用: * **一切都可以在运行时再进行,而非编译期就写死!** * 在运行时,实例化一个类的对象 * 在运行时,访问一个对象的成员变量、成员方法等等 * **可以实现配置化动态创建对象,即:让代码更有通用性,我们可以把一些变动的内容(例如类的全限定名称等)写在配置文件/注解等处,** **通过动态地获取配置信息,从而动态地确定要被创建的对象是什么,要调用的方法是什么等等,可以非常灵活,而原代码并未发生任何变动** * 具体的反射API就不在此诸多赘述了。 * 对实现我们要求的需求的作用: * 我们的要求是可以**自动管理**类与类的关系,要**自动**实现初始化(注入)等 * 而有了**反射**这一可以**动态创建对象并访问其成员** 并且 **还可以实现配置化** 的利器, 我们不就可以通过**提前配置好类相关的信息**,然后交由**工厂** 去**读取这些配置,然后通过反射创建对象** 这样一来,我们的客户端就可以完全不关心这些对象的创建过程,**也不需要自己手动去写new,** 只需要交由 结合了 **工厂模式 + 反射**的工厂,就可以获得这些对象了。 不就实现了我们要求的**自动**了吗? 至此,又出现了一个新的问题,那我们**如何去配置类相关的信息呢?** 借鉴一下Spring,我们可以通过类似于 **xml文件 这种集中式配置的方式**, 也可以 通过 **注解 这种分散式配置的方式** 两种其实都可以,我们这里暂时只采用了 **注解** 的方式去配置,在之后有余力,可以实现一下XML的配置方式。 (关于注解的相关知识,也不在此过多赘述了) * 我们一直都在说:工厂内部里要管理类与类之间的关系,**那么工厂在其内部创建对象时,应该按怎么样的思想和方式去创建类对象,实现类与类的关系呢?** 例如下述例子: ![image-20220528231009485](\mdImage\image-20220528231009485.png) * 上层依赖下层的方式: * 概念: 我们很容易想到,既然行李箱依赖箱体,箱体依赖底盘,底盘依赖轮子,那么我们在构造方法把依赖的下层去new出来不就行了吗? 这就是上层依赖下层的创建方式。 ![img](http://img.mukewang.com/szimg/62920fdb0001a1f919201080-500-284.jpg) * 缺点:不可扩展,难以维护 ​ 如果此时我们需要修改轮子的相关信息,那么就会引起一系列的连锁反应,导致项目难以维护: ​ 例如:我们要把Trie的构造函数改动了,那么上面使用该构造函数来new该成员变量对象的类都得改变代码: ​ ![img](\mdImage\A2S[5}}Q{42MV0W_YZ5`X2.jpg) ### **依赖倒置原则 -> 依赖注入 -> IOC思想 -> IOC容器:** * 原本一般是上层依赖下层,所以:在上层的构造函数中把依赖的下层对象new出来 * **依赖倒置**的所谓倒置, 就是变为:下层依赖上层,具体实现上:把 **依赖的下层对象,注入到 上层对象中** 这种实现方式,我们称为**依赖注入DI: 就是把一个类的成员变量(即:依赖的对象)通过 外部注入而非内部创建 的方式来实现赋值** 由此方法,改造上面我们的那个例子: ![image-20220528223705985](\mdImage\image-20220528223705985.png) ![image-20220528223727566](\mdImage\image-20220528223727566.png) 此时你会发现,无论下层的怎么变,只需要改动一下该变动的对象的创建方式即可,其他的均不影响,这就解决了之前的问题。 * 同时,你会发现,通过**依赖注入**改造后的代码中, **本来一个类对其成员变量的创建权,已经不在该类手上了,即:一个类的成员变量对象的创建,并非由其自己去实现** **在此案例代码的右边,我们可以看到:对于这些对象的创建,已经统一放在了这些类的外部了** **这种 把类在其内部程序本身创建对象的控制权,交由别的地方进行统一管理的设计思想,我们称为:控制反转IOC** * 而,**在此控制反转IOC的设计思想下,这个统一管理这些创建对象的地方,我们称之为:IOC容器** * 至此,我们一一介绍了依赖倒置、依赖注入、控制反转IOC思想,IOC容器的概念,他们的关系如下: ![image-20220528224407374](\mdImage\image-20220528224407374.png) * 在介绍完上述概念后,回答提出的问题:工厂内部要管理类与类之间的关系,那么**工厂在内部创建对象时,应该按怎么样的思想和方式去创建类对象呢?** 答案就应该是:**在依赖倒置原则 以及 控制反转设计思想的指导下,通过IOC容器,在其内部通过依赖注入的方式,实现类对象的创建** ![img](\mdImage\]DWCM0KGIK4@CDUSGI`ES%F.jpg) ![image-20220528225747531](\mdImage\image-20220528225747531.png) ### 总结: ![image-20220528225819691](\mdImage\image-20220528225819691.png) 我们依次介绍了: * 工厂模式 -> 结论:我们要实现的**自研框架要像一个工厂一样,客户端可以从该工厂里面获取对象,而无需关注内部的对象创建过程。** * 反射机制 -> 结论:我们要通过**反射这一以实现配置化动态创建对象的特性,实现我们的自动需求** * 注解 -> 结论:我们要通过**注解这一分散式配置方式,来为反射机制配置与提供类的相关信息。** * IOC容器 -> 结论:我们的**自研框架内部创建对象的方式,应该是个IOC容器** 总结来看,要实现我们提出的**需要一个自动管理类与类之间关系、自动初始化(注入)的工具**的需求, 那**我们的自研框架在这方面的实现方式**就应该是: * **用户可以通过 注解 来对 类的相关信息 进行配置;** * **而自研框架提供一个IOC容器,其内部的对象创建过程是:通过 读取程序中注解的配置信息+反射机制 动态地创建出对象,同时通过依赖注入的方式,实现类与类之间的依赖关系;** * **从而IOC容器就如同一个工厂一样,用户可以忽略上述的内部的对象创建过程,直接从该IOC容器中获得需要的对象** * **此过程中,用户只需要通过注解进行配置,然后就可以直接通过IOC容器获得对象,从头到尾没写过一行创建对象的代码,这就是我们的需求:“自动管理类与类之间关系、自动初始化(注入)” 的实现** ## 自研框架IoC容器(功能)的实现 ### IOC容器(功能)的实现流程: * 思路: 前面我们已经介绍过我们自研框架在实现上述需求时的实现方式了,其实该实现方式,就是**IOC容器(功能)的实现**! 据上面的分析,我们可以把框架的作用或功能分为以下几点: * 解析**配置**: * **定位到目标**同时**注册目标的实例对象到IOC容器**内 * 实现**依赖注入** * 提供对外的api来对IOC容器进行操作 我们要通过**以下的技术选型**来实现上述功能: * **注解**:用于给用户在特定的地方做标记进行配置,从而自研框架可以去识别并进行相应处理 => **创建注解 => 用于配置** * **类加载器**:之前学反射的时候说过,类加载器可以用于在在任何环境下获得指定范围(用户给定包名)下的所有的文件 => **用于从指定范围下获得所有的class文件,从而通过反射获得对应的Class对象** * **反射**:用于通过全限定名称获得Class对象,从而获得Constructor对象进行对象实例化,获得Field对象进行注入,同时可以通过isAnnotationPresent方 法来判断是否被某注解标注 => **获得目标的对象、以及成员变量对应的Field,从而可以实现注册与注入功能** * **ConcurrentHashMap**:用于作为IOC容器的载体,以Class对象作为Key(因为Class对象在JVM中只会有一个),以Class对象的实例对象作为Value => **实现IOC容器的载体,并被包装为一个IOC容器类,对外提供相关操作** * 综上,实现流程为: * 创建注解 * 定位与提取目标 * 实现容器 * 依赖注入 ### 创建注解: * 此处我们先来创建用于修饰类的注解 : Controller、Service、Repository、Component ```java /** * 要被注册的controller对象 */ @Target(ElementType.TYPE) // 用于修饰类 @Retention(RetentionPolicy.RUNTIME) // 要用反射进行判断,所以必须保留到Runtime时期 public @interface Controller { } ``` * 用于修饰之前我们业务系统里的类: ![image-20220603115015264](\mdImage\image-20220603115015264.png) ### 定位与提取目标: * 上面我们已经把创建好的 **标识 该类的对象是要被注册到容器的类 的注解** 放置到该放置的类上面了,那,**我们怎么去定位并提取这些目标呢?** * **毫无疑问,要定位并提取这些目标,我们首先要实现的,就是要把用户指定范围下所有的类都给获取并创建出来!先以此得到 全部的Class对象 ,再逐一遍历去过滤从而实现定位并提取这些目标** * 既然要实现 **指定的全限定名称 -> 类 -> 对象** ,必然是用 **反射机制**,那么问题来了,当用户使用该框架提供的IOC容器时,只会传入一个限定范围 例如:com.mlming 包下的所有类都要被获取,但是用户只会传一个“com.mlming”路径进来,这种情况下,我们怎么得到这个包下所有类的全限定名称呢? 在之前学反射时,我们学过,**通过类加载器 是可以实现在任何环境下面都能获取到绝对路径的方法!** ```java 之前学Java,我们都说过绝对路径不好,因为换个机子就不对了 但是,我们知道,相对路径是为了无论什么环境都能移植 但是Java不是如此,不同开发工具其的相对路径的默认相对的那个路径是不一样的 这就是失去了移植性 所以其实Java中一般也不用相对路径 在学Node.js时,我们其实面临过一样的问题, 我们最后采取的方式是: 动态获取当前文件的绝对路径 这样就能无论放在那里都能动态获取到绝对路径,从而实现了通用,可移植 那么在Java中,如何实现**动态获取绝对路径呢?** 以下代码是基本固定的,以后Java使用路径,基本上都是用这种方式,所以最好背会: 实现: Thread.currentThread().getContextClassLoader().getResource(文件相当于src的相对路径).getPath() 注意: 此种写法必须要求该文件处于**类路径下,即src目录下** **从而getSource()里面填的是文件相当于src目录下的相对路径** 解析: Thread.currentThread()获取当前线程对象 getContextClassLoader()是线程对象的一个方法,获取到当前线程的类加载器对象 getResource() 是类加载器对象的一个方法,可以到当前类所在的根路径下加载资源 **当前类所在的根路径 => 类路径 => src** 所以这种方式要求文件必须位于src下,是getSource()的执行问题 因为类的根目录肯定是src,所以类所在根路径就是src,所以资源即文件必须放在src下 getPath() 获取当前资源绝对路径 ``` 因此,**我们就可以获得传入的指定路径的绝对路径了,获得绝对路径后,我们可以通过穷举的方式把里面的.class文件一一的获得,从而通过字符串分割等方式获得全限定名称部门,从而利用反射进行Class对象的创建,**这样一来,就实现 **获取指定范围下所有的Class对象** 的需求了: 因为该功能是通用的,所以可以抽象成一个**ClassUtil中的extractPackageClasses方法**来进行实现: ```java @Slf4j public class ClassUtil { /** * 获取包下类集合 * * @parampackageName包名 * @return 类集合 */ public static Set<Class<?>> extractPackageClasses(String packageName) { // 1) 获得类加载器: ClassLoader classLoader = getClassLoader(); // 2) 通过类加载器加载指定路径的资源,获得其对应的绝对URL URL url = classLoader.getResource(packageName.replace(".","/")); if(url == null) { log.warn(FrameWorkConst.PACKAGE_NOT_FOUND_ERROR_MSG + packageName); return null; } // 3) 依据不同的资源类型,采用不同的方式获取资源的集合 Set<Class<?>> classSet = null; // 过滤出文件类型的资源 if (url.getProtocol().equalsIgnoreCase(FrameWorkConst.FILE_PROTOCOL)){ classSet = new HashSet<Class<?>>(); File packageDirectory = new File(url.getPath()); extractClassFile(classSet, packageDirectory, packageName); } // TODO: 可以处理别的资源类型 return classSet; } /** * 递归获取目标package里面的所有class文件(包括子package里的class文件) * * @param classSet 装载目标类的集合 * @param packageDirectory 文件或者目录 * @param packageName 包名 * @return 类集合 */ private static void extractClassFile(Set<Class<?>> classSet, File packageDirectory, String packageName) { // 如果该packageDirectory是文件,则直接return不处理 if(!packageDirectory.isDirectory()) return; // 1) 获得目录下的所有的File,并对其进行过滤: // 如果是目录,则还需要进一步的递归调用, 则加入到files数组, 然后之后遍历调用该函数进行穷举 // 如果是文件,且是.class文件,那么可以直接对其进行路径切割获得全限定名称,从而通过反射创建Class对象并加入set中 File[] files = packageDirectory.listFiles(new FileFilter() { @Override public boolean accept(File file) { if(file.isDirectory()) return true; // 如果是.class文件: String absoluteFilePath = file.getAbsolutePath(); if(absoluteFilePath.endsWith(".class")) { // 进行路径切割,获得全限定名称: // 因为包名是.分割,而路径是/, 所以先替换 absoluteFilePath = absoluteFilePath.replace(File.separator,"."); // 从包名处截断, 一直到最后一个.的位置, 从而就可以得到包名.类名, 也就是全限定名称 String className = absoluteFilePath.substring(absoluteFilePath.indexOf(packageName),absoluteFilePath.lastIndexOf(".")); // 反射获得Class对象, 并把该Class对象加入到set中去 Class<?> clazz = loadClass(className); classSet.add(clazz); } return false; } }); // 2) 把files数组里面存的这些目录, 进一步递归调用 if(!ValidationUtil.isEmpty(files)) { for (File file : files) { extractClassFile(classSet,file,packageName); } } } /** * 获得类加载器 * @return */ public static ClassLoader getClassLoader() { return Thread.currentThread().getContextClassLoader(); } /** * 获取Class对象 * * @param className class全名=package + 类名 * @return Class */ public static Class<?> loadClass(String className){ try { return Class.forName(className); } catch (ClassNotFoundException e) { log.error(FrameWorkConst.LOAD_CLASS_ERROR_MSG, e); throw new RuntimeException(e); } } } ``` * 上一步,我们通过ClassUtil中的extractPackageClasses方法就可以获得指定包下面的所有Class对象了,现在我们要做的,就是要定位并提取 但是这个提取目标,是和IOC容器本身的实现有关的,所以我们把这一步放到容器实现时去实现。 ### 实现容器:实现IOC容器的 “容器” * 要求: * 因为一个框架只能有一个IOC容器,所以该容器类必须是**单例模式**实现的 因为 **普通的饿汉模式** 以及 **双重检查锁的懒汉模式** 都会被 **反射** 破坏其单例性! 所以,此处我们选用 **枚举饿汉模式** 来实现单例, **由于枚举的性质,可以帮我们抵御反射以及序列化的破坏**。 **(之所以是懒汉模式,是因为IOC容器应该是项目启动时就被创建的,符合懒汉模式)** * **容器的数据结构(载体):** 因为在IOC容器里面,我们要得到一个实例对象,怎么获得?很明显需要一个唯一标识来获取,也就是说这应该是Key-Value形式 **以Class对象作为Key(因为一个类的Class对象在JVM中只有一个),以实例对象作为Value。** 因此,**毫无疑问应该是一个Map数据结构,考虑到多线程安全问题,我们可以使用ConcurrentHashMap** * 实现流程: * 定义容器类:通过 **枚举饿汉模式** 来定义 IOC容器类,其中以 **ConcurrentHashMap**作为数据载体 * 实现容器的加载:即**1.3后面定位与提取目标的那一个流程** * 通过1.3中的extractPackageClasses方法来获得指定范围下所有的类的集合 * 通过遍历该集合,并通过isAnnotationPresent判断其是否是被 我们创建的注解 所标记的, 如果是,那这就是目标,因此要把其实例化,把Class对象以及实例化对象放入ConcurrentHashMap中 * 对外提供一些操作供外界操作该容器 - **增加、删除操作** - **根据Class获取对应实例** - **获取多有的Class和实例** - **通过注解来获取被注解标注的Class** - **通过超类获取对应的子类Class** - **获取容器载体保存Class的数量** * 具体实现: BeanContainer类: ```java /** * 容器类 */ public class BeanContainer { /** * 存放所有被配置标记的目标对象的Map */ private final ConcurrentHashMap,Object> beanMap = new ConcurrentHashMap<>(); /** * 加载bean的注解列表 */ private static final List> BEAN_ANNOTATIONS = Arrays.asList(Component.class, Controller.class, Service.class, Repository.class); /** * 枚举懒汉单例模式 实现 BeanContainer的获取 */ public static BeanContainer getInstance() { return ContainerHolder.HOLDER.instance; } private enum ContainerHolder { HOLDER; private BeanContainer instance; // 懒汉模式: ContainerHolder() { instance = new BeanContainer(); } } // 加载标记: 一个容器只会被加载一次 private boolean loaded = false; public boolean isLoaded() { return loaded; } /** * 加载指定范围下面的被注解标记的目标Bean * * 因为只能加载一次,所以直接用synchronized实现比较方便,当然也能用双重验证锁 * @param packageName */ public synchronized void loadBeans(String packageName) { // 1) 如果加载过了, 则直接return if(loaded) return; // 2) 通过ClassUtil.extractPackageClasses()获得指定包下所有类对象 Set> classes = ClassUtil.extractPackageClasses(packageName); // 3) 遍历set, 对每一个Class判断其是否被 我们创建的注解修饰, 如果是, 则要注册到容器中 for (Class clazz : classes) { for (Class annotation : BEAN_ANNOTATIONS) { if(clazz.isAnnotationPresent(annotation)) { beanMap.put(clazz,ClassUtil.newInstance(clazz,true)); } } } // 4) 加载完毕,设置为true loaded = true; } /** * 添加一个class对象及其Bean实例 * * @param clazz Class对象 * @param bean Bean实例 * @return 原有的Bean实例, 没有则返回null */ public Object addBean(Class clazz, Object bean) { return beanMap.put(clazz, bean); } /** * 移除一个IOC容器管理的对象 * * @param clazz Class对象 * @return 删除的Bean实例, 没有则返回null */ public Object removeBean(Class clazz) { return beanMap.remove(clazz); } /** * 根据Class对象获取Bean实例 * * @param clazz Class对象 * @return Bean实例 */ public Object getBean(Class clazz) { return beanMap.get(clazz); } /** * 获取容器管理的所有Class对象集合 * * @return Class集合 */ public Set> getClasses(){ return beanMap.keySet(); } /** * 获取所有Bean集合 * * @return Bean集合 */ public Set getBeans(){ return new HashSet<>( beanMap.values()); } /** * 根据注解筛选出Bean的Class集合 * * @param annotation 注解 * @return Class集合 */ public Set> getClassesByAnnotation(Class annotation){ //1.获取beanMap的所有class对象 Set> keySet = getClasses(); if(ValidationUtil.isEmpty(keySet)){ return null; } //2.通过注解筛选被注解标记的class对象,并添加到classSet里 Set> classSet = new HashSet<>(); for(Class clazz : keySet){ //类是否有相关的注解标记 if(clazz.isAnnotationPresent(annotation)){ classSet.add(clazz); } } return classSet.size() > 0? classSet: null; } /** * 通过接口或者父类获取实现类或者子类的Class集合,不包括其本身 * * @param interfaceOrClass 接口Class或者父类Class * @return Class集合 */ public Set> getClassesBySuper(Class interfaceOrClass){ //1.获取beanMap的所有class对象 Set> keySet = getClasses(); if(ValidationUtil.isEmpty(keySet)){ return null; } //2.判断keySet里的元素是否是传入的接口或者类的子类,如果是,就将其添加到classSet里 Set> classSet = new HashSet<>(); for(Class clazz : keySet){ //判断keySet里的元素是否是传入的接口或者类的子类 if(interfaceOrClass.isAssignableFrom(clazz) && !clazz.equals(interfaceOrClass)){ classSet.add(clazz); } } return classSet.size() > 0? classSet: null; } /** * 获得注册Bean个数 * @return */ public int size() { return beanMap.size(); } } ``` ### 实现依赖注入:实现IOC容器的“IOC” * 通过上面的容器实现后,我们其实已经把之前创建的注解(@Controller、Service、Repository、Component)标注的类对应的Bean都注册到容器里面了 但是当我们跑单例点开里面的一个个实例对象就会发现,内部的成员变量还是null, 也就是说:**我们还需要实现依赖注入,来实现给Bean的成员变量进行注入** * 实现流程: * 创建注解:模仿前面我们实现IOC容器的“我们需要一个注解来标注什么类需要被注册”,我们肯定也需要一个注解来标记什么成员变量需要被注入 * 定位目标(成员变量):我们要从遍历容器,同时取出每一个Class对象里面的Field对象,然后通过isAnnotationPresent判断是否是上述注解标注,如果是就说明要被注入 * 实现依赖注入:获得该Field对象的类型,如果容器中有该Class对象,则直接注入其实例对象;如果没有,说明该类型是接口或没被之前那些注解修饰的父类,所以此时要通过容器对外提供的getClassesBySuper方法获得其实现类,如果只有一个则直接注入,否则还要通过@Qualifier的值进行判断应该取该类型的哪一个实现类。之后通过Field对象的set方法,来实现对对应成员变量的赋值,也就是注入 * 具体实现: * 创建注解: @Autowired ```java @Target(ElementType.FIELD) @Retention(RetentionPolicy.RUNTIME) public @interface Autowired { } ``` @Qualifier ```java /** * 用于当@Autowired修饰的成员变量的类型下面有多个实现类,用于特别注明该实现类的类名, 从而确定选哪一个作为该成员变量的注入 */ @Target(ElementType.FIELD) @Retention(RetentionPolicy.RUNTIME) public @interface Qualifier { String value() default ""; } ``` * 实现依赖注入的类: DependencyInjector类: ```java /** * 实现依赖注入的类 */ @Slf4j public class DependencyInjector { // Bean容器 private BeanContainer beanContainer; public DependencyInjector() { beanContainer = BeanContainer.getInstance(); } /** * 执行IOC: */ public void doIOC() { // 0) 如果beanContainer里面没有数据,则无需执行IOC if(beanContainer.size() == 0) { log.warn(FrameWorkConst.EMPTY_BEAN_CONTAINER_ERROR_MSG); return; } // 1) 遍历beanContainer中所有被注册进去的类 for (Class clazz : beanContainer.getClasses()) { // 2) 对于每一个class, 获得其所有成员变量, Field[] fields = clazz.getDeclaredFields(); if(ValidationUtil.isEmpty(fields)) continue; // 3) 对每一个成员变量Field判断是否由@Autowired修饰, 如果是,说明要被注入 for (Field field : fields) { if(field.isAnnotationPresent(Autowired.class)) { // 4) 获得该field的注入对象 Object filedObject = getFieldInstance(field); // 5) 之后通过Field对象的set方法,来实现对对应成员变量的赋值,也就是注入 // 如果此时filedObject还是null, 说明注入失败, 应该报错 if(filedObject == null) { throw new RuntimeException("unable to inject relevant type,Object is: " + beanContainer.getBean(clazz) + " target fieldClass is:" + field.getType().getName() + " fieldName is : " + field.getName()); } ClassUtil.setField(field,beanContainer.getBean(clazz),filedObject,true); } } } } /** * 获得field的注入对象 * @param field * @return */ private Object getFieldInstance(Field field) { // 1) 获得该Field对象的类型,如果容器中有该Class对象,则直接注入其实例对象; Class type = field.getType(); if(beanContainer.getBean(type) != null) { return beanContainer.getBean(type); } // 2) 如果没有,说明该类型是接口或没被之前那些注解修饰的父类, // 所以此时要通过容器对外提供的getClassesBySuper方法获得其实现类集合 Set> classesBySuper = beanContainer.getClassesBySuper(type); // 3) 如果只有一个实现类, 则直接注入 if(classesBySuper.size() == 1) { return beanContainer.getBean(classesBySuper.iterator().next()); } // 4) 否则还要通过@Qualifier的值进行判断应该取该类型的哪一个实现类。 Qualifier qualifier = field.getAnnotation(Qualifier.class); // 如果此时该field没有被@Qualifier标注,或者标注了但没有赋值, 是不合法的, 因为有多个实现类, 所以要报错 if(qualifier == null || ValidationUtil.isEmpty(qualifier.value())) { throw new RuntimeException(type + FrameWorkConst.MULTIPLE_IMPLEMENTS_BUT_NOT_SET_QUALIFIER_ERROR_MSG); } String qualifierName = qualifier.value(); for (Class aClass : classesBySuper) { if(qualifierName.equals(aClass.getSimpleName())) { return beanContainer.getBean(aClass); } } return null; } } ``` ### 总结: ![image-20220528225819691](mdImage/image-20220528225819691.png) 至此,我们的IOC容器就实现完毕了,总结而言分为了下面三个主要部分: * **注解**:实现容器:@Controller、@Service、@Repository、@Component、实现依赖注入:@Autowired、@Qualifier * **BeanContainer类:**容器类,由 **枚举懒汉单例模式 + ConcurrentHashMap** 并结合 **反射 + 注解** 实现,**是IOC容器的 “容器” 的实现** * **DependencyInjector类**:实现依赖注入的处理类,结合 **反射 + 注解** 实现, **是IOC容器的 “IOC” 的实现** 同时在实现IOC容器过程中,为了辅助我们功能的实现,我们来创建了一个ClassUtil工具类,帮助我们实现关于类相关的功能: * **extractPackageClasses方法**:获得指定范围(包)下的所有的.class文件对应的Class对象 * 一些反射的封装,例如newInstance实例化对象、loadClass获得Class对象等 同时最重要的,IOC容器本身实现了,**我们怎么去实现IOC容器的初始化呢?或者说,怎么去启动IOC容器去执行其作用呢?** 我们只需要 **在某个满足项目启动时,其就会被加载的类的方法中 (例如DispatcherServlet是一个servlet,其init方法会在项目启动时被自动调用)** 执行以下代码: ```java // 1) 先把容器给加载了 BeanContainer.getInstance().loadBeans("指定包名"); // 2) 再进行依赖注入 new DependencyInjector().doIOC(); ``` ## 自研框架AOP实现前奏【实现AOP核心功能的前置知识】 ### 为什么需要AOP?AOP是什么?Spring实现的AOP的局限性? * IOC无法实现的功能: 对于OOP(面向对象)而言,一个功能是交由一个对象去进行的,从而把 一个 **业务需求**分为多个对象去组合执行。 而IOC容器的作用:可以 **通过低耦合低侵入的方式** 使得对象与对象之间的关系更容易维护, 从而可以使得 **业务需求** 可以通过容器更好地分层,并更好地相互调用。 但是,对于 **业务需求之外的 系统需求**, IOC容器并不能很好地解决。 所谓的 **系统需求** 就是指的是: **只有程序员才会去关心的需求,例如:添加日志、添加权限校验等, 这种需求基本与业务毫无关系** **根据设计原则,我们不应该把 系统需求与业务需求的代码写在一块,而是应该把他们分开去写,让业务代码只关注业务需求,让系统需求代码只关注系统需求,从而实现两者的解耦合,然后通过某种方式进行组合,从而实现我们想要的功能。** **而IOC容器,只是一个方便对象与对象之间关系的工具,我们无法实现两种需求对应的对象的解耦,毕竟IOC容器只是托管了我们项目中需要的对象,然后在需要时我们通过容器来获取,然后进行组合,但这样一来,我们的系统需求对应的对象和业务需求对应的对象,总是要被一起写在一个地方去进行的,这样也没有实现我们所谓的解耦合。** **而且,一般系统需求都是通用的,例如:添加日志,不仅一个对象要添加日志,那么难道我们一个个去getBean然后组合吗?总归是不合理的。** * 综上,我们的框架需要提供**一个核心功能**: **我们可以把系统需求对应的那部分代码,抽象出来单独写,然后通过该功能,自动地把该系统需求实现,织入到我们需要添加额外系统需求的业务代码之中** 例如: 我们可以把 添加日志 的实现,单独写。然后我们通过框架提供的功能,把该添加日志的功能,自动地添加到 我们的Controller、Service等需要日志记录的业务代码之中去,而此时,开发Controller、Service这些业务代码时,我们根本无需去关心是怎么添加日志的,这样就实现了解耦合。 * 该核心功能,被称为:Aspect Oriented Programming , 即:**AOP** 在实现AOP核心功能之前,我们需要对AOP的专业术语进行统一: * 我们所说的额外的系统需求代码 => **Aspect切面, 一般是一个类** * 我们所说的需要被织入的业务代码(的位置) => **Pointcut 切入点,一般是 某个类的某个方法、成员变量、构造函数等** * 业务代码被织入的时机: 执行前,执行后, 返回后, 抛出异常后 => **Advice通知,一般是一个个方法,至于其时机的控制,交由框架去实现** 所以,用上述术语再去描述一下我们的核心功能AOP,那就是: **我们要单独编写Aspect切面类,指定该切面要织入的Pointcut切入点,同时编写切面类中的Advice方法来对应各个时机要额外添加的系统功能。** **这样一来,框架就会根据Pointcut找到对应的位置,从而把Aspect切面中定义的Advice方法,在对应的时机进行调用。** * 一个切入点上的单个Aspect的执行流程: ![image-20220607114948221](mdImage/image-20220607114948221.png) * 一个切入点上的多个Aspect的执行流程: ![image-20220607115035605](mdImage/image-20220607115035605.png) * **Spring中的AOP:** * spring中的IOC占主导地位, 所以AOP只能在OOP中集成,因此没有单独使用那么灵活。 * 因此**Spring自身实现的AOP并未实现“AOP”这个概念下的所有功能,而只能支持 方法级别的连接点, 而不能支持AOP这个概念下的构造方法、成员变量等**的连接点毕竟这样就能满足80%的开发需求了。 * 剩下的如果需要可以借助AspectJ来实现 ### 代理模式: 通过上面的分析,我们很容易就能想到一个设计模式: **代理模式** ![image-20220607115632877](mdImage/image-20220607115632877.png) * 概念: 代理类和被代理类同属于一个接口或一个类,代理类会在被代理类的原功能基础上,进行 **功能增强** 或 **控制访问**, 而客户端对此过程无感,只觉得自己在使用原对象,实际上是在使用被代理后的代理对象 * 由上述概念,很明显,我们可以通过 **代理** 的方式, 把我们的Aspect的那些Advice,从而对Pointcut进行织入。 客户端可以觉得它在使用原Pointcut,实际上是在使用代理对象。这样一来,就能实现我们的AOP功能了。 也就是说: **AOP 是通过 代理模式 进行实现的** * 实现方法: * 静态代理 * 动态代理 ### 静态代理 * 概念:通过**硬编码的方式**,来对 **实现了同一个接口**的目标对象,进行代理,从而对其目标方法进行控制 * 代码实现: 被代理类: ![image-20220607130645877](mdImage/image-20220607130645877.png) Aspect: ![image-20220607130758808](mdImage/image-20220607130758808.png) 测试: ![image-20220607130822110](mdImage/image-20220607130822110.png) 由上可见,对客户端而言,客户端只觉得自己是在用 ToCPay 的实例,并不知道自己调用的pay其实是被代理过的。 * 缺点: 根本原因在于:**硬编码**的方式:**我们的代理类要和被代理类硬编码实现同一个接口** * 当被代理类的接口新增一个方法时,尽管我们是要对新方法也是一样的织入,但是还是得硬编码去写。 * 当要新增一个被代理类时,尽管我们对它的织入代码是完全一样的,也必须新建一个代理类去进行代理。 * 代理对象只服务于一种类型的对象,如果要服务多类型的对象,势必要为每一种对象都进行代理,静态代理在程序规模稍大时就无法胜任了。 结合之前对于IOC的实现,我们就知道:**要想实现框架级别的灵活性与扩展性,只能是动态的方式来实现我们的功能** ### 动态代理概念 通过 **反射(JDK动态代理)** 或 **操控字节码(CGLIB)**的方式,在运行时动态地生成 代理类的字节码,并加载到JVM中去,并创建代理类对象,从而得以使用代理类对象。 ### JDK动态代理 * 概念:通过java.reflect包下的反射机制,实现动态代理的功能。 * 两个核心类: * InvocationHandler接口:相当于我们之前的Aspect * Proxy类:用于往目标类中织入上述Aspect,生成代理类对象 * 注意: **【被代理的类】必须实现接口** * 代码实现: * 定义InvocationHandler的实现类,并在invoke方法中编写Aspect的逻辑代码: * 有三个参数,proxy是生成的代理对象,method是被代理的方法,args是方法的参数列表 ```java // 相当于Aspect public class PayInvocationHandler implements InvocationHandler { // 因为invoke中的参数,并没有目标对象,所以要想调用目标对象的原方法,即被代理方法,还是得传入 Object target; public PayInvocationHandler(Object target) { this.target = target; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { before(); Object res = method.invoke(target,args);// 原方法 after(); return res; } // Before Advice: private void before() { System.out.println("支付前的操作"); } // After Advice: private void after() { System.out.println("支付后的操作"); } } ``` * 通过Proxy.newProxyInstance()生成代理类对象,并得以使用。 ```java public static void main(String[] args) { ToCPay target = new ToCPayImpl(); InvocationHandler aspect = new PayInvocationHandler(target); ToCPay proxy = (ToCPay) Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(),aspect); proxy.pay(); } ``` ### CGLIB动态代理 * 概念:内部主要封装了 **ASM JAVA字节码操控框架**,可以动态生成**被代理类的子类**以覆盖其非final的方法,**并绑定钩子回调自定义拦截器** ​ 即:当被代理类对象的目标方法被调用时,就会回调该所谓的自定义拦截器,其实就是我们说的Aspect,来对目标方法进行织入。 * 两个核心类: * MethodInterceptor接口:类似于前面JDK动态代理中InvocationHandler的作用,可以用于定义Aspect * Enhancer类:类似于前面JDK动态代理中的Proxy类,用于创建代理对象 * **【被代理类,有没有接口,都可以使用CGLIB】** * 代码实现: * 定义MethodInterceptor接口的实现类,并在intercept方法中编写Aspect的逻辑代码: * 有四个参数,proxy是生成的代理对象,method是被代理的方法,args是方法的参数列表,methodProxy被代理的方法 * **由于此处可以通过methodProxy.invokeSuper(proxy,args)直接调用原方法,所以此时并不需要像JDK动态代理那样传入被代理对象** ```java // 相当于Aspect public class PayMethodInterceptor implements MethodInterceptor { @Override public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable { before(); Object res = methodProxy.invokeSuper(proxy,args); // 可以直接调用原方法, 无需原对象 after(); return res; } // Before Advice: private void before() { System.out.println("支付前的操作"); } // After Advice: private void after() { System.out.println("支付后的操作"); } } ``` * 通过Enhancer.create()方法,创建代理对象并得以使用 ```java ToCPay toCPay = (ToCPay) Enhancer.create(ToCPayImpl.class,new PayMethodInterceptor()); toCPay.pay(); ``` ### 两种动态代理的对比 * 实现机制: * JDK动态代理:**基于反射机制**,要求被代理类**必须实现接口** * CGLIB:**基于ASM机制实现**,生成**被代理类的子类**作为代理类 * 优势: * JDK:是JDK原生支持,在JVM中运行较为可靠,可以平滑地支持JDK版本的升级 * CGLIB:被代理类无需实现接口,可以实现完全的无侵入代理。 ### Spring的AOP底层机制 * JDK动态代理 与CGLIB 共存: * 当被代理对象有接口时,默认JDK动态代理实现 * 当被代理对象无接口时,则使用CGLIB实现 * **CGLIB可以实现 有接口或无接口的动态代理, 但是还是默认用JDK的原因**: **JDK不依赖其他package,是Jdk自身提供的,并且兼容性较好,性能也不差** ## 自研框架AOP核心功能的实现: ### AOP 1.0 版本 #### 技术选型: * 自研框架只采用**CGLIB来实现动态代理**,因为不需要被代理类必须实现接口,相对灵活,编码也较方便。 * Spring的Aspect几乎都是通过注解来实现的,有如下注解: * @Aspect * @Order * @Before * @After * @AfterReturning ...... 但是,为了实现简单,我们仅仅使用@Aspect与@Order来标注Aspect类,剩下的那些注解主要是为了标注Advice方法, 我们通过 **定义模板类,由子类,即:Aspect类实现钩子方法(类似于模板方法设计模式中的钩子方法)** 来实现Advice方法即可 #### 实现流程: 我们之前说MethodInterceptor接口的实现类,相当于一个切入点的Aspect,用于之后被Enhancer类进行织入。 但是,之前那个案例是:一个切入点只有一个Aspect,但在框架内,事情可没那么简单。 由1.8-前奏中的: ![image-20220607115035605](mdImage/image-20220607115035605.png) 我们就知道,**在框架内,一个切入点是会有多个Aspect的!** 如果我们还是用一个MethodInterceptor接口的实现类来代表一个Aspect,很显然,该实现类被Enhancer类织入时,明显是不行的! 为什么?因为无法实现上述的时序执行逻辑! 所以,我们要转变前一个案例给我们带来的定式思维,一个MethodInterceptor接口的实现类应该是对应一个切入点,而不是一个Aspect,换句话说: **一个切入点,一个MethodInterceptor接口的实现类,可能有多个Aspect** **所以,思路很明确,我们应该额外定义一个个独立的Aspect,例如:日志记录Aspect,事务Aspect等,然后通过一个一个MethodInterceptor接口的实现类用一个List来存储这些Aspect,从而就能实现 代理逻辑 以及 上述的时序执行逻辑** 但是,**有切面了还不够,还得用Enhancer把代理逻辑织入到被代理对象,从而创建代理对象,所以我们需要一个额外的类来专门把代理对象创建出来,并替换掉IOC容器里面的原对象,从而实现之后我们用的对象都是代理后的对象。** 综上,AOP的实现流程如下: * 创建AOP的注解@Aspect、@Order,并定义Aspect通用的骨架(即上述我们说的模板类) * 创建一个MethodInterceptor接口的实现类,用于实现 代理逻辑 以及 多个Aspect时序 * 创建一个类,专门用于把代理逻辑织入到被代理对象从而创建出代理对象,并覆盖IOC容器的原对象 #### 创建AOP的注解@Aspect、@Order,并定义Aspect通用的骨架(即上述我们说的模板类) * 注解: 我们此处的@Aspect与Spring中的不同,Spring是用@Pointcut来指定切入点的,而我们为了方便,**直接在@Aspect中指定哪些类要被作为切入点。** @Aspect ```java @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) public @interface Aspect { /** * 被 该注解 标注的类就是需要被织入代理逻辑的切入点 * @return */ Class value(); } ``` @Order ```java @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) public @interface Order { /** * 优先级, * @return */ int value(); } ``` * 骨架DefaultAspect: ```java public abstract class DefaultAspect { /** * 事前 * @param targetClass * @param method * @param args */ public void before(Class targetClass, Method method, Object[] args) throws Throwable {} /** * 事后拦截 * @param targetClass * @param method * @param args * @param returnValue * @return */ public Object afterReturning(Class targetClass,Method method,Object[] args,Object returnValue) throws Throwable { return returnValue; } /** * * @param targetClass * @param method * @param args * @param e * @throws Throwable */ public void afterThrowing(Class targetClass,Method method,Object[] args,Throwable e) throws Throwable {} } ``` #### 创建一个MethodInterceptor接口的实现类,用于实现 代理逻辑 以及 多个Aspect时序 * 在实现流程中有分析: **一个切入点,一个MethodInterceptor接口的实现类,可能有多个Aspect** **所以,思路很明确,我们应该额外定义一个个独立的Aspect,例如:日志记录Aspect,事务Aspect等,然后通过一个一个MethodInterceptor接口的实现类用一个List来存储这些Aspect,从而就能实现 代理逻辑 以及 上述的时序执行逻辑** 所以,该实现类内部必须有一个List存储这些Aspect,同时我们说多个Aspect之间应该按时序执行,所以,我们必须把 Aspect的Order值与其关联起来,进行排序。 即:AspectInfo: ```java public class AspectInfo { private int orderIndex; private DefaultAspect aspectObject; } ``` * 综上,MethodInterceptor接口的实现类应该如下实现: * 成员变量是:被代理类:targetClass ; Aspect列表List * 方法: * 要对List 进行Order的排序 * 要按照之前规定的多个Aspect的执行时序,来依次执行这些Aspect的before、afterReturning、afterThrowing方法 * 代码实现: ```java public class AspectListExecutor implements MethodInterceptor { //被代理的类 private Class targetClass; //排好序的Aspect列表 @Getter private List sortedAspectInfoList; public AspectListExecutor(Class targetClass, List aspectInfoList){ this.targetClass = targetClass; this.sortedAspectInfoList = sortAspectInfoList(aspectInfoList); } /** * 按照order的值进行升序排序,确保order值小的aspect先被织入 * * @param aspectInfoList * @return */ private List sortAspectInfoList(List aspectInfoList) { Collections.sort(aspectInfoList, new Comparator() { @Override public int compare(AspectInfo o1, AspectInfo o2) { //按照值的大小进行升序排序 return o1.getOrderIndex() - o2.getOrderIndex(); } }); return aspectInfoList; } // 核心代理逻辑 @Override public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable { Object returnValue = null; if(ValidationUtil.isEmpty(sortedAspectInfoList)){ returnValue = methodProxy.invokeSuper(proxy, args); return returnValue; } // 1) 按照order的顺序升序执行完所有Aspect的before方法 invokeBeforeAdvices(method, args); try{ // 2)执行被代理类的方法 returnValue = methodProxy.invokeSuper(proxy, args); // 3)如果被代理方法正常返回,则按照order的顺序降序执行完所有Aspect的afterReturning方法 returnValue = invokeAfterReturningAdvices(method, args, returnValue); } catch (Exception e){ // 4) 如果被代理方法抛出异常,则按照order的顺序降序执行完所有Aspect的afterThrowing方法 invokeAfterThrowingAdvices(method, args, e); } return returnValue; } //4.如果被代理方法抛出异常,则按照order的顺序降序执行完所有Aspect的afterThrowing方法 private void invokeAfterThrowingAdvices(Method method, Object[] args, Exception e) throws Throwable { for (int i = sortedAspectInfoList.size() - 1; i >=0 ; i--){ sortedAspectInfoList.get(i).getAspectObject().afterThrowing(targetClass, method, args, e); } } //3.如果被代理方法正常返回,则按照order的顺序降序执行完所有Aspect的afterReturning方法 private Object invokeAfterReturningAdvices(Method method, Object[] args, Object returnValue) throws Throwable { Object result = null; for (int i = sortedAspectInfoList.size() - 1; i >=0 ; i--){ result = sortedAspectInfoList.get(i).getAspectObject().afterReturning(targetClass, method, args, returnValue); } return result; } //1.按照order的顺序升序执行完所有Aspect的before方法 private void invokeBeforeAdvices(Method method, Object[] args) throws Throwable { for(AspectInfo aspectInfo : sortedAspectInfoList){ aspectInfo.getAspectObject().before(targetClass, method, args); } } } ``` #### 创建一个类,专门用于把代理逻辑织入到被代理对象从而创建出代理对象,并覆盖IOC容器的原对象 * 前情回顾: * 我们已经有了Aspect的模板类DefaultAspect,则用户定义的Aspect必须实现该模板类,并根据需要实现其before、afterReturning、afterThrowing方法 * 我们已经有了@Aspect注解, 通过该注解的值, 指定**该值对应的注解所标注的类,就是我们的被代理类** * 通过@Order的值, 来指定该Aspect的优先级 * 我们已经有了一个切入点所对应的一个MethodInterceptor接口的实现类AspectListExecutor,并帮我们实现了同一个切入点的Aspect列表的时序控制。 * 实现流程 如今, 我们就是要借助上面的已经实现的类来实现AOP,我们可以采用逆序思维去考虑流程: * 首先,Enhancer要用AspectListExecutor来生成代理对象,那么我们就必须得对应一个切入点,有一个AspectListExecutor * 而一个AspectListExecutor里面有一个targetClass,一个List * 那么,对于List,我们可以从IOC容器中获得被@Aspect所标注的类,即:Aspect类,但是一个项目里有这么多Aspect, 我们肯定得先建立一个Map,把@Aspect注解里面的值相同的Aspect封装为AspectInfo然后放在同一个List里面 * 这样一来,List就有了,至于targetClass,我们要注意:**@Aspect里面的值,虽然是个class,但是并不是我们的targetClass,即:不是我们的被代理类Class对象,而只是一个 注解,例如@Controller、@Service等**, 所以, 我们得通过该注解, 从IOC容器中获得所有的被该注解标注的类的集合,从而遍历集合,这样一来,targetClass也有了, 我们就可以生成AspectListExecutor,然后通过Enhancer生成targetClass的代理类对象了 * 然后,我们就可以把这个代理类对象覆盖掉IOC容器中的原对象。至此AOP功能就完成了 所以,顺着来看,我们的实现流程如下: * 通过IOC容器获得所有的被@Aspect所标注的类即:Aspect类 的集合 * 然后,我们遍历上述Aspect类集合,根据 其@Aspect注解中的value(即:一个注解),将其分类,从而得到一个Map<注解, List * 从而,我们就可以遍历该Map<注解, List的keySet, 通过key:注解来获得IOC容器中所有被该注解修饰的类集合,遍历该类集合,对于每一个targetClass, 以及Map中该注解对应的List, 生成一个AspectListExecutor, 从而由Enhancer生成targetClass的代理类对象. 把该代理类对象存入IOC容器中覆盖原对象. * 代码实现: AspectWeaver: ```java /** * 实现AOP核心功能实现类 */ public class AspectWeaver { // IOC容器 private BeanContainer beanContainer; public AspectWeaver() { this.beanContainer = BeanContainer.getInstance(); } /** * 执行AOP核心功能的方法 */ public void doAop() { // 1) 获取所有的切面类 Set> aspectSet = beanContainer.getClassesByAnnotation(Aspect.class); if(ValidationUtil.isEmpty(aspectSet)){return;} // 2) 遍历所有的切面类,对切面类进行分类 Map,List> categoryAspectMap = new HashMap<>(); for (Class aspectClass : aspectSet) { if (verifyAspect(aspectClass)){ categoryAspect(categoryAspectMap,aspectClass); } else { //不遵守规范则直接抛出异常 throw new RuntimeException("@Aspect and @Order must be added to the Aspect class, and Aspect class must extend from DefaultAspect"); } } if(ValidationUtil.isEmpty(categoryAspectMap)) return; // 3) 根据不同的织入目标,来进行织入 for (Class category : categoryAspectMap.keySet()) { weaveByCategory(category,categoryAspectMap.get(category)); } } private void weaveByCategory(Class category, List aspectInfoList) { // 1) 获得所有被category注解修饰的类集合 Set> targetClasses = beanContainer.getClassesByAnnotation(category); // 2) 遍历类集合,生成AspectListExecutor,并织入 for (Class targetClass : targetClasses) { AspectListExecutor aspectListExecutor = new AspectListExecutor(targetClass, aspectInfoList); Object proxyBean = ProxyCreator.createProxy(targetClass, aspectListExecutor); // 3) 将生成的代理对象,覆盖原来IOC容器中的原对象 beanContainer.addBean(targetClass,proxyBean); } } private void categoryAspect(Map, List> categoryAspectMap, Class aspectClass) { // 1) 获得该类上面修饰的Order注解的值 Order orderTag = aspectClass.getAnnotation(Order.class); Aspect aspectTag = aspectClass.getAnnotation(Aspect.class); // 2) 从IOC容器中获得该切面类 DefaultAspect defaultAspect = (DefaultAspect) beanContainer.getBean(aspectClass); // 3) 将order值与切面类疯转为AspectInfo AspectInfo aspectInfo = new AspectInfo(orderTag.value(), defaultAspect); // 4) 将该aspectInfo加入到categoryAspectMap对应的注解值中去 if(categoryAspectMap.containsKey(aspectTag.value())) { categoryAspectMap.get(aspectTag.value()).add(aspectInfo); } else { List aspectInfoList = new ArrayList<>(); aspectInfoList.add(aspectInfo); categoryAspectMap.put(aspectTag.value(),aspectInfoList); } } //框架中一定要遵守给Aspect类添加@Aspect和@Order标签的规范,同时,必须继承自DefaultAspect.class //此外,@Aspect的属性值不能是它本身 private boolean verifyAspect(Class aspectClass) { return aspectClass.isAnnotationPresent(Aspect.class) && aspectClass.isAnnotationPresent(Order.class) && DefaultAspect.class.isAssignableFrom(aspectClass); } } ``` ### AOP2.0版本: 具备更精细化的切入点的AOP1.0 #### AOP1.0的短板: 虽然我们的1.0版本基本实现了AOP的功能,但是我们是通过@Aspect里面的value 来指定 **一个注解, 即: 被该注解所标注的类就是切入点**. 这样很明显, 切入点的粒度太大了, 参考Spring的AOP, 是通过@Pointcut里面的表达式来实现方法级别的粒度的切入点的, 所以, **我们必须在AOP1.0的基础上, 实现更精细化的切入点** #### 怎么实现? 参考SpringAOP的发展史: 当年Spring在开发AOP时, 也面临了切入点粒度太大的问题, 秉着Spring一直以来的海纳百川的思想, 他们选择了 **集成AspectJ** **AspectJ框架: 提供了完整的AOP解决方案, 不仅包含注解、还包含ajc编译工具等,定义了切面语法以及切面语法的解析机制,提供了强大的织入工具,同时几乎支持所有级别的切入点,例如:方法级别、构造方法级别、成员变量级别等。** 但是,在Spring看来,其原先的 **JDK动态代理/CGLIB + IOC容器** 的AOP实现机制,已经足以实现AOP功能了, **他们缺少的仅仅只是更完美的切面语法** 因此, 出于节省用户的学习成本, 以及减少改造成本, **Spring只在框架中集成了AspectJ中的切面语法, 用于实现更精细化的切入点控制** 同时, **由于方法级别的切入点已经可以满足80%的Spring框架开发需求, 所以Spring也没有集成AspectJ的支持所有级别切入点的功能** 综上所述, 我们当前面临的处境其实是与Spring类似的,所以参考大公司的思路:我们也要使用最小的改造成本,换取最大的收益: * 仅仅引入**AspectJ的切面表达式和相关的解析机制** * **只需要让Pointcut更加灵活以及精细**,无需改变原有的AOP实现机制 #### AspectJ的两大核心类: * PointcutParser类: 通过其中的:parsePointcutExpression(String expression) 方法, 可以得到该字符串表达式对应的PointcutExpression类对象 * PointcutExpression类:可以通过其中的: * couldMatchJoinPointsInType(Class ) 判断当前targetClass是否符合表达式, 注意:该方法只支持within等某些表达式,不支持execution表达式(直接返回true),所以只能对Class进行粗筛 * matcherMethodExecution(Method) 判断当前method是否符合表达式 该方法支持execution表达式,可以定位到method级别,所以是精筛 ​ 考虑到性能问题,我们可以先进行一遍粗筛,把不符合表达式的Class先remove掉,之后再通过精筛去看看方法是否符合表达式 很明显,通过以上两个类,我们就可以实现 切面语法 以及 切面语法的解析了。 接下来,就是把上述两个类,整合到我们先前的逻辑里面了。 #### 实现流程: * 我们先前用**@Aspect**中的Class value 来指定注解, 如今为了方便植入AspectJ的pointcut逻辑,**我们可以把value改成String, 来指定Pointcut表达式** * 既然**如今我们已经是使用Pointcut表达式, 是精确到各个类本身以及其方法级别的, 而不是简单的注解标注以及其所有方法的级别了.所以, 我们无法再对@Aspect进行分类为一个个List, 来表示他们是对一类Aspect了,然后对该注解下面的所有类都简单地直接传入AspectListExecutor进行创建** 所以, **我们应该每个Aspect有自己的Pointcut的解析类, 来判断当前这个targetClass以及其method是否符合当前Aspect(即: 先粗筛后精筛),只有符合,才把该Aspect留到AspectListExecutor最后执行时序逻辑的那个List里面去进行织入** 综上, 我们的实现流程如下: * @Aspect中的value改成String类型, 来作为Pointcut表达式 * 创建PointcutLocator类, 使用上述AspectJ的两大核心类用于提供 **对应的一个Pointcut表达式的判断机制** * 把@Order的value值、DefaultAspect对象、PointcutLocator对象,组合成一个AspectInfo类 即:**一个Aspect,对应一个Pointcut表达式,对应一个PointcutLocator进行判断** * 实现AOP, 即: 在原doAOP中: * 先从IOC容器中获得所有的DefaultAspect类对象,对每一个DefaultAspect, 通过其@Aspect注解的Pointcut表达式,生成PointcutLocator,组合成一个AspectInfo对象 从而得到一个List。 * 从IOC容器中获得所有的Class类对象(除了DefaultAspect),对这些类对象进行遍历, 对每一个targetClass: * 遍历List,通过AspectInfo中的PointcutLocator的粗筛,判断该Aspect是否是该targetClass的切面,如果是存到一个新的List里面 * 以上遍历结束,则就会得到一个粗筛之后的List * 但是还得进行精筛,精筛是需要Method的,只有AspectListExecutor类中的intercept方法才会有这个targetClass中的一个个方法,从而把这个List以及targetClass传入**AspectListExecutor中,在intercept方法中进行精筛**: **遍历List,通过AspectInfo中的PointcutLocator的精筛,判断该Aspect是否是该Method的切面,如果不是那就remove掉。** 此时注意: 之前就说过:一个targetClass是对应一个AspectListExecutor,而Pointcut表达式是精细到方法级别的,在AspectListExecutor中的sortedList只是代表这些Aspect是该targetClass的切面,并不意味着这些Aspect是该targetClass所有方法的切面,所以要进行精筛把不是该Method的Aspect给去除掉然后再执行时序逻辑。但是同时,一个AspectListExecutor只对应一个sortedList,如果我们直接对该sortedList进行remove,那么其他Method很有可能就会失去他们的Aspect,举个例子: Aspect1是方法A的切面,Aspect2是方法B的切面,两个都是targetClass的切面,所以被存入了同一个AspectListExecutor中的sortedList。 当我们的方法A被调用时,intercept方法会拦截同时进行代理,此时根据上述逻辑,我们遍历Aspect1和Aspect2,会发现Aspect2并不是方法A的切面,如果此时我们直接对原sortedList进行remove,那么当方法B被调用时,Aspect2就无了,就无法实现代理了。 **所以此时我们要考虑不在原sortedList上面操作,而是考虑使用一个副本(可以把逻辑改成: 如果是就加入到一个新List,从而把这个新List传入各个方法里面执行时序逻辑)来执行时序逻辑。此时sortedList的语义就发生了变化,其只是代表着是targetClass的切面,至于之后Method的时序控制,得对精筛后的list来进行** * 至此,就有了AspectListExecutor了, 就可以通过Enhancer进行创建动态代理对象了, 从而就可以覆盖IOC容器中的原对象了 #### 代码实现: 根据上述流程,对1.0代码进行修改或重构 * @Aspect: ```java @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) public @interface Aspect { /** * 通过表达式表示切入点 * @return */ String pointcut(); /** * 被 该注解 标注的类就是需要被织入代理逻辑的切入点 * @return */ // Class value(); } ``` * PointcutLocator类: ```java package org.simpleframework.aop; import org.aspectj.weaver.tools.PointcutExpression; import org.aspectj.weaver.tools.PointcutParser; import org.aspectj.weaver.tools.ShadowMatch; import java.lang.reflect.Method; /** * 解析Aspect表达式并且定位被织入的目标 */ public class PointcutLocator { /** * Pointcut解析器,直接给它赋值上Aspectj的所有表达式,以便支持对众多表达式的解析 */ private PointcutParser pointcutParser= PointcutParser.getPointcutParserSupportingSpecifiedPrimitivesAndUsingContextClassloaderForResolution( PointcutParser.getAllSupportedPointcutPrimitives() ); /** * 表达式解析器 */ private PointcutExpression pointcutExpression; public PointcutLocator(String expression){ this.pointcutExpression = pointcutParser.parsePointcutExpression(expression); } /** * 判断传入的Class对象是否是Aspect的目标代理类,即匹配Pointcut表达式(初筛) * * @param targetClass 目标类 * @return 是否匹配 */ public boolean roughMatches(Class targetClass){ // couldMatchJoinPointsInType比较坑,只能校验within // 不能校验 (execution(精确到某个类除外), call, get, set),面对无法校验的表达式,直接返回true return pointcutExpression.couldMatchJoinPointsInType(targetClass); } /** * 判断传入的Method对象是否是Aspect的目标代理方法,即匹配Pointcut表达式(精筛) * @param method * @return */ public boolean accurateMatches(Method method){ ShadowMatch shadowMatch = pointcutExpression.matchesMethodExecution(method); if(shadowMatch.alwaysMatches()){ return true; } else{ return false; } } } ``` * AspectInfo类: ```java @AllArgsConstructor @Getter public class AspectInfo { private int orderIndex; private DefaultAspect aspectObject; private PointcutLocator pointcutLocator; } ``` * AspectListExecutor类: ```java public class AspectListExecutor implements MethodInterceptor { //被代理的类 private Class targetClass; //排好序的Aspect列表 @Getter private List sortedAspectInfoList; public AspectListExecutor(Class targetClass, List aspectInfoList){ this.targetClass = targetClass; this.sortedAspectInfoList = sortAspectInfoList(aspectInfoList); } /** * 按照order的值进行升序排序,确保order值小的aspect先被织入 * * @param aspectInfoList * @return */ private List sortAspectInfoList(List aspectInfoList) { Collections.sort(aspectInfoList, new Comparator() { @Override public int compare(AspectInfo o1, AspectInfo o2) { //按照值的大小进行升序排序 return o1.getOrderIndex() - o2.getOrderIndex(); } }); return aspectInfoList; } // 核心代理逻辑 @Override public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable { Object returnValue = null; // 1) 对sortedAspectInfoList进行精筛, 并把该方法的切面存入到一个新的List中 List aspectInfoForCurMethodList = new ArrayList<>(); for (AspectInfo aspectInfo : sortedAspectInfoList) { if(aspectInfo.getPointcutLocator().accurateMatches(method)) aspectInfoForCurMethodList.add(aspectInfo); } // 注意: 如果该方法没有切面, 那就直接调用其原方法, 而不是不调用 if(ValidationUtil.isEmpty(aspectInfoForCurMethodList)){ returnValue = methodProxy.invokeSuper(proxy, args); return returnValue; } // 2) 按照order的顺序升序执行完所有Aspect的before方法 invokeBeforeAdvices(aspectInfoForCurMethodList,method, args); try{ // 3)执行被代理类的方法 returnValue = methodProxy.invokeSuper(proxy, args); // 4)如果被代理方法正常返回,则按照order的顺序降序执行完所有Aspect的afterReturning方法 returnValue = in vokeAfterReturningAdvices(aspectInfoForCurMethodList,method, args, returnValue); } catch (Exception e){ // 5) 如果被代理方法抛出异常,则按照order的顺序降序执行完所有Aspect的afterThrowing方法 invokeAfterThrowingAdvices(aspectInfoForCurMethodList,method, args, e); } return returnValue; } //4.如果被代理方法抛出异常,则按照order的顺序降序执行完所有Aspect的afterThrowing方法 private void invokeAfterThrowingAdvices(List aspectInfoForCurMethodList,Method method, Object[] args, Exception e) throws Throwable { for (int i = aspectInfoForCurMethodList.size() - 1; i >=0 ; i--){ aspectInfoForCurMethodList.get(i).getAspectObject().afterThrowing(targetClass, method, args, e); } } //3.如果被代理方法正常返回,则按照order的顺序降序执行完所有Aspect的afterReturning方法 private Object invokeAfterReturningAdvices(List aspectInfoForCurMethodList,Method method, Object[] args, Object returnValue) throws Throwable { Object result = null; for (int i = aspectInfoForCurMethodList.size() - 1; i >=0 ; i--){ result = aspectInfoForCurMethodList.get(i).getAspectObject().afterReturning(targetClass, method, args, returnValue); } return result; } //1.按照order的顺序升序执行完所有Aspect的before方法 private void invokeBeforeAdvices(List aspectInfoForCurMethodList,Method method, Object[] args) throws Throwable { for(AspectInfo aspectInfo : aspectInfoForCurMethodList){ aspectInfo.getAspectObject().before(targetClass, method, args); } } } ``` * AspectWeaver类: ```java package org.simpleframework.aop; import org.simpleframework.aop.annotation.Aspect; import org.simpleframework.aop.annotation.Order; import org.simpleframework.aop.aspect.AspectInfo; import org.simpleframework.aop.aspect.DefaultAspect; import org.simpleframework.core.BeanContainer; import org.simpleframework.util.ValidationUtil; import java.lang.annotation.Annotation; import java.util.*; /** * 实现AOP核心功能实现类 */ public class AspectWeaver { // IOC容器 private BeanContainer beanContainer; public AspectWeaver() { this.beanContainer = BeanContainer.getInstance(); } /** * 执行AOP核心功能的方法 */ public void doAop() { // 1) 获取所有的切面类 Set> aspectSet = beanContainer.getClassesByAnnotation(Aspect.class); if(ValidationUtil.isEmpty(aspectSet)) return; // 2) 把所有的切面类都封装为AspectInfo, 从而得到List List aspectInfoList = packAspectSetToAspectInfoList(aspectSet); // 3) 从IOC容器中获得所有的Class, Set> classes = beanContainer.getClasses(); for (Class targetClass : classes) { if(targetClass.isAnnotationPresent(Aspect.class)) continue; // 4) 对除了@Aspect修饰的以外, 都要作为targetClass进行切面筛选=>粗筛 List roughMatchesList = roughMatches(targetClass,aspectInfoList); // 5) 粗筛后的roughMatchesList, 说明该List都是targetClass的切面, 所以要进行精筛 // 由于精筛在aspectListExecutor里面, 所以该方法可以直接织入 weaveIfNecessary(targetClass,roughMatchesList); } } private void weaveIfNecessary(Class targetClass, List roughMatchesList) { // 通过两个参数生成aspectListExecutor AspectListExecutor aspectListExecutor = new AspectListExecutor(targetClass,roughMatchesList); // 从而创建动态代理对象 Object proxyBean = ProxyCreator.createProxy(targetClass, aspectListExecutor); // 覆盖IOC容器中的原对象 beanContainer.addBean(targetClass,proxyBean); } private List roughMatches(Class targetClass, List aspectInfoList) { List roughMatchesList = new ArrayList<>(); // 对当前targetClass进行粗筛 for (AspectInfo aspectInfo : aspectInfoList) { if (aspectInfo.getPointcutLocator().roughMatches(targetClass)) { roughMatchesList.add(aspectInfo); } } return roughMatchesList; } private List packAspectSetToAspectInfoList(Set> aspectSet) { List aspectInfoList = new ArrayList<>(); for (Class aspect : aspectSet) { if(verifyAspect(aspect)) { Order orderTag = aspect.getAnnotation(Order.class); Aspect aspectTag = aspect.getAnnotation(Aspect.class); // 获得当前Aspect对应的defaultAspect对象 DefaultAspect defaultAspect = (DefaultAspect) beanContainer.getBean(aspect); // 获得当前Aspect对应的pointcutLocator对象 PointcutLocator pointcutLocator = new PointcutLocator(aspectTag.pointcut()); // 封装为AspectInfo对象 AspectInfo aspectInfo = new AspectInfo(orderTag.value(),defaultAspect,pointcutLocator); aspectInfoList.add(aspectInfo); } else { //不遵守规范则直接抛出异常 throw new RuntimeException("@Aspect and @Order must be added to the Aspect class, and Aspect class must extend from DefaultAspect"); } } return aspectInfoList; } //框架中一定要遵守给Aspect类添加@Aspect和@Order标签的规范,同时,必须继承自DefaultAspect.class //此外,@Aspect的属性值不能是它本身 private boolean verifyAspect(Class aspectClass) { return aspectClass.isAnnotationPresent(Aspect.class) && aspectClass.isAnnotationPresent(Order.class) && DefaultAspect.class.isAssignableFrom(aspectClass); } } ``` ### 总结: ![image-20220608135201455](mdImage/image-20220608135201455.png) 至此,我们的AOP核心功能就实现了,其实主要分为以下三部分: * 注解:@Aspect用于标注Aspect类以及该Aspect的切入点,@Order用于指定该Aspect的时序优先级 * DefaultAspect抽象类:通过钩子函数的方式,给所有Aspect进行一个模板定义,Aspect必须继承该类 * AspectExecutor类:实现了CGLIB的MethodInterceptor接口, 用于实现对同一个targetClass进行切入的切面类的时序逻辑, 并交由Enhancer创建代理对象 * PointcutLocator类:集成了AspectJ两个核心类:PointcutParser以及PointcutExpression,从而可以对targetClass以及method判断是否是该切面的切入点 * AspectWeaver类:整合上述类,执行AOP核心逻辑,实现AOP功能 同时,我们的逻辑是: **一个切面,对应一个DefaultAspect类、对应一个@Aspect指定的pointcut,对应一个PointcutLocator类** **一个动态代理对象,对应一个targetClass,对应一个AspectExecutor类,对应多个切面,且被代理的方法均有所不同** 以上,基本就是AOP的总结。 那么,我们如何在项目中**初始化或启动 IOC与AOP功能呢?**,只需执行以下代码: ```java // 1) 先加载IOC容器 BeanContainer beanContainer = BeanContainer.getInstance(); beanContainer.loadBeans("com.mlming"); // 2) 再AOP代理,把该代理的Bean均代理了,并覆盖原Bean new AspectWeaver().doAop(); // 3) 最后依赖注入 new DependencyInjector().doIOC(); ``` 之后就可以通过IOC容器无感地使用代理对象了。