2021-08-31

DiySpring

IOC

使用: 加入@Service @Component @Controller @Aspect注解就可以把他叫给容器管理
用户首先用容器的getInstance()得到容器工厂,然后用工厂的loadBeans(“包名”)就可以把包下的有注解的类都注入到工厂的map里,此时得到这些map里的一些还没有实现依赖注入,然后调用依赖注入器的doIOC()方法就可以实现依赖注入了。

public void doIocTest(){
        BeanContainer beanContainer = BeanContainer.getInstance();
        beanContainer.loadBeans("com.zjd.easyFramework");
        Assertions.assertEquals(true, beanContainer.isLoaded());
        MainPageController mainPageController = (MainPageController)beanContainer.getBean(MainPageController.class);
        Assertions.assertEquals(true, mainPageController instanceof MainPageController);
        Assertions.assertEquals(null, mainPageController.getHeadLineShopCategoryCombineService());
        new DependencyInjector().doIoc();
        Assertions.assertNotEquals(null, mainPageController.getHeadLineShopCategoryCombineService());
        Assertions.assertEquals(true, mainPageController.getHeadLineShopCategoryCombineService() instanceof HeadLineShopCategoryCombineServiceImpl1);
        Assertions.assertEquals(false, mainPageController.getHeadLineShopCategoryCombineService() instanceof HeadLineShopCategoryCombineServiceImpl2);
    }

实现
1 首先定义以上这些注解
2 去得到classloader,然后用classloader的getResource方法得到目标包的url,然后将url去掉头,我这里只进行file协议头的处理,去掉头后,将后边的path放到一个file对象中,然后就可以递归这个file对象,递归中可以用listFiles方法遍历这个当前file,如果是目录的话呢就继续递归,否则的话呢就可以判断如果文件的endswith是.class那么就直接加载,放到一个set里,那么这样的话呢,就可以得到一个放置所有clazz的set集合。
3 然后的话,实现一个容器,容器里可以顶一个Enum类或者一个list,然后list里边就放这些定义的注解,然后的话呢,去遍历之前clazz的set,如果clazz的类有目标注解(clazz.isAnnotationPresent)就给他放到容器里,当然都只放一次。

依赖注入

遍历容器中所有class对象
然后clazz.getDeclaredFields得到Class对象的成员变量
然后可以fielld.isAnnotation(AutoWired.class)得到有AutoWired注解的成员变量。
得到这些成员变量的类型,然后去容器里找到这个类型对应的实例
然后可以用反射的field.set(get,value)注入属性

循环依赖

以上解决了循环依赖,解决循环依赖的话,因为实例化之后已经都放到了map里,然后调用依赖注入器进行依赖注入,都是可以注入的。

spring解决循环依赖的方式:(三个map)

循环依赖问题在Spring中主要有三种情况:

通过构造方法进行依赖注入时产生的循环依赖问题。

new就阻塞了,所以肯定阻塞了,

通过setter方法进行依赖注入且是在多例(原型)模式下产生的循环依赖问题。

无法解决,多例,每次getbean产生一个新的bean,OOM了

通过setter方法进行依赖注入且是在单例模式下产生的循环依赖问题。

可以解决

在这里插入图片描述

Spring中有三个缓存,用于存储单例的Bean实例,这三个缓存是彼此互斥的,不会针对同一个Bean的实例同时存储。

如果调用getBean,则需要从三个缓存中依次获取指定的Bean实例。 读取顺序依次是一级缓存–>二级缓存–>三级缓存

第一 先说说第一级缓存singletonObjects(diySpring相当于只有一个这个缓存)

1.先说一级缓存singletonObjects。实际上,一级依赖已经可以解决循环依赖的问题,假设两个beanA和beanB相互依赖,beanA被实例化后,放入一级缓存,即使没有进行初始化,但是beanA的引用已经创建(栈到堆的引用已经确定),其他依赖beanB已经可以持有beanA的引用,但是这个bean在没有初始化完成前,其内存(堆)里的字段、方法等还不能正常使用,but,这并不影响对象之间引用持有;这个时候beanA依赖的beanB实例化,beanB可以顺利拿到beanA的引用,完成beanB的实例化与初始化,并放入一级缓存,在beanB完成创建后,beanA通过缓存顺利拿到beanB的引用,至此,循环依赖只需一层缓存就能完成。
2.一级缓存的关键点在与:bean实例化与初始化的分离。从JVM的角度,实例化后,对象已经存在,其内的属性都是初始默认值,只有在初始化后才会赋值,以及持有其他对象的引用。通过这个特性,在实例化后,我们就可以将对象的引用放入缓存交给需要引用依赖的其他对象,这个过程就是提前暴露。

第二 第三级缓存

第三级缓存是为了解决动态代理对象,如果需要的是一个代理对象,那这个工厂的map提供了一些扩展,可以在这个里扩展成代理,这样的话,缓存的时候就先走三级缓存,如果是代理的话呢,这里就已经被代理了,被代理之后再放到二级缓存,这样上一级缓存拿到的就是代理对象了。

第三 二级缓存

1.为什么需要earlySingletonObjects这个二级缓存?并且,如果只有一个缓存的情况下,为什么不直接使用singletonFactories这个缓存,即可实现代理又可以缓存数据。
2.从软件设计角度考虑,三个缓存代表三种不同的职责,根据单一职责原理,从设计角度就需分离三种职责的缓存,所以形成三级缓存的状态。 3、再次说说三级缓存的划分及其作用。 一级缓存singletonObjects是完整的bean,它可以被外界任意使用,并且不会有歧义。
二级缓存earlySingletonObjects是不完整的bean,没有完成初始化,它与singletonObjects的分离主要是职责的分离以及边界划分,可以试想一个Map缓存里既有完整可使用的bean,也有不完整的,只能持有引用的bean,在复杂度很高的架构中,很容易出现歧义,并带来一些不可预知的错误。
三级缓存singletonFactories,其职责就是包装一个bean,有回调逻辑,所以它的作用非常清晰,并且只能处于第三层。
在实际使用中,要获取一个bean,先从一级缓存一直查找到三级缓存,缓存bean的时候是从三级到一级的顺序保存,并且缓存bean的过程中,三个缓存都是互斥的,只会保持bean在一个缓存中,而且,最终都会在一级缓存中。

Spring官方推荐使用 构造器Autowired的方式构造,直接规范设计阶段,预防循环依赖。

AOP

使用:

@Aspect(value = Controller.class)
@Order(0)
public class ControllerTimeCalculatorAspect extends DefaultAspect {
    private long timeStampCache;
    @Override
    public void before(Class<?> target, Method method, Object[] args) throws Throwable {
        System.out.println("【INFO】开始计时,执行的类是"+target.getName()+",执行的方法是:"+method.getName()+",参数是:"+args);
        timeStampCache = System.currentTimeMillis();
    }

    @Override
    public Object afterReturning(Class<?> target, Method method, Object[] args, Object returnValue) throws Throwable {
        long endTime = System.currentTimeMillis();
        long costTime = endTime - timeStampCache;
        System.out.println("【INFO】结束计时,执行的类是"+target.getName()+",执行的方法是:"+method.getName()+",参数是:"+args+
                ",返回值是"+returnValue+",时间是:"+costTime);
        return returnValue;

    }
}

注意先do aop再do ioc 这点是和spring一样的,do aop就是用代理对象替换了容器里的原始对象。

/*AspectWeaverTest.java*/ public class AspectWeaverTest {
    @DisplayName("织入通用逻辑测试:doAop")
    @Test
    public void doAopTest(){
        BeanContainer beanContainer = BeanContainer.getInstance();
        beanContainer.loadBeans("com.zjd");
        new AspectWeaver().doAop();
        new DependencyInjector().doIoc();
        HeadLineOperationController headLineOperationController =
                (HeadLineOperationController) beanContainer.getBean(HeadLineOperationController.class);
        headLineOperationController.addHeadLine(null, null);
    } }

AOP实现
1 定义与横切逻辑相关的注解
一个aspect,aspect里边需要有一个 注解的value();(Class<? extends Annotation> value());
还有一个order注解 order注解的value()就是一个int值

2 定义一个模板方法类 DefaultAspect,用户使用时继承这个类,重写里边的方法,比如:before after
returning等

3 创建 MethodInterceptor 的实现类 AspectListExecutor
定义必要的成员变量:被代理的类以及Aspect列表
按照 Order 对 Aspect 进行排序
实现 Aspect 横切逻辑以及被代理对象方法的定序执行

成员变量主要就是一个被代理类 target,一个AspectInfo的列表,AspectInfo里边主要有order和DefaultAspect,主要目的就是为了排序执行,然后主要就是重写里边的intercepet()方法,先排序,然后按照list中order的升序执行Aspecrt的before,按照逆序执行after这样。

生成代理的类,Enhancer.creat(targetClass,methodInterceptor)生成代理

4 织入 :
织入的话,一个负责织入的类,里边有个方法就是doAOP();
首先从容器里找到有aspect注解的类,然后把这些有aspecrt注解的类做个归类,得到注解ascpect的值和order的值(getAnnotation(order.class),放到map里,对应同一个注解,对应同一个list,然后做织入,织入的话就是创建methodInterceptor的实现类,然后创建代理类,然后用代理类替换容器中的原始类。

目前缺点:没有用ASpectJ,没有引入切点表达式

Springmvc

所有请求首先打到WebServlet(/*)注解下的DisparthServelt,然后这里的init方法先完成容器的初始化,包括ioc,aop等。然后再service方法里调责任链,链里放个list,list里就是按序放的Processror和render

ControllerRequestProcessor的实现
1 针对特定的请求选择对应的Controller处理(简单来说就是做个map,key就是请求的信息,value就是这个信息请求的具体类的具体方法)
1.1定义相关注解

@Target({ElementType.TYPE,ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface RequestMapping {
    String value() default "";
    RequestMethod method() default RequestMethod.GET;   //需要先定义RequestMethod枚举类
}
@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
public @interface RequestParam {
    String value() default "";
    boolean required() default true;
}
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface ResponseBody {
}

1.2 定义相关类
存放某个controller类的具体方法包括类是哪个类,哪个方法,什么参数

public class ControllerMethod {
    //Controller对应的Class对象
    private Class<?> controllerClass;
    //执行的Controller方法实例
    private Method invokeMethod;
    //方法参数名称以及对应的参数类型
    private Map<String, Class<?>> methodParameters;
}
/*RequestPathInfo.java*/
/*存储http请求路径和请求方法*/
@Data
@AllArgsConstructor
@NoArgsConstructor
public class RequestPathInfo {
    //http请求方法
    private String httpMethod;
    //http请求路径
    private String httpPath;
}

实现 Controller 请求处理器 ControllerRequestProcessor
第一步得有个map,map的key就是请求信息,value就是方法

 public ControllerRequestProcessor() {
        this.beanContainer = BeanContainer.getInstance();
        先在构造方法中从容器中取出有requestMapping的类
        Set<Class<?>> requestMappingSet = beanContainer.getClassesByAnnotation(RequestMapping.class);
        initPathControllerMethodMap(requestMappingSet);
    }

1 遍历所有被@RequestMapping标记的类,获取类上面该注解的属性值作为一级路径

2.遍历类里所有被@RequestMapping标记的方法,获取方法上面该注解的属性值,作为二级路径
把 1 2拼接就得到了请求的url,请求信息此时还差个requestParam里边的值,然后把requestParam里边的参数的类型放到map的key里,requestParam里边参数的值放到value里,

3 将以上至封装到第一步的Ctrollermethod的map里

第二步 ,执行方法,处理,然后封装结果,然后根据result的不同调用不同的render,

//据不同情况设置不同的渲染器
    private void setResultRender(Object result, ControllerMethod controllerMethod, RequestProcessorChain requestProcessorChain) {
        if (result == null)
            return;
        ResultRender resultRender;
        boolean isJson = controllerMethod.getInvokeMethod().isAnnotationPresent(ResponseBody.class);
        if (isJson)
            resultRender = new JsonResultRender(result);
        else
            resultRender = new ViewResultRender(result);
        requestProcessorChain.setResultRender(resultRender);
    }

ViewResultRender

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值