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