其他分享
首页 > 其他分享> > 【总结】自研Spring框架的实现总结

【总结】自研Spring框架的实现总结

作者:互联网

实例的创建

1、创建注解@Controller、@Service、@Repository、@Component

2、提取标记对象

实现思路

◆指定范围,获取范围内的所有类
◆遍历所有类,获取被注解标记的类并加载进容器里

怎么根据包名获取类集合?

创建一个方法extractPacakgeClass,利用类加载器获取资源url,再根据协议名过滤出文件类型的资源,遍历目录获得class文件,再使用反射获取对应的Class对象添加到集合中。

extractPacakgeClass里面需要完成的事情

◆获取到类的加载器
◆通过类加载器获取到加载的资源信息
◆依据不同的资源类型,采用不同的方式获取资源的集合

Bean容器的实现与加载

导读:

因为需要同一个容器将所有需要管理的对象给管理起来,所以容器需要用单例来实现。

确保一个类只有一个实例,并对外提供统一访问方式。客户端不需要同时也无法实例化该类的对象。

实现:

构造容器我们使用线程安全的能抵御反射和序列化的枚举型的单例模式。

容器的组成部分

◆保存Class对象及其实例的载体
◆容器的加载 (需要定义配置获取并过滤目标对象的方法)

◆容器的操作方式

我们使用concurrentHashMap作为容器保存class对象和实例的载体。

之所以用concurrentHashMap,是因为它支持并发并且并发性能很好,在jdk1.8进行了巨大的变动,摒弃了分段锁,利用CAS+红黑树细化了锁的粒度,进一步提高并发性能。

并非所有class的对象都是容器管理的对象 ,而是从中选取配置里指定的class对象,也就是注解标记的对象,才将其存入载体并管理。

容器的加载:

实现思路

◆配置的管理与获取(如何将注解管理起来,随时读取目标注解,得到被它标记的类)
◆获取指定范围内的Class对象

◆依据配置提取Class对象,连同实例一-并存入容器

具体实现:

1、先根据标志判断容器是否加载过。

2、先根据包名获取所有的类集合。

3、遍历得到的类集合,将添加了注解需要被spring管理起来的类,以类本身为key,类实例为v,添加到map容器中。

4、将加载标志置为true。

容器的操作方式

实现容器的操作方式
涉及到容器的增删改查

◆增加、删除操作(操作map集合的增删)
◆通过注解来获取被注解标注的Class(即所有的key)

clazz.isAnnotationPresent(annotation)

◆根据Class获取对应实例(通过key得到v)

◆通过超类获取对应的子类Class(断类是否传入参数的子类,并去除本身)

interfaceOrClass.isAssignableFrom(clazz) && !clazz.equals(interfaceOrClass)
    
A.isAssignableFrom(B); //判断A是不是B的父类,还可以看AB两者是否是本身一样的或者有继承关系,不仅局限于父子类、实现类、还有祖孙 
父类.isAssignableFrom(子类);
接口.isAssignableFrom(实现类);

◆获取所有的Bean实例集合

beanMap.values();

◆获取容器载体保存Class的数量(map的size)

总结实现IoC容器:

先是定义注解标签(@Component、@Controller、@Service、@Repository),实现了将指定的packageName下的被上述注解标记的class对象和对象相关的实例以键值对的形式保存到容器的ConcurrentHashMap类型的成员变量beanMap里,以实现容器的初始化。此外为保证容器实例的唯一性,通过枚举的手段实现了抵御反射和序列化的安全单例。从这里可以看出咱们框架管理的Bean都是单例的,并且是非延时加载的(主要是因为实现简单且能满足大多的业务需求)。

依赖注入

了解Autowired的定义及使用:

@Target(ElementType.FIELD) //仅支持在成员变量上使用
@Retention(RetentionPolicy.RUNTIME)
public @interface Autowired {
    String value() default "";
}
@Autowired(value = "HeadLineServiceImpl")//在使用的时候指定成员变量要注入的实现类
private HeadLineService headLineService;

IoC逻辑:

1、遍历Bean容器中所有的Class对象

2、遍历Class对象的所有成员变量

Field[] fields = clazz.getDeclaredFields();

3、找出被注解Autowired标记的成员变量(isAnnotationPresent)

4、获得注解里面的属性和成员变量的类型

5、根据成员变量的类型获取在容器里对应的实例或者实现类

//根据Class在beanContainer里获取其实例或者实现类
//如果直接根据传入的类对象找到了容器中对应的实例,说明传入的是类。如果在容器中根据class没有得到对应的实例,有两种情况:1、传入的是接口,bean里面存的是实现类  2、就是没有

Object fieldValue=getFileInstance(fieldClass,autowiredValue);
//这时就需要定义一个方法来根据接口获得实现类(直接调用上面是实现的容器的操作:通过接口或者父类获取类或者子类的Class集合),获得集合后,如果集合长度大于1且注解属性为空未指定实现类名称就会报错。指定了就去遍历类集合寻找对应class并返回。

6、通过反射将对应的成员变量实例注入到成员变量所在类的实例

field.set(targetBean,value);

总结

先定义@Autowired注解,实现被该注解依赖注入的逻辑,之后调用doIoC方法去处理已经加载进来的bean实例里面被@Autowired标记的属性,针对这些属性调用getFieldInstance方法去获取这些属性在bean容器里面对应的bean实例,还支持根据接口获取对应的实现类。最后将获取到的实例通过反射注入到成员变量所在类的实例。

AOP的实现

因为CGLIB动态代理不要求被代理类实现接口相对灵活,所以我们采用CGLIB的方式实现SpringAOP

思路:

◆解决标记的问题(识别Aspect以及Advice),定义横切逻辑的骨架

◆定义Aspect横切逻辑以及被代理方法的执行顺序

◆将横切逻辑织入到被代理的对象以生成动态代理对象

前提准备工作:

1、定义注解Aspect(属性为切入点pointcut表达式)和Order,用来标记切面类及切面类执行的顺

2、将通知Advice(前置通知、正确返回结果的通知、异常通知)设置为方法封装在一个抽象类里

3、定义一个类去解析Aspect表达式并且定位被织入的目标

利用AspectJ的切面表达式和相关的定位解析机制

Pointcut解析器

直接给他赋值上Aspectj的所有表达式,以便支持对众多表达式的解析.

PointcutParser实例是需要被创建出来并且装配上相关的语法树,才能识别注解Aspect属性里面的pointcut表达式

private PointcutParser pointcutParser=PointcutParser.getPointcutParserSupportingSpecifiedPrimitivesAndUsingContextClassloaderForResolution(
      PointcutParser.getAllSupportedPointcutPrimitives()
);

表达式解析器

PointcutExpression:是PointcutParser根据表达式解析出来的产物,用来判断某个类或者方法是否匹配pointcut表达式。

public PointcutLocator(String expression){
//在构造函数中利用AspectJ的切面表达式解析机制给成员变量赋值
  this.pointcutExpression=pointcutParser.parsePointcutExpression(expression);
}

定位

根据AspectJ的定位解析机制进行粗筛和细筛

//粗筛:判断传入的Class对象是否是Aspect的目标代理类,即匹配PointCut表达式(初筛)
pointcutExpression.couldMatchJoinPointsInType(targetClass);

//细筛:断传入的Method对象是否是Aspect的目标代理方法,及匹配Pointcut表达式(精筛)
//完全匹配:alwaysMatches
pointcutExpression.matchesMethodExecution(method).alwaysMatches();

4、将切面类的执行顺序order、通知抽象类、解析表达式并定位类封装在一个类中(方便管理操作)

5、定义一个类实现MethodInterceptor接口(作用是定义横切逻辑Aspect

这个类用于对每个被代理的对象进行方法拦截

5.1、先对切面类集合根据order属性进行升序排列,确保order值小的切面类先织入。

5.2、重写MthodInterceptor接口里面的intercept方法

intercept横切逻辑执行:

1、首先对排序好的初筛后的切面类集合进行精筛,不符合条件的类从切面类集合中移除

2、如果切面类集合变为空了之后只执行自身的方法

returnValue=methodProxy.invokeSuper(proxy,args);

3、按照order的顺序升序执行所有Aspect的before方法

invokeBeforeAdvices(method,args);

4、执行被代理类的方法

returnValue=methodProxy.invokeSuper(proxy,args);

5、如果被代理方法正常返回,则按照order的顺序降序执行完所有Aspect的after方法

returnValue=invokeAfterReturningAdvices(method,args,returnValue);

6、如果被代理方法抛出异常,则按照order的顺序降序执行

invokeAfterThrowingAdvices(method,args,e);

因为切面类集合本来就是按照order按照升序排序好的,上面的降序执行直接让集合从后向前遍历即可。

创建代理类

利用CGLIB的Enhancer创建代理类

public static Object createProxy(Class<?> targetClass, MethodInterceptor methodInterceptor){
    return  Enhancer.create(targetClass,methodInterceptor);
}

横切逻辑织入

doAOP

1、在容器中根据注解获得获取所有的切面类集合

2、封装AspectInfo类

将注解Order属性顺序值、通知抽象类对象实例、解析Apect表达式且定位织入目标类的对象实例封装到AspectInfo类里面。

3、遍历容器里的类

4、粗筛符合条件的切面类

5、进行Apect横切逻辑的织入(创建动态代理对象执行)

private void wrapIfNecessary(List<AspectInfo> roughMatchedAspectList, Class<?> targetClass) {
    if(ValidationUtil.isEmpty(roughMatchedAspectList)){return;}
    //创建动态代理对象
    AspectListExecutor aspectListExecutor=new AspectListExecutor(targetClass,roughMatchedAspectList);
    Object proxyBean = ProxyCreator.createProxy(targetClass, aspectListExecutor);
    beanContainer.addBean(targetClass,proxyBean);
}

代码

public void doAop(){
    //1、获取所有的切面类
    Set<Class<?>> aspectSet = beanContainer.getClassesByAnnotation(Aspect.class);
    if(ValidationUtil.isEmpty(aspectSet)){return;}//判空处理
    //2、拼接AspectInfoList
    List<AspectInfo> aspectInfoList=packAspectInfoList(aspectSet);
    //3、遍历容器里的类
    Set<Class<?>> classSet = beanContainer.getClasses();
    for (Class<?> targetClass : classSet) {
        //排除AspectClass自身
        if(targetClass.isAnnotationPresent(Aspect.class)){
            continue;
        }
        //4、粗筛符合条件的Aspect
        List<AspectInfo> roughMatchedAspectList=collectRoughMatchedAspectListForSpecificClass(aspectInfoList,targetClass);
        //5、尝试进行Aspect的织入
        wrapIfNecessary(roughMatchedAspectList,targetClass);
    }

MVC实现

image-20220205143934718

大致流程

获取http请求和需要回发的http响应对象,之后将他们委托给RequestProcessorChain处理。我们只处理get和post方法的请求,RequestProcessorChain参照的是责任链模式的后置处理器的处理逻辑,里面保存了处理RequestProcessor接口的多个不同的实现类,之所以会有多个不同的实现类对应为DispatcherServlet是项目里面所有请求的唯一入口。这些请求里即会有获取jsp页面的请求,也会有获取静态资源的请求、直接获取json数据的请求等,针对不同的请求会使用不同的RequestProcessor来处理。

DispatcherServlet实现

继承HttpServlet,重写init和service方法

init() 初始化

servlet是程序执行的入口:对容器进行初始化并将相关的bean加载进来,同时完成AOP相关逻辑的织入,以及相关的IoC依赖注入操作。同时为了后面能够采用责任链模式实现RequestProcessor矩阵,需要将对应的处理器添加到处理器列表中。

将请求处理器按照PreRequestProcessor、StaticResourceRequestProcessor、JspRequestProcessor、ControllerRequestProcessor进行添加。因为我们的请求经过编码和路径的处理之后再进行后续的处理。将ControllerRequestProcessor放到最后因为它的处理比较耗时,需要将请求和controller的方法实例进行匹配。

service方法实现

责任链模式(职责链模式)详解

1,创建责任链对象实例

RequestProcessorChain requestProcessorChain = new RequestProcessorChain(PROCESSOR.iterator(), req, resp);

2,通过责任链模式来依次调用请求处理器对请求进行处理

requestProcessorChain.doRequestProcessorChain();

2.1,通过迭代器遍历注册的请求处理器实现类列表

2.2,直到某个请求处理器执行后返回为false为止

2.3,期间如果出现异常,则交由内部异常渲染器处理

3,对处理结果进行渲染

3.1,如果请求处理器实现类均为选择合适的渲染器,则使用默认的

3.2,调用渲染器的render方法对结果进行渲染

ControllerRequestProcessor的实现

Controller请求处理器

功能:

◆针对特定请求,选择匹配的Controller方法进行处理
◆解析请求里的参数及其对应的值,并赋值给Controller方法的参娄
◆选择合适的Render ,为后续请求处理结果的渲染做准备

前提准备:

将请求方法和请求路径封装到RequestPathInfo类里。将Controller对应的Class对象,执行的Controller方法实例,方法参数名称以及对应的参数类型封装到ControllerMethod类里。两者以KV的形式组成映射表。

//请求和controller方法的映射集合
private Map<RequestPathInfo, ControllerMethod> pathControllerMethodMap = new ConcurrentHashMap<>();
实现:

1、依靠容器的能力,建立起请求路径、请求方法与Controller方法实例的映射

1,遍历所有被@RequestMapping修饰的类

2、获得注解属性也就是一级路径

3、通过反射获得类中所有方法数组,遍历得到被@RequestMapping修饰的方法

Method[] methods = requestMappingClass.getDeclaredMethods();

4、获得注解属性,即二级路径。一级路径和二级路径拼接得到url

5、解析方法里被@RequestParam标记的参数,将注解属性方法参数名称,参数类型以kv的形式存到map里面。

(我们为了实现简单规定在被注解@RequestMapping标记的方法,如有参数必须用注解@RequestParam标记)

6、将获取到的信息封装到映射表中

重写请求执行器RequestProcessor 里面的process方法

1、解析HttpServletRequest的请求方法、请求路径,封装为RequestPathInfo,再去映射表中获取对应的ControllerMethod

2、解析请求参数,并传递给获取到的controllerMethod 实例去执行

Object result = invokeControllerMethod(controllerMethod, requestProcessorChain.getRequest());

1)从请求里获取get或者post的参数名及其对应的值

2)获得controllerMethod里面的参数和参数的映射关系,遍历集合,将请求路径中的参数值转为controller方法里面参数所需要的类型。最后将转化后的值添加到方法参数集合中。

3)利用反射invoke执行controller里面对应的方法并返回结果

3、根据处理的结果,选择对应的render进行渲染

(根据不同的情况设置不同的渲染器)

判断方法是否被注解ResponseBody修饰(isAnnotationPresent),是就设置为JsonResultRender,不是就设置为ViewResultRender。

标签:总结,容器,请求,自研,Spring,获取,实例,注解,方法
来源: https://blog.csdn.net/qq_43430343/article/details/122791444