目录的大致情况:所有的类都会加进来.
1.首先先写基本的Controller Service ServiceImpl
Controller Service ServiceImpl是用来验证下面写的框架信息是否正常运行
package demo.controller; import demo.service.HelloService; import framework.annotation.FAutowried; import framework.annotation.FController; import framework.annotation.FRequestMapping; import framework.annotation.FRequestParam; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; import java.io.PrintWriter; /** * DemoController. * * @author Feng Yongkang, 2019/9/9 * @version v1.0 */ @FController @FRequestMapping("/demo") public class HelloController { @FAutowried private HelloService service; @FRequestMapping("/getName") public void getName(HttpServletRequest req, HttpServletResponse resp, @FRequestParam("name") String name) { service.firstServlet(name); try { System.out.println(name); PrintWriter writer = resp.getWriter(); writer.write(name); writer.flush(); writer.close(); } catch (IOException e) { e.printStackTrace(); } finally { } } }
package demo.service; /** * HelloService. * * @author Feng Yongkang, 2019/9/10 * @version v1.0 */ public interface HelloService { void firstServlet(String str); }
package demo.service.impl; import demo.service.HelloService; import framework.annotation.FService; /** * HelloService. * * @author Feng Yongkang, 2019/9/10 * @version v1.0 */ @FService public class HelloServiceImpl implements HelloService { public void firstServlet(String str) { System.out.println("你好,我接收到一个参数:" + str); } }
2.需要用的注解
这里是考虑最简单常用的几个注解.可以支持简单的请求处理
package framework.annotation; import java.lang.annotation.*; @Target(ElementType.FIELD)//这个注解的使用范围 @Retention(RetentionPolicy.RUNTIME)//生命周期 @Documented public @interface FAutowried { String value() default ""; }
package framework.annotation; import java.lang.annotation.*; @Target(ElementType.TYPE)//这个注解的使用范围 @Retention(RetentionPolicy.RUNTIME)//生命周期 @Documented public @interface FController { String value() default ""; }
package framework.annotation; import java.lang.annotation.*; @Target({ElementType.METHOD, ElementType.TYPE})//这个注解的使用范围 @Retention(RetentionPolicy.RUNTIME)//生命周期 @Documented public @interface FRequestMapping { String value() default ""; }
package framework.annotation; import java.lang.annotation.*; @Target(ElementType.PARAMETER)//这个注解的使用范围 @Retention(RetentionPolicy.RUNTIME)//生命周期 @Documented public @interface FRequestParam { String value() default ""; }
package framework.annotation; import java.lang.annotation.*; @Target(ElementType.TYPE)//这个注解的使用范围 @Retention(RetentionPolicy.RUNTIME)//生命周期 @Documented public @interface FService { String value() default ""; }
3.核心DispatcherServlet
这个是关键.所有的功能全包含在这里.
dispatcherServlet查看顺序,先看init方法(里面方法步骤一个一个看).init方法执行完毕,则Spring容器也就启动完毕.
再看service方法,如何处理请求.一个请求到达的时候,根据uri去HandlerMapping用适合的方法处理请求.
package framework; import framework.annotation.*; import javax.servlet.ServletConfig; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.File; import java.io.IOException; import java.io.InputStream; import java.lang.annotation.Annotation; import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.net.URL; import java.util.*; import java.util.regex.Matcher; import java.util.regex.Pattern; /** * FDispatcherServlet. * * @author Feng Yongkang, 2019/9/9 * @version v1.0 */ public class FDispatcherServlet1 extends HttpServlet { //resources下的配置文件中的内容 private Properties contextConfig = new Properties(); //指定要扫描路径的下的类名的集合 private List<String> classNames = new ArrayList<String>(); //classNames中被注解标识为bean的类实例 private Map<String, Object> ioc = new HashMap<String, Object>(); //保存所有url与映射的关系 private List<Handler> handlerMappping = new ArrayList<Handler>(); @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { doPost(req, resp); } @Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { try { doDispatch(req, resp); } catch (Exception e) { e.printStackTrace(); resp.getWriter().write("500 EXCEPTION"); } } /** * 根据uri得到匹配的Handler.根据Handler中的paramIndexMapping填充方法中的参数. * 参数是实参,根据请求中的数据获取的. * 通过代理模式,执行ioc容器下保存的Controller的bean对应的URI的方法.完成请求处理. * * @param req * @param resp * @throws IOException */ private void doDispatch(HttpServletRequest req, HttpServletResponse resp) throws Exception { try { //查找与请求匹配的handler Handler handler = getHandler(req); if (handler == null) { resp.getWriter().write("404 NOT FOUND"); return; } //获得方法的形参类型 Class<?>[] parameterTypes = handler.method.getParameterTypes(); //在调用invoke代理时,根据参数的位置,传递参数用. Object[] paramValues = new Object[parameterTypes.length]; //获得请求中的所有参数,并遍历,看请求中参数的key是否出现 Map<String, String[]> params = req.getParameterMap(); for (Map.Entry<String, String[]> param : params.entrySet()) { String value = Arrays.toString(param.getValue()).replaceAll("\[|\]", "").replaceAll("/+", "/"); //如果找到对象开始填充参数值 if (!handler.paramIndexMapping.containsKey(param.getKey())) { continue; } Integer index = handler.paramIndexMapping.get(param.getKey()); if (index != null) { paramValues[index] = convert(parameterTypes[index], value); } } //设置方法中的request与response对象,如果方法中有这个key就返回对应的value如果没有就返回null Integer reqIndex = handler.paramIndexMapping.get(HttpServletRequest.class.getName()); if (reqIndex != null) { paramValues[reqIndex] = req; } Integer respIndex = handler.paramIndexMapping.get(HttpServletResponse.class.getName()); if (respIndex != null) { paramValues[respIndex] = resp; } handler.method.invoke(handler.controller, paramValues); } catch (IllegalAccessException e) { e.printStackTrace(); } catch (IllegalArgumentException e) { e.printStackTrace(); } catch (InvocationTargetException e) { e.printStackTrace(); } } @Override public void init(ServletConfig config) throws ServletException { //1 加载配置文件 doLoadConfig(config.getInitParameter("contextConfigLocation")); //2 扫描所有相关的类 doScanner(contextConfig.getProperty("scanPackage")); //3 初始化所有相关的类 doInstance(); //4 自动注入.到这里Spring的功能已经完成 doAutowired(); //5 初始HandlerMapping,这里开始就是SpringMVC的功能 doHandlerMapping(); System.out.println("Spring初始化完成"); } /** * 遍历ioc容器的中bean的方法.private的方法一般不作为请求. * public类型的被FAutowried修饰.根据类上面的注解和方法上面的注解,生成uri转为正则方便后续的匹配. * uri的正则,Controller,还有对应的方法组成一个Handler.放入List集合中.方便请求时生成代理,处理请求. */ private void doHandlerMapping() { if (ioc.isEmpty()) { return; } for (Map.Entry<String, Object> entry : ioc.entrySet()) { Class<?> claszz = entry.getValue().getClass(); if (!claszz.isAnnotationPresent(FController.class)) { continue; } String baseUrl = ""; //获取公用uri if (claszz.isAnnotationPresent(FRequestMapping.class)) { FRequestMapping annotation = claszz.getAnnotation(FRequestMapping.class); baseUrl = annotation.value(); } //扫描所有公共方法 for (Method method : claszz.getMethods()) { if (!method.isAnnotationPresent(FRequestMapping.class)) { continue; } FRequestMapping annotation = method.getAnnotation(FRequestMapping.class); //如果用户的FRequestMapping注解中没有/,在类注解和方法注解中,拼接的时候给他加上,防止用户加了,在拼接后的URI中,把多于一个/换成一个/ String methodUrl = ("/" + baseUrl + "/" + annotation.value()).replaceAll("/+", "/"); //handlerMapping.put(methodUrl, method); Pattern pattern = Pattern.compile(methodUrl); //pattern:uri的正则匹配格式,method:处理对应请求的方法,entry.getValue():方法所在的实例化后放在ioc中的bean(Controller实例) handlerMappping.add(new Handler(entry.getValue(), method, pattern)); } } } /** * 迭代ioc容器中所有bean的字段.如果这个字段有FAutowried修饰. * 1.优先根据注解的别名去ioc容器中查找对应的实例进行注入. * 2.字段所属类型的类型全名(一般写的都是接口类型) * 3.没有考虑Controller通过接口的实现进行声明而不是接口类型进行声明. */ private void doAutowired() { if (ioc.isEmpty()) { return; } //循环IOC中的所有类,然后对需要赋值的属性进行赋值 for (Map.Entry<String, Object> entry : ioc.entrySet()) { //获得bean实例中所有字段,判断字段是否有@Autowried, // 如果有,优先注解自定义的属性名,默认字段对应的类型名(在放入IOC时,对于接口类型,直接接口的全名作为key,对于接口实现类,类名首字母小写作为类型名) Field[] declaredFields = entry.getValue().getClass().getDeclaredFields(); for (Field declaredField : declaredFields) { if (!declaredField.isAnnotationPresent(FAutowried.class)) { continue; } FAutowried annotation = declaredField.getAnnotation(FAutowried.class); String beanName = annotation.value().trim(); if ("".equals(beanName)) { beanName = declaredField.getType().getName(); } //根据key去容器中查找,注入到字段中. boolean accessible = declaredField.isAccessible(); declaredField.setAccessible(true); try { declaredField.set(entry.getValue(), ioc.get(beanName));//注入 } catch (IllegalAccessException e) { e.printStackTrace(); } declaredField.setAccessible(accessible); } } } /** * 把classNames中保存的符合的作为bean的类进行实例化并放入到容器中 * 生成的bean的命名规则1.默认类名首字母小写 2.优先使用自定义名字 3.如果是一个接口,接口全名作为key, * 如果一个接口有多个实例就会报错,体现在.查看这个实例的接口名是否已经存在,已经存在则说明,这个接口有别的实现类. */ private void doInstance() { if (classNames.isEmpty()) { return; } try { for (String className : classNames) { Class<?> clazz = Class.forName(className); //实例化加了注解的类 //Controller注解的类实例化 if (clazz.isAnnotationPresent(FController.class)) { String beanName = lowerFirstCase(clazz.getSimpleName()); ioc.put(beanName, clazz.newInstance()); //Service注解的类的实例化 } else if (clazz.isAnnotationPresent(FService.class)) { //获得这个注解,看注解中是否有自定义的注解名,自定义的优先,然后是默认的类名首字符小写 //1.自定义的beanName FService annotation = clazz.getAnnotation(FService.class); String beanName = annotation.value(); //2.默认首字母小写的beanName if ("".equals(beanName)) { beanName = lowerFirstCase(clazz.getSimpleName()); } Object instance = clazz.newInstance(); ioc.put(beanName, instance); //3.这个类如果有接口. //getInterfaces获得这个类所实现的所有接口,如果这个类对应的接口的getName已经存在,证明,这个接口不止自己一个实现类. //一个接口如果有多个实现类,在注入的时候不知道注入哪一个,如果存在这种情况抛出异常.不存在就以接口名为key,存入实现类的实例. for (Class<?> anInterface : clazz.getInterfaces()) { if (ioc.containsKey(anInterface.getName())) { throw new Exception("The BeanName is exists"); } ioc.put(anInterface.getName(), instance); } } else { continue; } } } catch (ClassNotFoundException e) { e.printStackTrace(); } catch (IllegalAccessException e) { e.printStackTrace(); } catch (InstantiationException e) { e.printStackTrace(); } catch (Exception e) { e.printStackTrace(); } } /** * 把指定的路径下所有的class类的类名保存到classNames集合中. * * @param scanPackage 要扫描的包路径 */ //得到所有的class. private void doScanner(String scanPackage) { //要把所有的文件路径处理成包路径 URL url = this.getClass().getClassLoader().getResource("/" + scanPackage.replaceAll("\.", "/")); File classDir = new File(url.getFile()); for (File file : classDir.listFiles()) { if (file.isDirectory()) { doScanner(scanPackage + "." + file.getName()); } else { if (!file.getName().endsWith(".class")) { //如果不是class文件进行下一次循环. continue; } String className = scanPackage + "." + file.getName().replace(".class", ""); classNames.add(className); } } } /** * 加载外部的配置文件.这里用比较简单的properties;key-value类型的对象. * * @param contextConfigLocation 对应web.xml中的配置的资源文件的路径 */ private void doLoadConfig(String contextConfigLocation) { InputStream is = this.getClass().getClassLoader().getResourceAsStream(contextConfigLocation); try { contextConfig.load(is); } catch (IOException e) { e.printStackTrace(); } finally { if (null != is) { try { is.close(); } catch (IOException e) { e.printStackTrace(); } } } } /** * 把一个首字符大写的字符串转为首字母小写的字符串. * * @param str 首字母大写的字符串 * @return 首字母小写的字符串 */ private String lowerFirstCase(String str) { char[] chars = str.toCharArray(); chars[0] += 32; return String.valueOf(chars); } /** * 根据请求路径获得与路径匹配的Handler * * @param req * @return * @throws Exception */ private Handler getHandler(HttpServletRequest req) throws Exception { if (handlerMappping.isEmpty()) { return null; } //绝对路径 String requestURI = req.getRequestURI(); //绝对路径中URI之外的路径 String contextPath = req.getContextPath(); requestURI.replace(contextPath, "").replaceAll("/+", "/"); for (Handler handler : handlerMappping) { try { //比较请求中的URL与存在handler中的是否匹配 Matcher matcher = handler.pattern.matcher(requestURI); //如果没有继续下一个匹配 if (!matcher.matches()) { continue; } return handler; } catch (Exception e) { throw e; } } return null; } private Object convert(Class<?> type, String value) { if (Integer.class == type) { return Integer.valueOf(value); } return value; } /** * Handler 记录Conroller中的RequestMapping和Method的对应关系 */ private class Handler { protected Object controller; protected Method method; protected Pattern pattern; protected Map<String, Integer> paramIndexMapping;//参数的别名,或者类型名作为key,位置作为value. public Handler(Object controller, Method method, Pattern pattern) { this.controller = controller;//保存方法对应的实例 this.method = method;//保存映射的方法 this.pattern = pattern; paramIndexMapping = new HashMap<String, Integer>();//参数顺序 putParamIndexMapping(method); } /** * 把Controller中方法中的参数有注解的根据注解名,或者request response类型的参数放入到map中 * 只考虑有FRequestParam注解的别名与request,response的类型名.作为key,在参数中的位置作为value. * 在调用invoke方法的时候,可以按照参数顺序进行传参. * * @param method */ private void putParamIndexMapping(Method method) { //1.一个参数可能有很多注解,一个方法有可能有很多参数,所以返回一个注解的二维数组.如果注解里面指定了参数的参数的别名,就把参数别名作为key,参数位置作为value放入map. //这里仅仅考虑FRequestParam注解,像验证,RequestBody,没有注解没有考虑. Annotation[][] pa = method.getParameterAnnotations(); for (int i = 0; i < pa.length; i++) { for (Annotation a : pa[i]) { if (a instanceof FRequestParam) { String paranName = ((FRequestParam) a).value(); if (!"".equals(paranName.trim())) { paramIndexMapping.put(paranName, i); } } } } //2.对于 request与response,默认放入根据类型名与参数位置放入map Class<?>[] parameterTypes = method.getParameterTypes(); for (int i = 0; i < parameterTypes.length; i++) { Class<?> type = parameterTypes[i]; if (type == HttpServletRequest.class || type == HttpServletResponse.class) { paramIndexMapping.put(type.getName(), i); } } } } }