也有个一周没有更新博客了,其实我没有偷懒,因为之前一直在看Spring源码,所以想着去写一个类Spring的框架,也没有给自己定什么高的要求,简单实现MVC、AOP、IOC等功能就行。现在这个框架基本上已经成型了,自己也测试过了,因此拿出来和大家分享一下。

  我本文就不写标题了,因为自己的思路是跟着代码走的,所以可能说着说着MVC就跳到DI那一块了。首先我在开始的时候,也是跟随着大部分人的思路,先从DispatcherServlet入手。因为为DispatcherServlet是和用户交互的。和DispatcherServlet交互的有四个模块,分别是:HandlerMapping、HandlerAdapter、ViewResolver、ModelAndView,我定义了四个类分别对应着这四个模块,先不说他们是怎么工作的,先聊一下DispatcherServlet。

  它和HandlerMapping先进行交互,然后HandlerMapping处理拿到对象。

private void initHandlerMappings(LusheApplicationContext context) {//按照我们通常的理解应该是一个Map,Map<String,Method> map;map.put(url,Method)//首先从容器中取到所有的实例String [] beanNames = context.getBeanDefinitionNames();try {for (String beanName : beanNames) {//到了MVC层,对外提供的方法只有一个getBean方法Object proxy = context.getBean(beanName);Object originObject = LusheAopProxyUtils.getOriginObject(proxy);Class<?> clazz = originObject.getClass();if (!clazz.isAnnotationPresent(Controller.class)) {continue;}String baseUrl = "";if (clazz.isAnnotationPresent(RequestMapping.class)) {RequestMapping requestMapping = clazz.getAnnotation(RequestMapping.class);baseUrl = requestMapping.value();}//扫描所有的public方法Method[] methods = clazz.getMethods();for (Method method : methods) {if (!method.isAnnotationPresent(RequestMapping.class)) {continue;}RequestMapping requestMapping = method.getAnnotation(RequestMapping.class);String regex = ("/" + baseUrl + requestMapping.value().replaceAll("\\*", ".*")).replaceAll("/+", "/");Pattern pattern = Pattern.compile(regex);this.handlerMappings.add(new LusheHandlerMapping(pattern, originObject, method));System.out.println("Mapping: " + regex + " , " + method);}}}catch (Exception e){e.printStackTrace();}}

  我是这么处理的,我只给Servlet获得实例的接口,然后再让它来解析这些实例,放到定义好的HandlerMapping数组中。那么这里就要考虑给getBean的类了,之前在Spring中他们是在一个终极父类里定义到了这个方法,我的处理是简化,直接定义一个ApplicationContext,把它当做BeanFactory,所有的处理都在这里面进行,对外给出getBean方法。那么来看一下ApplicationContext这个类。按照Spring的思想,首先是定义一个refresh方法,它的作用就是:根据XML文件定义的属性来进行IOC容器的初始化。

public void refresh(){//定位this.reader = new BeanDefinitionReader(configureLocations);//加载List<String> beanDefinitions = this.reader.loadBeanDefinitions();//注册
        doRegisty(beanDefinitions);//依赖注入(lazy-init = false),自动调用getBean方法
        doDependencyInjection();}

  我按照定位、加载、注册和依赖注入四部分来处理。首先是定位,就是对配置文件进行定位读取解析,这里自己定义了一个BeanDefinitionReader的类,把配置文件存放在它内部一个Properties变量中。并且把配置文件中划好的包中的类给拿出来,

private void doScanner(String packName) {URL url = this.getClass().getClassLoader().getResource("/" + packName.replaceAll("\\.","/"));File classDir = new File(url.getFile());for(File file : classDir.listFiles()) {if(file.isDirectory()) {doScanner(packName + "." + file.getName());} else {registyBeanClasses.add(packName + "." + file.getName().replace(".class",""));}}}

  然后是加载,在BeanDefinitionReader里,我将属性和类名做成一个Map,存放起来。之后就是注册,还是在BeanDefinitionReader中,因为我之前已经将属性和名称做成了一个Map,这样我可以通过类名就能得到它对应的BeanDefinition。

public LusheBeanDefinition registerBean(String className) {if(this.registyBeanClasses.contains(className)) {LusheBeanDefinition lusheBeanDefinition = new LusheBeanDefinition();lusheBeanDefinition.setBeanClassName(className);lusheBeanDefinition.setFactoryBeanName(StringUtils.lowerFirstCase(className.substring(className.lastIndexOf(".") + 1)));return lusheBeanDefinition;}return null;}

  回到ApplicationContext的refresh方法,把我们所有定义好的BeanDefinition放到BeanDefinitionMap中,也就是Spring的Cache里。因为之前只是将所有的BeanDefinition放进去,还没有做进一步的处理,因此就在这一块进行处理了。

private void doRegisty(List<String> beanDefinitions){try {for(String className : beanDefinitions) {Class<?> beanClass = Class.forName(className);//判断是不是接口,如果是接口,则用其实现类来实现if(beanClass.isInterface()) {continue;}//beanName 为类名首字母小写//自定义beanNameLusheBeanDefinition lusheBeanDefinition = this.reader.registerBean(className);if(lusheBeanDefinition != null) {beanDefinitionMap.put(lusheBeanDefinition.getFactoryBeanName(), lusheBeanDefinition);}//接口注入Class<?>[] interfaces = beanClass.getInterfaces();for(Class<?> interf : interfaces) {//多个接口功能相同//如果多个属性类只能覆盖,因为Spring没有那么智能this.beanDefinitionMap.put(interf.getName(), lusheBeanDefinition);}}} catch (Exception e) {e.printStackTrace();}}

  首先看看是不是个接口,如果是,就直接跳过,因为我会在后面给每个类的每个接口赋予相同的BeanDefinition,这里的确有一点瑕疵,但是我也在思考更好地处理办法。最后我得到的BeanDefinitionMap,这里注册的功能就完成了。

  然后就是依赖注入这块,我是这么做的:

private void populateBean(String beanName, Object instance) {Class clazz = instance.getClass();if(clazz.isAnnotationPresent(Controller.class) || clazz.isAnnotationPresent(Service.class)) {//读取所有字段Field[] fields = clazz.getDeclaredFields();for(Field field : fields) {if(field.isAnnotationPresent(AutoWried.class)) {AutoWried autoWried = field.getAnnotation(AutoWried.class);String autoWriedBeanName = autoWried.value().trim();if(autoWriedBeanName.equals("")) {autoWriedBeanName = field.getType().getName();}field.setAccessible(true);try {field.set(instance,this.beanWrapperMap.get(autoWriedBeanName).getWrapperInstance());} catch (IllegalAccessException e) {e.printStackTrace();}} else {continue;}}} else {return;}}

  先不说这个,因为这里我们还没有得到Instance,我是通过反射获得的这个实例

private Object instantionBean(LusheBeanDefinition lusheBeanDefinition) {String className = lusheBeanDefinition.getBeanClassName();Object beanInstance = null;try {synchronized (this) {if (!beanCacheMap.containsKey(className)) {Class<?> clazz = Class.forName(className);beanInstance = clazz.newInstance();beanCacheMap.put(className, beanInstance);} else {beanInstance = beanCacheMap.get(className);}}} catch (Exception e) {e.printStackTrace();}return beanInstance;}

  然后再看一下依赖注入这一块,也就是populateBean,会传入一个bean的实例并拿到它的类,如果这个类已经声明为Controller或者Service类,那么就把它所有的字段拿出来,如果某个字段被标注为AutoWried,那么就从包装Map中把实例给拿出来,具体的实现就是这样的。

  上面提到包装类BeanWrapper,它的作用就是方便对其进行功能拓展,比如说增加AOP切点之类的。我是这么构造一个包装类实例的:

public LusheBeanWrapper(Object Instance) {//添加动态代理this.wrapperInstance = aopProxy.getProxy(Instance);//原始对象this.originalInstance = Instance;}

我包装类的构造函数是传入一个原始类实例,然后并将这个实例保存起来,然后通过AOP模块的动态代理拿到包装类实例。

//这里传入原始对象,Spring中传入的事ClassLoaderpublic Object getProxy(Object object) {this.target = object;Class<?> clazz = this.target.getClass();return Proxy.newProxyInstance(clazz.getClassLoader(),clazz.getInterfaces(), this);}

  Spring中在Aop动态代理创建的时候传入的是ClassLoader,而我这里为了简便直观,传入的是原始类实例,然后拿到这个实例的类,并调用Proxy的newProxyInstance方法获得这个被代理对象。接下来就是讨论AOP是如何实现的,也就是如何对原始方法进行增强的。下面是我AopProxy类中invoke方法,它的作用就是对被代理对象切入点方法进行增强。

public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {//之前if(aopConfigure.contains(method)) {//对于某个方法,看看是不是切入点LusheAopConfigure.LusheAspect aspect = aopConfigure.get(method);aspect.getPoints()[0].invoke(aspect.getAspect(),args);}//反射调用原始方法Object obj = method.invoke(this.target, args);//之后if(aopConfigure.contains(method)) {LusheAopConfigure.LusheAspect aspect = aopConfigure.get(method);aspect.getPoints()[1].invoke(aspect.getAspect(),args);}//可以考虑一下为什么这么写//这里面用到了循环return obj;}

  首先来解释一下AopConfigure这个类的作用,它是对ApplicationContext中expression的封装,对目标代理对象的一个方法增强,增强的方法就是自己定义逻辑。配置文件告诉我们哪些类哪些方法需要增强和需要增强的内容,我用一个Map去存放这些信息,key是目标对象的方法名,value是我自己定义的一个实体Bean,存放的用来增强的类和它的方法(这里我只是简单的定义了两个:before和after)。之后再回到上面的那个invoke方法,这里是这么处理的,首先看一下这个方法是不是一个需要增强的方法,如果是,那么先搞定before方法增强,然后这个方法的执行,最后执行方法后增强。

  最后再回到getBean,当我们使用到这个方法的时候,默认已经完成了IOC容器的初始化和AOP切面方法增强,最后getBean返回给DispatcherServlet的,并不是原始的BeanInstance,而是一个BeanWrapper的实例,换句话说,getBean的作用就是传入一个Bean的类名,然后读取BeanDefinition的信息,通过反射机制创建实例并返回。在这个框架中,用到了Spring中用到的思想,它用BeanMapper把原始Bean包装起来(具体怎么包装我在上面已经说了),然后作用是不仅保存了之前的OP关系,而且方便了之后的拓展和增强。

public Object getBean(String beanName) {LusheBeanPostProcessor lusheBeanPostProcessor = new LusheBeanPostProcessor();LusheBeanDefinition lusheBeanDefinition = beanDefinitionMap.get(beanName);Object beanInstance = instantionBean(lusheBeanDefinition);lusheBeanPostProcessor.postProcessBeforeInitialization(beanInstance,beanName);if(beanInstance == null) {return null;}LusheBeanWrapper lusheBeanWrapper = new LusheBeanWrapper(beanInstance);//生成通知事件try {lusheBeanWrapper.setAopConfigure(instantionAopConfigure(lusheBeanDefinition));} catch (Exception e) {e.printStackTrace();}lusheBeanWrapper.setLusheBeanPostProcessor(lusheBeanPostProcessor);beanWrapperMap.put(beanName, lusheBeanWrapper);lusheBeanPostProcessor.postProcessAfterInitialization(beanInstance,beanName);//populateBean(beanName,beanInstance);return beanWrapperMap.get(beanName).getWrapperInstance();}

  首先从已经初始化好的BeanDefinitionMap中拿到BeanDefinition,然后调用instantionBean方法拿到实例Bean(这个方法的介绍在上面),如果这个实例存在,那我们就对它进行包装并进行AOP方法切入点的增强(前提是有),并将生成的包装实例放在Map中保存,并返回包装器中的包装实例,可以肯定的说,实例的类不是原始类,而是一个动态代理生成的类,类似以$Proxy0这种。

  回到DispatcherServlet的initHandlerMapping方法:

private void initHandlerMappings(LusheApplicationContext context) {//按照我们通常的理解应该是一个Map,Map<String,Method> map;map.put(url,Method)//首先从容器中取到所有的实例String [] beanNames = context.getBeanDefinitionNames();try {for (String beanName : beanNames) {//到了MVC层,对外提供的方法只有一个getBean方法Object proxy = context.getBean(beanName);Object originObject = LusheAopProxyUtils.getOriginObject(proxy);Class<?> clazz = originObject.getClass();if (!clazz.isAnnotationPresent(Controller.class)) {continue;}String baseUrl = "";if (clazz.isAnnotationPresent(RequestMapping.class)) {RequestMapping requestMapping = clazz.getAnnotation(RequestMapping.class);baseUrl = requestMapping.value();}//扫描所有的public方法Method[] methods = clazz.getMethods();for (Method method : methods) {if (!method.isAnnotationPresent(RequestMapping.class)) {continue;}RequestMapping requestMapping = method.getAnnotation(RequestMapping.class);String regex = ("/" + baseUrl + requestMapping.value().replaceAll("\\*", ".*")).replaceAll("/+", "/");Pattern pattern = Pattern.compile(regex);this.handlerMappings.add(new LusheHandlerMapping(pattern, originObject, method));System.out.println("Mapping: " + regex + " , " + method);}}}catch (Exception e){e.printStackTrace();}}

  我们通过getBean拿到实例,然后拿到他的原始类实例,我是这么处理的:

 /*** 找到代理对象的原始对象* @param proxy* @return* @throws Exception*/public static Object getOriginObject(Object proxy) throws Exception {//不是代理对象if(!isProxy(proxy)) {return proxy;} else {return getProxyTargetObject(proxy);}}private static boolean isProxy(Object object) {return Proxy.isProxyClass(object.getClass());}private static Object getProxyTargetObject(Object proxy) throws Exception {//在代理模式中看到过,原始类的信息就被存放在类型为InvocationHandler的变量中,名字就叫hField h = proxy.getClass().getSuperclass().getDeclaredField("h");h.setAccessible(true);LusheAopProxy  aopProxy = (LusheAopProxy)h.get(proxy);Field target = aopProxy.getClass().getDeclaredField("target");target.setAccessible(true);return target.get(proxy);}

  因为在动态代理的过程中,原始类的信息就已经被存放在InvocationHandler中了,所以就很方便的拿到了。

  再回来,我首先判断一下这个类是不是Controller,不是就走了,是的话,再看一下它的方法中,哪个是RequestMapping方法,将他存放起来。

  然后是调用initHandlerAdapters方法,完成存放的方法的参数的处理。

  最后再说一下类是怎么和AOP配置匹配起来的,这里我建立了一个instantionAopConfigure方法,在建立BeanWrapper的时候调用:

private LusheAopConfigure instantionAopConfigure(LusheBeanDefinition beanDefinition) throws Exception{LusheAopConfigure aopConfigure = new LusheAopConfigure();String expression = reader.getConfigure().getProperty("pointCut");String[] before = reader.getConfigure().getProperty("aspectBefore").split("\\#");String[] after = reader.getConfigure().getProperty("aspectAfter").split("\\#");String className = beanDefinition.getBeanClassName();Class<?> clazz = Class.forName(className);//解析成正则去匹配Pattern pattern = Pattern.compile(expression);//指出切面类,因为我们在上面已经将切面方法的位置分成了两部分,第一部分是包类名,第二部分是方法名//因此我们这里就取第一部分就可以了,然后因为两个切面方法属于一个类,为了方便,就拿第一个就可以了。Class<?> aspectClass = Class.forName(before[0]);//拿出被代理类的所有方法//举例:com.spring.test.LusheMVC.framework.context.LusheApplication.getBean(java.lang.String)//而我们的正则是://public.* com\.spring\.test\.LusheMVC\.demo\.service\..*ServiceImpl\..*\(.*\)//这个肯定匹配不上 而//com.spring.test.LusheMVC.demo.service.impl.DemoServiceImpl.get(java.lang.String)//这个就匹配上了for(Method method : clazz.getMethods()) {Matcher matcher = pattern.matcher(method.toString());//如果匹配上了if(matcher.matches()) {//把能满足切面规则的类添加到AOP配置中
                aopConfigure.put(method,clazz.newInstance(),new Method[]{aspectClass.getMethod(before[1]),aspectClass.getMethod(after[1])});}}return aopConfigure;}

  首先拿到切点类的定义,用来做正则判断,格式类似于public.* com\.spring\.test\.LusheMVC\.demo\.service\..*ServiceImpl\..*\(.*\),然后拿出增强内容方法的信息(就是before和after的信息),并将它们处理到String数组里,第一个位置放的是类名,第二个是方法名。然后将BeanDefinition中存放的类名拿出来生成这个类的Class,将起所有方法拿出来进行正则匹配,如果匹配到了,说明这个方法是个切入点,需要被增强,那么就将这个信息存起来,处理完所有的之后,将这些内容给BeanWrapper,它会在AOP的时候用到。

  差不多就说这些吧,如果有补充我会更新的。

转载于:https://www.cnblogs.com/Jolivan/p/9275459.html

聊一个自己写的MVC框架相关推荐

  1. 从零开始实现一个简易的Java MVC框架(六)--加强AOP功能

    前言 在前面从零开始实现一个简易的Java MVC框架(四)--实现AOP和从零开始实现一个简易的Java MVC框架(五)--引入aspectj实现AOP切点这两节文章中已经实现了AOP功能并且引用 ...

  2. 从零开始实现一个简易的Java MVC框架(九)--优化MVC代码

    前言 在从零开始实现一个简易的Java MVC框架(七)--实现MVC中实现了doodle框架的MVC的功能,不过最后指出代码的逻辑不是很好,在这一章节就将这一部分代码进行优化. 优化的目标是1.去除 ...

  3. 自己动手写PHP MVC框架

    自己动手写PHP MVC框架 来自:yuansir-web.com / yuansir@live.cn 代码下载: https://github.com/yuansir/tiny-php-framew ...

  4. 学习如何写PHP MVC框架(1) -- 路由

    说到PHP开发web,自然离不开开发框架,开发框架为我们提供了灵活的开发方式,MVC层分离,业务解耦等... 第一篇先来简单点的,说说MVC框架的路由功能... 一般的单入口框架路由都是这样的结构: ...

  5. local tomcat 找不到springmvc里的包_唰唰的手撕一个简单的Spring Mvc 框架

    @[TOC] 自定义实现Spring Mvc框架 前言 在使用Spring Mvc时候我们需要配置一个中央处理器DispatcherServlet用于分发请求,根据url查找处理器,然后再根据处理器 ...

  6. Nancy总结(一)Nancy一个轻量的MVC框架

    Nancy是一个基于.net 和Mono 构建的HTTP服务框架,是一个非常轻量级的web框架. 设计用于处理 DELETE, GET, HEAD, OPTIONS, POST, PUT 和 PATC ...

  7. 手写一个迷你版Spring MVC框架

    前期准备 我这里要写的是一个迷你版的Spring MVC,我将在一个干净的web工程开始开发,不引入Spring,完全通过JDK来实现. 我们先来看一眼工程: 工程代码结构 第一:在annotatio ...

  8. ioc框架 java_从零开始实现一个简易的Java MVC框架(三)--实现IOC

    Spring中的IOC IoC全称是Inversion of Control,就是控制反转,他其实不是spring独有的特性或者说也不是java的特性,他是一种设计思想.而DI(Dependency ...

  9. 自己动手写一个简单的MVC框架(第二版)

    一.ASP.NET MVC核心机制回顾 在ASP.NET MVC中,最核心的当属"路由系统",而路由系统的核心则源于一个强大的System.Web.Routing.dll组件. 在 ...

  10. 仿照源码,手写一个自定义 Spring MVC 框架

    毫无疑问,Spring 框架目前已经成为 Java 开发的行业标准,Spring MVC 作为其 Web 解决方案,是所有 Java 开发者都必须掌握的基本技能,理解其底层原理,才能更好地应用它进行实 ...

最新文章

  1. Delphi 2010 安装及调试
  2. Faker库:一个数据造假的神库
  3. MongoDB与阿里云达成战略合作,最新数据库独家上线阿里云!
  4. php 实时查看公众号加粉数据,公众号实时涨粉数据怎么查看
  5. 易语言通过服务器发送文件,易语言局域网文件传输带聊天功能例程
  6. java 基础数据结构源码详解及数据结构算法
  7. 计算机取消补考,2005级《计算机文化基础》补考通知
  8. java sqlite sqlite_busy_SQLite 关闭时SQLITE_BUSY
  9. 【Mac 教程系列第 14 篇】如何设置 Mac 允许从任意来源下载 App
  10. 微信小程序,引入WeUI组件库 扩展组件库
  11. Java容器(集合)
  12. ora-01172 ora-01151的处理
  13. 【Css】使用float:left浮动后,导致后面div高度“塌陷”的解决办法(示例和图示)
  14. 快商通肖龙源:从智能客服到智能营销,客服中心迎来价值升级节点
  15. 大疆精灵4多光谱 辐射定标 Metashape(原photoscan)
  16. Linux学习记录 Day1(常用命令)
  17. 导出功能: 导出为excel文档,后端做导出处理,js代码发送接口直接下载
  18. linux 强大的文本编辑器 vim
  19. 倩女幽魂手游服务器维护多久,倩女手游维护到几点 倩女幽魂要维护到几点看系...
  20. 概率论与数理统计实验(一)

热门文章

  1. 天才程序员之陨落:在业余项目创业 Cloudflare,公司上市前患病失去自理能力...
  2. [转载]JS的IE和Firefox兼容性汇编(原作:hotman_x)
  3. dentity在Java里是什么意思_Function.identity()
  4. html学习——介绍
  5. ESP32编程使用OLED屏
  6. 【5G/4G】AT Command 详细教程(基于3GPP Spec 27.007)
  7. 投资学翻译2 Digesting Anomalies An Investment Approach
  8. Adaptive icon 二 设计自适应图标
  9. 美国人口普查年收入情况分析
  10. 成都博兰科技有限公司助宝兰网进入云贵川渝市场 www.beylai.com