Java教程

用java实现纯注解Spring框架(包括IOC,AOP,定时器,javaWeb)

本文主要是介绍用java实现纯注解Spring框架(包括IOC,AOP,定时器,javaWeb),对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!

用Java实现纯注解Spring框架[包括IOC,AOP,定时器,javaWeb后端]

  • 前言
    • 1、手写Spring框架的意义
    • 2、该框架的实现内容
  • 一、源码下载地址(github)
  • 二、IOC--源码及其实现原理
    • 1、该框架IOC的实现原理
    • 2、注入一个Bean的全过程
  • 三、AOP--源码及其实现原理
    • 1、该框架AOP的实现原理
    • 2、AOP的测试Demo
  • 四、该框架对IOC和AOP整合实现原理及源码
    • IOC和AOP整合的实现原理
  • 五、定时器--源码及其实现原理
    • 1、定时器的原理
    • 2、定时器的源码
  • 六、JavaWeb(类似SpringBoot)--源码及其实现原理
    • 1、JavaWeb后端的实现原理
    • 2、演示示例
  • 七、如何使用该框架
  • 总结


前言

1、手写Spring框架的意义

  • 各个公司的框架几乎都离不开Spring框架,众所周知Spring框架几乎现在已经成为了Java事实上的标准,那么Spring框架到底为我们做了什么。
  • 不论是什么项目,为什么Spring可以做到不侵入,只需要引入该框架,Spring就可以帮你完成很多的东西,几乎所有场景都可以使用Spring框架。
  • 为什么Spring在进行配置文件编写的时候,类名前面写的是包名,为什么有的Bean有他的Id,这个Id在什么时候进行了使用。
  • 为什么三个类进行了循环依赖,用纯java在new对象的时候报错,用bean进行获取的时候就不会报错
  • 为什么类继承了另外一个类,要给他在配置文件注入的时候要加parent,否则在使用父类的public对象的时候会空指针呢。
    有了以上的问题,就有了下面的内容

2、该框架的实现内容

该框架主要实现了Spring框架的几个主要部分,并且进行了拓展

  1. IOC,即依赖注入,以bean工厂的方式,对所有被@Bean标注的的类在BeanFactory进行注册,使用时只需要在BeanFactory进行获取。
  2. AOP,即面向切面编程,通过不侵入式的编程,只需要对相关的类和方法加注解,就可实现对特定的类进行增强。
  3. 定时器,该框架是以多线程的方式,来实现定时任务。
  4. JavaWeb,使用原生的socket通信来进行通信,优点是可以支持各种类型请求。该框架定义了一个@RequestMaping注解,可以配合该框架来进行类似于Spring框架的后端框架的开发。

提示:以下是本篇文章正文内容,下面案例中会有关于如何使用该框架的demo

一、源码下载地址(github)

Github项目地址:https://github.com/MaBo2420935619/framework

二、IOC–源码及其实现原理

1、该框架IOC的实现原理

(1)、IOC的实现原理是反射,那么什么是反射,通过下面的例子来进行说明
简单创建一个类Student

package com.mabo;

public class Student {
    
    private String name="mabo";
    
    private int age=22;
    
    public String getName() {
        return name;
    }
    
    public int getAge() {
        return age;
    }
}

通过反射来获取Student的对象

package com.mabo;

public class StudentReflect {
    public static void main(String[] args) {
        Student student = null;
        try {
            Class<?> aClass = Class.forName("com.mabo.Student");
            //利用无参构造方法反射生成对象
            student = (Student)aClass.newInstance();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        }
        int age = student.getAge();
        String name = student.getName();
        System.out.println(name);
        System.out.println(age);
    }
}

执行结果
在这里插入图片描述
可以看到,在main方法中,没有使用new 关键字来生成对象,但是最终通过反射的方式帮我们生成了一个对象。
现在可以大概明白在Spring中xml文件注入的时候带包名和类名是用来经行反射生成bean对象的。并且我们也会常常在写的时候会给类写一个无参构造方法就是这个原因。
在该框架中,底层原理也是采用该方法来对Bean进行第三层缓存的初始化,初始化过程如下

(2)、HashMap用来存储Bean
bean的底层存储是用HashMap来进行存储的,这样可以保证一个Id,只对应一个bean

2、注入一个Bean的全过程

一个bean的生成过程经历大概以下的步骤

  • 利用该类,利用反射调用类的无参构造生成类对象,将该类放入三级缓存
  • 该类是否需要增强AOP,如果增强,该框架使用CGLIB的底层的字节码技术进行增强
  • 遍历所有生成的对象,利用反射,用类对象属性的set方法为对象注入属性
  • 该类成功实现,并且填充了属性,放入一级缓存,可以直接调用使用

以上是一个类正常情况下的生成过程,但是有以下情况我们也会遇到

  • 该类继承另外一个类,在填充属性的时候,优先实现父类,对子类实现填充的时候,调用父类的相关 属性的get,set方法进行填充。
  • 循环依赖的时候,将使用二级缓存的方式来进行实现,如果循环依赖的时候,涉及到AOP,需要三级缓存。

三层缓存解决循环依赖(只解决循环依赖实际上2层缓存足够)
这是BeanFactory的三层缓存,实际上就是三个Map用来存放对象
在这里插入图片描述
不考虑循环依赖中有AOP的情况
解决循环依赖的原理(默认A,B,C一直都没有被初始化)
假设A依赖B,B依赖C,C依赖A
在进行BeanFactory初始化的时候,

  1. 在一级缓存查找A,不存在,利用A的无参构造,通过反射的方式生成A对象,并且在三级缓存中注入bean A
  2. 对A进行属性的初始化,发现A依赖B,查找B发现不存在,通过反射的方式生成B对象,并且在三级缓存中注入beanB,
  3. 对B进行属性的初始化,发现B依赖C,查找C发现不存在,通过反射的方式生成C对象,并且在三级缓存中注入beanC,
  4. 对C进行初始化,发现C依赖A,一级缓存中不存在,将三级缓存中A注入到C的属性中,C初始化完成,将C保存到一级缓存
  5. C初始化完成,将C注入到B中,B初始化完成。
  6. B初始化完成,将B注入到A中,到这里A,B,C三个类的实例化对象都完成了。

最后的效果如图
在这里插入图片描述
如果直接new出来A的对象再使用对象,对象的属性将是空指针
在这里插入图片描述

三、AOP–源码及其实现原理

1、该框架AOP的实现原理

实现动态代理有两种方式JDK动态代理和CGLIB动态代理,两者的简单区别就是JDK动态代理的类必须实现了接口,而CGLIB不需要实现接口,但是CGLIB会加大系统开销,所以在Spring中两者都使用,有类实现接口就用JDK的动态代理,否则不用
该框架只使用了CGLIB的动态代理,其中需要

  • AOP的实现原理是依赖注入,有两种实现方式JDK和CGLIB
  • cglib实际上是继承了该类,如果同时该类需要代理,其方法必须是public修饰,否则子类无法实现正常代理
  • cglib是通过字节码技术创建这个类的子类,实现动态代理。
  • AOP的根本原理是新建了一个class文件,是继承了代理类,实现了对代理类的增强

下面来看AOP的主要实现源码,可以看到,

public class SayHelloProxy implements MethodInterceptor {
    private Object target;

    public Object getInstance(Object target) {
        this.target = target;
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(this.target.getClass());
        // 设置回调方法
        enhancer.setCallback(this);
        // 创建代理对象
        return enhancer.create();
    }

    /**
     * 实现MethodInterceptor接口中重写的方法
     *
     * 回调方法
     */
    @Override
    public Object intercept(Object object, Method method, Object[] args, MethodProxy proxy) throws Throwable {
        System.out.println("前置增强。。。");
        Object result = proxy.invokeSuper(object, args);
        System.out.println("后置增强。。。");
        return result;
    }

主要实现增强的地方是下图的位置,我们只需要结合Bean根据注解去遍历Bean,在类调用方法的前后,各做其他的事情,就可以实现对类的增强。
在这里插入图片描述

2、AOP的测试Demo

如何使用该增强类

public class SayHelloService {
    public void sayHello(){
        System.out.println("Hello,我再执行sayHello()方法");
    }
    
    public static void main(String[] args) {
        SayHelloService sayHelloService = new SayHelloService();
        SayHelloProxy sayHelloProxy = new SayHelloProxy();
        SayHelloService sayHelloService1 = (SayHelloService) sayHelloProxy.getInstance(sayHelloService);
        sayHelloService1.sayHello();
    }

}

测试结果,不仅仅执行了我们定义的方法,还在方法前后执行了其他的内容。
在这里插入图片描述

四、该框架对IOC和AOP整合实现原理及源码

IOC和AOP整合的实现原理

前文中提到,IOC在实现的时候,采用了三层缓存的原理,当IOC结合AOP的时候,实际上大部分情况也可以利用双重缓存解决。使用三层和缓存的原因是为了解决依赖循环中的类存在AOP的问题。
前面已经提到了循环依赖的解决过程。
下面再复制过来
在这里插入图片描述

三层缓存,就是在二级缓存的基础上,当一个bean在三级缓存中初始化过,不断递归去实现他的属性的时候,最后回到了去一级缓存中寻找自己bean,这个时候,我们就可以判断出来我们的bean注入过程 出现了循环依赖。如果只是出现循环依赖,上文中是可以解决的。下面讨论循环依赖中出现AOP的情况。

  1. 当循环以来的类A又实现了AOP,那么就在发现出现循环依赖的时候,将A类的三级缓存的bean进行增强,生成代理类,放到二级缓存。
  2. 当C类依赖A的时候,就利用A列的增强类在 还未注入属性的时候,利用构造方法生成代理对象,给C。C初始化完成,放到一级缓存,
  3. C初始化完成,B也可以完成初始化。
  4. B完成初始化,A再去调用代理对象,注入属性b.
  5. 到这里,就完成了A,B,C循环依赖的注入,并且对A进行了增强

实现的结果就是下图,并且可以看到类A的前面出现$$符号,证明A类的字节码文件已经不是A本身了,而是代理类的字节码文件
在这里插入图片描述

五、定时器–源码及其实现原理

1、定时器的原理

定时器的原理比较简单,简单理解就是在所有的Bean完成初始化以后,利用多线程来经行定时执行遗传代码
下图可以看到定时器启动在BeanFatory的启动时在类完成初始化后执行的
在这里插入图片描述

2、定时器的源码

采用异步线程的方式实现定时器,利用反射获取需要定时执行的方法

public class QuartzReflect {
    private static Log log=new Log(QuartzReflect.class);

    /**
     * @Author mabo
     * @Description   用于反射定时器
     */
    public static void reflect(){
        List<Method> methods = InitInterface.annotationOnMethod(Quartz.class);
        for (Method method : methods) {
            method.setAccessible(true);//设置方法为可执行的
            if (method.isAnnotationPresent(Quartz.class)) {
                Class declaringClass = method.getDeclaringClass();
                String simpleName =null;
                Bean bean = (Bean)declaringClass.getAnnotation(Bean.class);
                if(bean==null||bean.value().equals("")){
                    simpleName = StringUtil.toLowerCaseFirstOne(declaringClass.getSimpleName());
                }else{
                    simpleName=bean.value();
                }
                Object o = BeanFactory.getBean(simpleName);
                //获取注解的接口
                Quartz mt = method.getAnnotation(Quartz.class);
                //获取注解的参数
                Object finalO = o;
                Runnable runnable = new Runnable() {
                    public void run() {
                        try {
                            method.invoke(finalO);
                        } catch (Exception e) {
                            e.printStackTrace();
                            log.error(method.getName()+"()方法执行失败");
                        }
                    }
                };
                ScheduledExecutorService service = Executors
                        .newSingleThreadScheduledExecutor();
                // 第二个参数为首次执行的延时时间,第三个参数为定时执行的间隔时间
                service.scheduleAtFixedRate(runnable, mt.waitSecond(), mt.time(), TimeUnit.SECONDS);

            }
        }
        log.info("多线程定时器初始化成功");
    }
}

六、JavaWeb(类似SpringBoot)–源码及其实现原理

1、JavaWeb后端的实现原理

  1. 这里所说的JavaWeb后端其实就是利用Socket通信,来实现和前端的交互。

  2. 使用socket的原因时不受请求类型的限制,并且可以自定义任意的请求类型和请求头内容

  3. 框架内已经实现了Socket的请求和反射过程,当我们需要对一各请求进行响应,只需要在方法上面加@RequestMaping注解,参数为请求类型,例如"/login",当前端发起了/login的请求,后端就会执行该注解下面的方法。
    实现原理

  4. 启动socket通信,接收请求,解析请求的url和参数。

  5. 根据url地址,取查找对应的@RequestMaping注解的方法,并执行

  6. 将方法直接结果返回socket,发送给前端服务器(也可以时其他服务器)
    反射执行方法的源码:

/**
 * @Author mabo
 * @Description
 * String requestMapping: RequestMapping接口的反射类的请求类型
 * RequestType requestType: 发送的是什么类型的请求POST/GET/PUT
 */
    public static Object getReflect(String requestMapping,RequestType requestType, Map map) {
        List<Method> methods = InitInterface.annotationOnMethod(RequestMapping.class);
        for (Method method : methods) {
            method.setAccessible(true);//设置方法为可执行的
            if (method.isAnnotationPresent(RequestMapping.class)) {
                Class declaringClass = method.getDeclaringClass();
                Object o = null;
                //获取注解的接口
                RequestMapping mt = method.getAnnotation(RequestMapping.class);
                //获取注解的参数
                String value = mt.value();
                RequestType rt = mt.requestType();
                if (requestMapping.equals(value)&&requestType.equals(rt))
                {
                    //实例化类
                    try {
                        o = declaringClass.newInstance();
                    } catch (Exception e) {
                        log.info(declaringClass.getName()+"()类实例化失败");
                    }
                    //反射执行方法
                    try {
                        //重要
                        //获取bean工厂的bean
                        Bean annotation = o.getClass().getAnnotation(Bean.class);
                        String value1 = annotation.value();
                        String name =null;
                        if (!value1.equals(""))
                            name=value1;
                        else{
                            name = o.getClass().getSimpleName();

                            name = StringUtil.toLowerCaseFirstOne(name);
                        }
                        Object bean = BeanFactory.getBean(name);
                        Object invoke = method.invoke(bean, map);
                        log.info(method.getName()+"()方法执行成功");
                        return invoke;
                    } catch (Exception e) {
                        log.info(method.getName()+"()方法执行失败");
                    }
                }
            }
        }
        return null;
    }

2、演示示例

利用postman向后端发起请求,和响应结果
在这里插入图片描述
后端响应结果
在这里插入图片描述

七、如何使用该框架

Github项目地址:https://github.com/MaBo2420935619/framework

  • 如何使用该框架可以去github下载具体的代码作为参考
  • 配置文件内相关内容都做了注释,非常容易理解

在这里插入图片描述
数据库的配置文件
在这里插入图片描述
redis配置文件
在这里插入图片描述

在这里插入图片描述

  • 该矿建也内置了一些用的工具类
    在这里插入图片描述

总结

这篇关于用java实现纯注解Spring框架(包括IOC,AOP,定时器,javaWeb)的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!