目录
1、基本思路
1.1、配置
1.2、初始化
1.3、运行
2、总体设计
2.1、自定义注解
2.2、配置文件
2.3、自定义Servlet
3、具体实现
3.1、加载配置文件
3.2、扫描配置对应包
3.3、IOC的初始化与实例注入
3.4、依赖注入
3.5、URL与方法的匹配
3.6、具体调用实现
spring的主要功能是IOC,DI,MVC,AOP,如果一个系统能实现这些功能,就可以实现一个简易版本的Spring框架。
1、基本思路Spring的实现可以分为三个基本的阶段,配置,初始化,运行。
1.1、配置配置web.xml:DispatcherServlet,SpringMVC的入口,最初始的入口
设定init-param:主配置文件的接入,contextConfigLocation=classpath:application.xml
设定url-pattern:servlet的配置/*
配置Annotation:对应的各个层级注解,@Controller,@Service,@Component
1.2、初始化调用init()方法:加载配置文件
IOC容器初始化:IOC容器本质还是一个Map
扫描类:会在配置文件中配置一个暴露路径
创建初始化实例并保存至容器:使用反射机制将类的实例放入IOC中
进行DI:扫描IOC实例,给没有赋值的属性赋值
初始化HandlerMapping:MVC结构部分,将URL和Method的对应关系映射到Map中
1.3、运行调用doPost()/doGet():Web容器调用doPost()/doGet()方法,获得request/response
匹配HandlerMapping:从request对象获得用户输入的URL,从HandlerMapping获取对应method
反射调用method.invoker():反射调用方法并返回结果
response.getWrite().write():将返回结果输出到浏览器
2、总体设计 2.1、自定义注解在设计自己的Spring框架之前,需要一些新的注解来区别Spring注解
这些注解就是最简单的自定义注解,不包含任何其他作用。
2.2、配置文件web.xml文件的详细配置,有关mvc和配置
Gupao Web Application mymvc com.example.springwrite.myframework.v1.MyDispatchServlet contextConfigLocation application.properties 1 mymvc /*
application.properties文件详细配置
scanPackage=com.example.springwrite.demo2.3、自定义Servlet
重新定义 Servlet,重写init,dopost和doget方法。
public class MyDispatchServlet extends HttpServlet { @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { } @Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { } @Override public void init(ServletConfig config) throws ServletException { } }
在init方法中,需要依照步骤实现
- 加载配置文件
- 根据配置文件扫描对应包下文件,获取到对应的类名列表
- 初始化IOC,根据类名列表进行实例化,将类放入IOC
- 对IOC容器内的bean进行DI
- 对URL和Method进行匹配
- 对请求进行方法实现
private Properties contextConfig = new Properties(); @Override public void init(ServletConfig config) throws ServletException { //1、加载配置文件 doLoadConfig(config.getInitParameter("contextConfigLocation")); }
第一个方法,对配置文件进行读取,指定读取对应的contextConfigLocation,读取方式为流,将读取到的配置文件数据保存到属性Properties
private void doLoadConfig(String contextConfigLocation) { InputStream resourceAsStream = this.getClass().getClassLoader().getResourceAsStream(contextConfigLocation); try { contextConfig.load(resourceAsStream); } catch (IOException e) { e.printStackTrace(); } finally { if (resourceAsStream != null) { try { resourceAsStream.close(); } catch (IOException e) { e.printStackTrace(); } } } }3.2、扫描配置对应包
private Properties contextConfig = new Properties(); private ListclassNames = new ArrayList (); @Override public void init(ServletConfig config) throws ServletException { //1、加载配置文件 doLoadConfig(config.getInitParameter("contextConfigLocation")); //2、扫描相关的类 doScanner(contextConfig.getProperty("scanPackage")); }
获取到包地址之后,需要进行一次转化,因为得到的地址是以“.”作为分隔符的,需要转化为“/”才能被识别。
获取到包下的文件列表后,需要进行循环处理,如果扫描到的是文件夹,就需要进行递归扫描,继续下一层级的搜索。
如果不是文件夹,就需要查看文件是否为class类型的文件,如果是class文件,就提取出class的名,将扫描包名增加在class名之前,组成可以被反射调用到的地址
private void doScanner(String scanPackage) { URL url = this.getClass().getClassLoader().getResource("/" + scanPackage.replaceAll("\.", "/")); File classpath = new File(url.getFile()); for (File file : classpath.listFiles()) { if (file.isDirectory()) { doScanner(scanPackage + "." + file.getName()); } else { if (!file.getName().endsWith(".class")) { continue; } String fileName = file.getName().replace(".class", ""); String className = scanPackage + "." + fileName; classNames.add(className); } } }3.3、IOC的初始化与实例注入
private Properties contextConfig = new Properties(); private ListclassNames = new ArrayList (); private Map ioc = new HashMap (); @Override public void init(ServletConfig config) throws ServletException { //1、加载配置文件 doLoadConfig(config.getInitParameter("contextConfigLocation")); //2、扫描相关的类 doScanner(contextConfig.getProperty("scanPackage")); //3、初始化IOC容器,将扫描到的类进行实例化,缓存到IOC容器中 doInstance(); }
对获取到的文件名进行循环,然后进行实例化,将具有@MyController和@MyService等需要被IOC装载的类读取到IOC中。
对于有@MyController注解的类,按照Spring的默认规则,以类名首字母小写作为IOC的bean名称即可
对于有@MyService注解的类,如果没有别名,按照Spring的默认规则,以类名首字母小写作为IOC的bean名称即可。如果具有别名,则以别名作为bean名称,如果是一个接口,则将全名作为Bean名称。
private void doInstance() { if (classNames.isEmpty()) { return; } try { for (String className : classNames) { Class clazz = Class.forName(className); if (clazz.isAnnotationPresent(MyController.class)) { Object instance = clazz.newInstance(); String beanName = toLowerFirstCase(clazz.getSimpleName()); ioc.put(beanName, instance); } else if (clazz.isAnnotationPresent(MyService.class)) { Object instance = clazz.newInstance(); //1、首字母小写的类名 String beanName = toLowerFirstCase(clazz.getSimpleName()); //2、同名类使用别名 MyService myService = instance.getClass().getAnnotation(MyService.class); if (!"".equals(myService.value())) { beanName = myService.value(); } ioc.put(beanName, instance); //3、如果是接口,就只能初始化实现类 for (Class claz : clazz.getInterfaces()) { if (ioc.containsKey(claz.getName())) { throw new Exception("接口多实现错误,清使用别名"); } ioc.put(claz.getName(), instance); } } else { continue; } } } catch (Exception e) { e.printStackTrace(); } }3.4、依赖注入
@Override public void init(ServletConfig config) throws ServletException { //1、加载配置文件 doLoadConfig(config.getInitParameter("contextConfigLocation")); //2、扫描相关的类 doScanner(contextConfig.getProperty("scanPackage")); //3、初始化IOC容器,将扫描到的类进行实例化,缓存到IOC容器中 doInstance(); //4、完成依赖注入 doAutowired(); }
被@MyAutowired注解修饰的属性无论是否具有私有修饰,都要被强制注入。在没有别名的情况下,用首字母小写的类名在IOC中寻找,如果有别名,按照别名进行寻找。
在注入私有属性时,反射需要允许强制访问。
private void doAutowired() { if (ioc.isEmpty()) { return; } for (Map.Entry3.5、URL与方法的匹配instance : ioc.entrySet()) { //忽略字段的修饰符 for (Field field : instance.getValue().getClass().getDeclaredFields()) { if (!field.isAnnotationPresent(MyAutowired.class)) { continue; } MyAutowired myAutowired = field.getAnnotation(MyAutowired.class); String beanName = myAutowired.value(); if ("".equals(beanName)) { beanName = field.getType().getName(); } //私有变量需要开启反射强制访问 field.setAccessible(true); try { field.set(ioc.get(toLowerFirstCase(field.getDeclaringClass().getSimpleName())), ioc.get(beanName)); } catch (Exception e) { e.printStackTrace(); } } } }
private MaphandlerMapping = new HashMap (); @Override public void init(ServletConfig config) throws ServletException { //1、加载配置文件 doLoadConfig(config.getInitParameter("contextConfigLocation")); //2、扫描相关的类 doScanner(contextConfig.getProperty("scanPackage")); //3、初始化IOC容器,将扫描到的类进行实例化,缓存到IOC容器中 doInstance(); //4、完成依赖注入 doAutowired(); //5、初始化HandlerMapping doInitHandlerMapping(); }
查看被 @MyController的类是否也被@MyRequestMapping修饰,如果有,就在url前增加前缀
将被@MyController注解修饰的类中的公有方法取出,将没有被@MyRequestMapping修饰的方法排除,获取到对应的URL后,拼接,加入到HandlerMapping中,完成映射
private void doInitHandlerMapping() { if (ioc.isEmpty()) { return; } for (Map.Entry3.6、具体调用实现instance : ioc.entrySet()) { Class clazz = instance.getValue().getClass(); String url = ""; if (!clazz.isAnnotationPresent(MyController.class)) { continue; } if (clazz.isAnnotationPresent(MyRequestMapping.class)) { MyRequestMapping myRequestMapping = (MyRequestMapping) clazz.getAnnotation(MyRequestMapping.class); url = "/" + myRequestMapping.value(); } for (Method method : clazz.getMethods()) { if (!method.isAnnotationPresent(MyRequestMapping.class)) { continue; } MyRequestMapping myRequestMapping = method.getAnnotation(MyRequestMapping.class); handlerMapping.put((url + "/" + myRequestMapping.value()).replaceAll("/+", "/"), method); } } }
@Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { //6、根据URL委派给具体的调用方法 try { doDispatch(req, resp); } catch (Exception e) { e.printStackTrace(); resp.getWriter().write("500 exception"); } }
从HttpServletRequest中获取url和classpath,去除重复的“//”,从其中获取到参数列表和对应的参数注解,从这两者的匹配中获取到一个完整的参数列表,再用反射调用方法。
private void doDispatch(HttpServletRequest req, HttpServletResponse resp) throws IOException, InvocationTargetException, IllegalAccessException, ClassNotFoundException, InstantiationException { String url = req.getRequestURI(); String contextPath = req.getContextPath(); url = url.replaceAll(contextPath, "").replaceAll("/+", "/"); if (!this.handlerMapping.containsKey(url)) { resp.getWriter().write("404 not found"); return; } Method method = handlerMapping.get(url); MapparamMapping = new HashMap (); Annotation[][] parameterAnnotations = method.getParameterAnnotations(); for (int i = 0; i < parameterAnnotations.length; i++) { for (Annotation annotation : parameterAnnotations[i]) { if (annotation instanceof MyRequestParam) { String value = ((MyRequestParam) annotation).value(); if (!"".equals(value)) { paramMapping.put(value, i); } } } } Class>[] parameterTypes = method.getParameterTypes(); for (int i = 0; i < parameterTypes.length; i++) { Class> parameterType = parameterTypes[i]; if (HttpServletRequest.class == parameterType || HttpServletResponse.class == parameterType) { paramMapping.put(parameterType.getName(), i); } } Object[] paramValues = new Object[paramMapping.size()]; Map params = req.getParameterMap(); for (Map.Entry param : params.entrySet()) { String value = Arrays.toString(param.getValue()) .replaceAll("\[|\]", "") .replaceAll("\s", ""); if (!paramMapping.containsKey(param.getKey())) { continue; } paramValues[paramMapping.get(param.getKey())] = value; } if (paramMapping.containsKey(HttpServletRequest.class.getName())) { int index = paramMapping.get(HttpServletRequest.class.getName()); paramValues[index] = req; } if (paramMapping.containsKey(HttpServletResponse.class.getName())) { int index = paramMapping.get(HttpServletResponse.class.getName()); paramValues[index] = resp; } String beanName = toLowerFirstCase(method.getDeclaringClass().getSimpleName()); Object obj = ioc.get(beanName); method.invoke(obj, paramValues); }
最后启动代码,访问一个固定的url:http://localhost:8080/user/name?name=lily
如果一切正常,将得到一个完整的返回结果。
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)