目录:
- 自定义接口常用注解:
@MyController @MyRequestMapping @MyRequestParam
- 包扫描
- 实现接口统一调度: DispatcherServlet
1. 自定义接口常用注解
- 在framework包下新建annotation包,新增3个注解
-
package com.mp.framework.annotation; import java.lang.annotation.*; @Documented @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) public @interface MyController { }
package com.mp.framework.annotation; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; @Target({ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) public @interface MyRequestMapping { String value(); }
package com.mp.framework.annotation; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; @Target(ElementType.PARAMETER) @Retention(RetentionPolicy.RUNTIME) public @interface MyRequestParam { String value(); }
2.包扫描
-
core下新建ClassScanner类
-
package com.mp.framework.core; import sun.net.www.protocol.file.FileURLConnection; import java.io.File; import java.io.IOException; import java.net.JarURLConnection; import java.net.URL; import java.net.URLConnection; import java.util.ArrayList; import java.util.Enumeration; import java.util.List; import java.util.jar.JarEntry; import java.util.jar.JarFile; /** * ClassName: ClassScanner * Function: 包扫描通过类加载器获取目录下的类列表 * Date: 2020-05-07 10:42 * author mp * version V1.0 */ public class ClassScanner { private List<Class<?>> classes; { classes = new ArrayList<>(); } // 扫描指定包下的类列表 public List<Class<?>> doScanner(String packageName) throws IOException, ClassNotFoundException { String path = packageName.replace(".", "/"); // 获取类加载器 ClassLoader classLoader = Thread.currentThread().getContextClassLoader(); Enumeration<URL> resources = classLoader.getResources(path); while (resources.hasMoreElements()){ URL url = resources.nextElement(); if (url.getProtocol().equalsIgnoreCase("file")) { // todo 遍历所有的子类文件 File dir = new File(url.getPath()); for (File file: dir.listFiles()){ if (file.isDirectory()){ doScanner(new StringBuilder(packageName).append(".").append(file.getName()).toString()); }else { classes.add(Class.forName(new StringBuilder(path.replace("/",".")).append(".").append(file.getName()).toString().replace(".class",""))); } } } } return classes; } }
(1) 使用类加载器 classLoader, 文件遍历class文件,包括递归遍历
-
(2)通过反射将扫描的class,做容器统一管理
-
接口uri及方法等信息映射 MappingHandler
-
package com.mp.framework.handler; import com.mp.framework.beans.MyBeanFactory; import org.apache.naming.factory.BeanFactory; import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; import javax.servlet.http.HttpServletRequest; import java.io.IOException; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.Optional; import java.util.stream.Stream; /** * ClassName: MappingHandler * Function: TODO * Date: 2020-05-07 14:12 * author mp * version V1.0 */ public class MappingHandler { private String uri; private Method method; private Class<?> cls; private String[] args; public MappingHandler(String uri, Method method, Class<?> cls, String[] args) { this.uri = uri; this.method = method; this.cls = cls; this.args = args; } // 执行接口方法 public boolean handle(ServletRequest req, ServletResponse res) throws IllegalAccessException, InstantiationException, InvocationTargetException, IOException { String reqUri = ((HttpServletRequest) req).getRequestURI(); if (!this.uri.equals(reqUri)) return false; Object[] params = new Object[args.length]; // 获取参数 for (int i=0;i< args.length;i++){ params[i] = req.getParameter(args[i]); // 获取请求参数值 } Object obj = this.cls.newInstance(); // 执行方法,获取返回值 Object response = this.method.invoke(obj, params); res.getWriter().write(Optional.ofNullable(response).map(r -> r.toString()).orElse("")); // 针对无返回值类型的接口,需要做null处理 return true; } }
说明:(1)拦截servlet服务,获取请求的uri,参数值
-
(2) 通过反射机制,调用方法执行,并将执行结果返回
-
这里,可能会有一个疑问? 怎么获取接口的地址uri、方法名及参数列表呢? 这就涉及到要去解析我们的自定义注解了,根据不同注解的作用来解析出不同的变量
-
增加HandlerManager服务,
-
package com.mp.framework.handler; import com.mp.framework.annotation.MyController; import com.mp.framework.annotation.MyRequestMapping; import com.mp.framework.annotation.MyRequestParam; import org.apache.commons.lang3.StringUtils; import java.lang.reflect.Method; import java.util.ArrayList; import java.util.List; import java.util.function.Consumer; import java.util.stream.Collectors; import java.util.stream.Stream; /** * ClassName: HandlerManager * Function: TODO * Date: 2020-05-07 13:13 * author mp * version V1.0 */ public class HandlerManager { public static List<MappingHandler> list = new ArrayList<>(); // 用于统一管理接口uri映射 public static void resolveMappingHandler(List<Class<?>> classes){ classes.stream().filter(cls -> cls.isAnnotationPresent(MyController.class)) .forEach(controllerHandlerConsumer); } static Consumer<Class<?>> controllerHandlerConsumer = cls -> { // 反射,获取方法 Method[] methods = cls.getDeclaredMethods(); String finalTypeUri = typeUri; Stream.of(methods) .filter(method -> method.isAnnotationPresent(MyRequestMapping.class)) // 只处理controller接口,@myRequestMapping 注解的方法 .forEach(method -> { // 获取uri路径, 注解的值 String uri = method.getDeclaredAnnotation(MyRequestMapping.class).value(); // 获取参数列表 List<String> params = Stream.of(method.getParameters()) .filter(parameter -> parameter.isAnnotationPresent(MyRequestParam.class)) .map(parameter -> parameter.getDeclaredAnnotation(MyRequestParam.class).value()) .collect(Collectors.toList()); MappingHandler mappingHandler = new MappingHandler(uri, method, cls, params.toArray(new String[params.size()])); list.add(mappingHandler); }); }; }
说明:(1)这里使用一个集合,来存放所有的servlet映射
-
(2)通过 cls.isAnnotationPresent(MyController.class) 来过滤,只处理接口相关服务
-
(3)利用反射,获取类所有的方法,判断方法上是否有@MyRequestMapping注解,如果有,就代表是一个接口,所以需要获取注解的value值,即接口请求的uri
-
(4)参数列表获取: 利用反射,获取方法所有的参数 method.getParameters() , 遍历判断是否有@MyRequestParam注解,只要有,就代表是接口的一个请求参数,value值即为参数名 (需要根据这个参数名来获取传递的参数值)
-
(5)遍历完所有的接口后,将其统一管理起来.
-
统一处理请求: DispatcherServlet
-
package com.mp.framework.servlet; import com.mp.framework.handler.HandlerManager; import javax.servlet.*; import java.io.IOException; import java.lang.reflect.InvocationTargetException; /** * ClassName: DispatcherServlet * Function: 统一处理请求 * Date: 2020-05-07 14:59 * author mp * version V1.0 */ public class DispatcherServlet implements Servlet { @Override public void init(ServletConfig config) throws ServletException { } @Override public ServletConfig getServletConfig() { return null; } @Override public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException { HandlerManager.list.forEach(mh -> { // 获取controller注解的方法列表? todo 这里可以优化,遍历去比对所有接口列表(性能低),uri一致就调用,后面实现精准调用. try { mh.handle(req,res); } catch (IllegalAccessException e) { e.printStackTrace(); } catch (InstantiationException e) { e.printStackTrace(); } catch (InvocationTargetException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } }); } @Override public String getServletInfo() { return null; } @Override public void destroy() { } }
说明,需要在TomcatServer中注册 DispatcherServlet,拦截所有的请求;--这一点,性能低下,可以实现精准调用
-
修改TomcatServer
-
package com.mp.framework.web.server; import com.mp.framework.servlet.DispatcherServlet; import com.mp.framework.servlet.Servlet01; import com.mp.framework.servlet.Servlet02; import org.apache.catalina.Context; import org.apache.catalina.LifecycleException; import org.apache.catalina.core.StandardContext; import org.apache.catalina.startup.Tomcat; import javax.servlet.Servlet; /** * ClassName: TomcatServer * Function: 集成Tomcat服务器 * Date: 2020-05-07 09:55 * author mp * version V1.0 */ public class TomcatServerNew { private Tomcat tomcat; private String[] args; public TomcatServerNew(String[] args){ this.args = args; } public void startServer() throws LifecycleException { // 实例化Tomcat tomcat = new Tomcat(); tomcat.setPort(9999); tomcat.start(); // 实例化context容器 Context context = new StandardContext(); context.setPath(""); context.addLifecycleListener(new Tomcat.FixContextListener()); Servlet dispatcherServlet = new DispatcherServlet(); Tomcat.addServlet(context,"dispatcherServlet",dispatcherServlet).setAsyncSupported(true); // 添加URl映射 context.addServletMappingDecoded("/","dispatcherServlet"); tomcat.getHost().addChild(context); // 设置守护进程 Thread thread = new Thread("tomcat-await-thread") { @Override public void run() { super.run(); TomcatServerNew.this.tomcat.getServer().await(); } }; // 设置为非守护进程 thread.setDaemon(false); thread.start(); } }
监听dispatcherServlet,拦截所有请求.
-
修改MyApplication 服务,启动包扫描,旨在处理接口相关注解服务
-
package com.mp.framework.starter; import com.mp.framework.beans.MyBeanFactory; import com.mp.framework.core.ClassScanner; import com.mp.framework.handler.HandlerManager; import com.mp.framework.web.server.TomcatServer; import com.mp.framework.web.server.TomcatServerNew; import org.apache.catalina.LifecycleException; import java.io.IOException; import java.util.List; /** * ClassName: MyApplication * Function: TODO * Date: 2020-05-07 09:44 * author mp * version V1.0 */ public class MyApplication { public static void run(Class<?> cls,String[] args) throws LifecycleException, IOException, ClassNotFoundException { System.out.println("hello my0-spring application!!!"); // 实例化Tomcat服务 // TomcatServer tomcatServer = new TomcatServer(args); TomcatServerNew tomcatServer = new TomcatServerNew(args); tomcatServer.startServer(); // 添加获取类列表和反射调用 List<Class<?>> list = new ClassScanner().doScanner(cls.getPackage().getName()); list.stream().forEach(c -> System.out.println(c.getName())); // 筛选controller 接口,处理接口映射 HandlerManager.resolveMappingHandler(list); } }
说明:(1)doScanner 扫描包及其子包
-
(2)HandlerManager.resolveMappingHandler(list); 用于统计接口调度做准备
-
(3)这里使用新的TomcatServerNew服务,只监听 dispatcherServlet
-
demo中新建TestController接口
-
package com.mp.demo.controller; import com.mp.demo.service.Service01; import com.mp.framework.annotation.MyController; import com.mp.framework.annotation.MyRequestMapping; import com.mp.framework.annotation.MyRequestParam; import com.mp.framework.beans.MyAutowired; /** * ClassName: TestController * Function: TODO * Date: 2020-05-07 15:04 * author mp * version V1.0 */ @MyController public class TestController { @MyRequestMapping("/test") public String test01(@MyRequestParam("name") String name){ System.out.println("do it test01..."); return "this name: "+name; } @MyRequestMapping("/test02") public void test02(@MyRequestParam("addr") String addr){ System.out.println("do it test02: "+ addr); } }
-
启动服务,访问接口 http://localhost:9999/test?name=mp 成功访问.
-
可能有人会问? 在springboot框架中,在接口上经常指定RequestMapping地址,那我们这里能否也加上呢,这个当然可以,想一想原理也很简单,就是在遍历类的注解上增加@MyRequestMapping扫描,获取values值,到时候作为方法uri的前缀就可以解决这个问题了
-
实现: 1. @MyRequestMapping 增加target范围
-
package com.mp.framework.annotation; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; @Target({ElementType.METHOD,ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) public @interface MyRequestMapping { String value(); }
HandlerManager 服务中增加类上@MyRequestMapping 注解解析服务
-
package com.mp.framework.handler; import com.mp.framework.annotation.MyController; import com.mp.framework.annotation.MyRequestMapping; import com.mp.framework.annotation.MyRequestParam; import org.apache.commons.lang3.StringUtils; import java.lang.reflect.Method; import java.util.ArrayList; import java.util.List; import java.util.function.Consumer; import java.util.stream.Collectors; import java.util.stream.Stream; /** * ClassName: HandlerManager * Function: TODO * Date: 2020-05-07 13:13 * author mp * version V1.0 */ public class HandlerManager { public static List<MappingHandler> list = new ArrayList<>(); // 用于统一管理接口uri映射 public static void resolveMappingHandler(List<Class<?>> classes){ classes.stream().filter(cls -> cls.isAnnotationPresent(MyController.class)) .forEach(controllerHandlerConsumer); } static Consumer<Class<?>> controllerHandlerConsumer = cls -> { // 判断controller上是否有 MyRequestMapping 注解,需要加到uri上 String typeUri = ""; if (cls.isAnnotationPresent(MyRequestMapping.class)){ typeUri = cls.getDeclaredAnnotation(MyRequestMapping.class).value(); } // 反射,获取方法 Method[] methods = cls.getDeclaredMethods(); String finalTypeUri = typeUri; Stream.of(methods) .filter(method -> method.isAnnotationPresent(MyRequestMapping.class)) // 只处理controller接口,@myRequestMapping 注解的方法 .forEach(method -> { // 获取uri路径, 注解的值 String uri = method.getDeclaredAnnotation(MyRequestMapping.class).value(); if (StringUtils.isNotEmpty(finalTypeUri)){ uri = new StringBuilder(finalTypeUri).append(uri).toString(); } // 获取参数列表 List<String> params = Stream.of(method.getParameters()) .filter(parameter -> parameter.isAnnotationPresent(MyRequestParam.class)) .map(parameter -> parameter.getDeclaredAnnotation(MyRequestParam.class).value()) .collect(Collectors.toList()); MappingHandler mappingHandler = new MappingHandler(uri, method, cls, params.toArray(new String[params.size()])); list.add(mappingHandler); }); }; }
TestController类上增加 @MyRequestMapping("/v1")
-
package com.mp.demo.controller; import com.mp.demo.service.Service01; import com.mp.framework.annotation.MyController; import com.mp.framework.annotation.MyRequestMapping; import com.mp.framework.annotation.MyRequestParam; import com.mp.framework.beans.MyAutowired; /** * ClassName: TestController * Function: TODO * Date: 2020-05-07 15:04 * author mp * version V1.0 */ @MyController @MyRequestMapping("/v1") public class TestController { @MyAutowired Service01 service01; @MyRequestMapping("/test") public String test01(@MyRequestParam("name") String name){ System.out.println("do it test01..."); return service01.method01(name); } @MyRequestMapping("/test02") public void test02(@MyRequestParam("addr") String addr){ System.out.println("do it test02: "+ addr); } }
重启服务,访问http://localhost:9999/v1/test?name=mp 正常.
-
2020-05-07 优化接口同一调度DispatcherServlet,将uri映射转存Map存储,这样在分配接口时,就不用做list遍历,直接从map中取出对应的MappingHandler来处理,接口量大时,性能高些
-
package com.mp.framework.handler; import com.mp.framework.annotation.MyController; import com.mp.framework.annotation.MyRequestMapping; import com.mp.framework.annotation.MyRequestParam; import org.apache.commons.lang3.StringUtils; import java.lang.reflect.Method; import java.util.List; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import java.util.function.Consumer; import java.util.stream.Collectors; import java.util.stream.Stream; /** * ClassName: HandlerManager * Function: TODO * Date: 2020-05-07 13:13 * author mp * version V1.0 */ public class HandlerManager { public static Map<String,MappingHandler> map = new ConcurrentHashMap<>(); // 用于统一管理接口uri映射 public static void resolveMappingHandler(List<Class<?>> classes){ classes.stream().filter(cls -> cls.isAnnotationPresent(MyController.class)) .forEach(controllerHandlerConsumer); } static Consumer<Class<?>> controllerHandlerConsumer = cls -> { // 判断controller上是否有 MyRequestMapping 注解,需要加到uri上 String typeUri = ""; if (cls.isAnnotationPresent(MyRequestMapping.class)){ typeUri = cls.getDeclaredAnnotation(MyRequestMapping.class).value(); } // 反射,获取方法 Method[] methods = cls.getDeclaredMethods(); String finalTypeUri = typeUri; Stream.of(methods) .filter(method -> method.isAnnotationPresent(MyRequestMapping.class)) // 只处理controller接口,@myRequestMapping 注解的方法 .forEach(method -> { // 获取uri路径, 注解的值 String uri = method.getDeclaredAnnotation(MyRequestMapping.class).value(); if (StringUtils.isNotEmpty(finalTypeUri)){ uri = new StringBuilder(finalTypeUri).append(uri).toString(); } // 获取参数列表 List<String> params = Stream.of(method.getParameters()) .filter(parameter -> parameter.isAnnotationPresent(MyRequestParam.class)) .map(parameter -> parameter.getDeclaredAnnotation(MyRequestParam.class).value()) .collect(Collectors.toList()); MappingHandler mappingHandler = new MappingHandler(uri, method, cls, params.toArray(new String[params.size()])); map.put(uri,mappingHandler); }); }; }
package com.mp.framework.servlet; import com.mp.framework.handler.HandlerManager; import com.mp.framework.handler.MappingHandler; import javax.servlet.*; import javax.servlet.http.HttpServletRequest; import java.io.IOException; import java.lang.reflect.InvocationTargetException; /** * ClassName: DispatcherServlet * Function: 统一处理请求 * Date: 2020-05-07 14:59 * author mp * version V1.0 */ public class DispatcherServlet implements Servlet { @Override public void init(ServletConfig config) throws ServletException { } @Override public ServletConfig getServletConfig() { return null; } @Override public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException { /*HandlerManager.list.forEach(mh -> { // 获取controller注解的方法列表? todo 这里可以优化,遍历去比对所有接口列表(性能低),uri一致就调用,后面实现精准调用. try { mh.handle(req,res); } catch (IllegalAccessException e) { e.printStackTrace(); } catch (InstantiationException e) { e.printStackTrace(); } catch (InvocationTargetException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } });*/ String reqUri = ((HttpServletRequest) req).getRequestURI(); try { HandlerManager.map.get(reqUri).handle(req, res); } catch (IllegalAccessException e) { e.printStackTrace(); } catch (InstantiationException e) { e.printStackTrace(); } catch (InvocationTargetException e) { e.printStackTrace(); } } @Override public String getServletInfo() { return null; } @Override public void destroy() { } }
改造完毕...