大体流程
自定义MVC框架
注解开发
package com.test.edu.mvcframework.annotation; import java.lang.annotation.*; /** * @Author: denghy * @DateTime: 2021/3/24 10:16 * @Description: TODO * @package: com.test.edu.mvcframework.annotion * * */ @Documented //在自定义注解的时候可以使用@Documented来进行标注,如果使用@Documented标注了,在生成javadoc的时候就会把@Documented注解给显示出来。 /** 类, 接口 (包括注解类型), 或 枚举 声明 TYPE, 字段声明(包括枚举常量) FIELD, 方法声明(Method declaration) METHOD, 正式的参数声明 PARAMETER, 构造函数声明 CONSTRUCTOR, 局部变量声明 LOCAL_VARIABLE, 注解类型声明 ANNOTATION_TYPE, 包声明 PACKAGE, 类型参数声明 TYPE_PARAMETER, 类型的引用 TYPE_USE */ //ElementType.FIELD 标识在字段上, ElementType.TYPE 是标识在类上, /* @zdyAutowired private IDemoService demoService; @zdySerevice public class demoService{} */ @Target(ElementType.FIELD) /** * 注解只在源代码级别保留,编译时被忽略 SOURCE, * 注解将被编译器在类文件中记录 * 但在运行时不需要JVM保留。这是默认的 * 行为. CLASS, *注解将被编译器记录在类文件中 *在运行时保留JVM,因此可以反读。 RUNTIME */ @Retention(RetentionPolicy.RUNTIME)//RetentionPolicy这个枚举类型的常量描述保留注解的各种策略,它们与元注解(@Retention)一起指定注释要保留多长时间 public @interface zdyAutowired { String value() default ""; }
package com.test.edu.mvcframework.annotation; import java.lang.annotation.*; @Documented @Target({ElementType.TYPE,ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) public @interface zdyController { String value() default ""; } ================================================================ package com.test.edu.mvcframework.annotation; import java.lang.annotation.*; @Documented @Target({ElementType.TYPE,ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) public @interface zdyRequestMapping { String value() default ""; } ================================================================ package com.test.edu.mvcframework.annotation; import java.lang.annotation.*; @Documented @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) public @interface zdyService { String value() default ""; }
前端控制器zdyDispatcherServlet
package com.test.edu.mvcframework.servlet; import com.test.edu.mvcframework.annotation.*; import com.test.edu.mvcframework.pojo.Handler; import org.apache.commons.lang3.StringUtils; 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.reflect.Field; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.lang.reflect.Parameter; import java.util.*; import java.util.regex.Matcher; import java.util.regex.Pattern; /** * @Author: denghy * @DateTime: 2021/3/24 10:45 * @Description: TODO * @package: com.test.edu.mvcframework.servlet */ public class zdyDispatcherServlet extends HttpServlet { private Properties properties = new Properties(); private List<String> classNames = new ArrayList<>(); // 缓存扫描到的类的 全限定类名 // ioc容器 private Map<String,Object> ioc = new HashMap<String,Object>(); // handlerMapping //private Map<String,Method> handlerMapping = now HashMap<>(); // 存储 url和Method之间的映射关系 private List<Handler> handlerMapping = new ArrayList<>(); private Set<String> permissionSet = new HashSet<>(); @Override public void init(ServletConfig config) throws ServletException { // 1 加载配置⽂件 springmvc.properties String contextConfigLocation = config.getInitParameter("contextConfigLocation"); doLoadConfig(contextConfigLocation); // 2 扫描相关的类,扫描注解 doScan(properties.getProperty("scanPackage")); // 3 初始化bean对象(实现ioc容器,基于注解) doInstance(); // 4 实现依赖注⼊ doAutoWired(); // 5 构造⼀个HandlerMapping处理器映射器,将配置好的url和Method建⽴映射关系 initHandlerMapping(); System.out.println("lagou mvc 初始化完成...."); // 等待请求进⼊,处理请求 } private void initHandlerMapping() { if(ioc.isEmpty()) {return;} for(Map.Entry<String,Object> entry: ioc.entrySet()) { // 获取ioc中当前遍历的对象的class类型 Class<?> aClass = entry.getValue().getClass(); if(!aClass.isAnnotationPresent(zdyController.class)) {continue;} String baseUrl = ""; if(aClass.isAnnotationPresent(zdyRequestMapping.class)) { zdyRequestMapping annotation = aClass.getAnnotation(zdyRequestMapping.class); baseUrl = annotation.value(); // 等同于/demo } // 获取方法 Method[] methods = aClass.getMethods(); for (int i = 0; i < methods.length; i++) { Method method = methods[i]; // 方法没有标识LagouRequestMapping,就不处理 if(!method.isAnnotationPresent(zdyRequestMapping.class)) {continue;} // 如果标识,就处理 zdyRequestMapping annotation = method.getAnnotation(zdyRequestMapping.class); String methodUrl = annotation.value(); // /query String url = baseUrl + methodUrl; // 计算出来的url /demo/query // 把method所有信息及url封装为一个Handler Handler handler = new Handler(entry.getValue(),method, Pattern.compile(url)); // 计算方法的参数位置信息 // query(HttpServletRequest request, HttpServletResponse response,String name) Parameter[] parameters = method.getParameters(); for (int j = 0; j < parameters.length; j++) { Parameter parameter = parameters[j]; if(parameter.getType() == HttpServletRequest.class || parameter.getType() == HttpServletResponse.class) { // 如果是request和response对象,那么参数名称写HttpServletRequest和HttpServletResponse handler.getParamIndexMapping().put(parameter.getType().getSimpleName(),j); }else{ handler.getParamIndexMapping().put(parameter.getName(),j); // <name,2> } } // 建立url和method之间的映射关系(map缓存起来) handlerMapping.add(handler); /* ************************* zdySecurity ******************* */ // 方法没有标识zdySecurity,就不处理 if(!method.isAnnotationPresent(zdySecurity.class)) {continue;} // 如果标识,就处理 zdySecurity zdySecurityAnnotation = method.getAnnotation(zdySecurity.class); String[] secValue = zdySecurityAnnotation.value(); // /query // 应该到 redis 或数据库 中获取该用户的权限信息 并判断是否有权限 // 这里测试使用 直接add permissionSet.add("admin"); permissionSet.add("admin1"); StringBuffer sb = new StringBuffer(); for (String s : secValue) { for (String s1 : permissionSet) { if(s.equalsIgnoreCase(s1)){ sb.append(s1 + ","); } } } handler.setSecured(sb.substring(0,sb.length()-1)); /* ************************* zdySecurity ******************* */ } } } private void doAutoWired() { if(ioc.isEmpty()) {return;} // 有对象,再进行依赖注入处理 // 遍历ioc中所有对象,查看对象中的字段,是否有@zdyAutowired注解,如果有需要维护依赖注入关系 for(Map.Entry<String,Object> entry: ioc.entrySet()) { // 获取bean对象中的字段信息 Field[] declaredFields = entry.getValue().getClass().getDeclaredFields(); // 遍历判断处理 for (int i = 0; i < declaredFields.length; i++) { Field declaredField = declaredFields[i]; // @zdyAutowired private IDemoService demoService; if(!declaredField.isAnnotationPresent(zdyAutowired.class)) { continue; } // 有该注解 zdyAutowired annotation = declaredField.getAnnotation(zdyAutowired.class); String beanName = annotation.value(); // 需要注入的bean的id if("".equals(beanName.trim())) { // 没有配置具体的bean id,那就需要根据当前字段类型注入(接口注入) IDemoService beanName = declaredField.getType().getName(); } // 开启赋值 declaredField.setAccessible(true); try { declaredField.set(entry.getValue(),ioc.get(beanName)); } catch (IllegalAccessException e) { e.printStackTrace(); } } } } // ioc容器 // 基于classNames缓存的类的全限定类名,以及反射技术,完成对象创建和管理 private void doInstance() { if(classNames.size() == 0) return; try{ for (int i = 0; i < classNames.size(); i++) { String className = classNames.get(i); // com.test.demo.controller.DemoController // 反射 Class<?> aClass = Class.forName(className); // 区分controller,区分service' if(aClass.isAnnotationPresent(zdyController.class)) { // controller的id此处不做过多处理,不取value了,就拿类的首字母小写作为id,保存到ioc中 String simpleName = aClass.getSimpleName();// DemoController String lowerFirstSimpleName = lowerFirst(simpleName); // demoController Object o = aClass.newInstance(); ioc.put(lowerFirstSimpleName,o); }else if(aClass.isAnnotationPresent(zdyService.class)) { zdyService annotation = aClass.getAnnotation(zdyService.class); //获取注解value值 String beanName = annotation.value(); // 如果指定了id,就以指定的为准 if(!"".equals(beanName.trim())) { ioc.put(beanName,aClass.newInstance()); }else{ // 如果没有指定,就以类名首字母小写 beanName = lowerFirst(aClass.getSimpleName()); ioc.put(beanName,aClass.newInstance()); } // service层往往是有接口的,面向接口开发,此时再以接口名为id,放入一份对象到ioc中,便于后期根据接口类型注入 Class<?>[] interfaces = aClass.getInterfaces(); for (int j = 0; j < interfaces.length; j++) { Class<?> anInterface = interfaces[j]; // 以接口的全限定类名作为id放入 ioc.put(anInterface.getName(),aClass.newInstance()); } }else{ continue; } } }catch (Exception e) { e.printStackTrace(); } } //首字母小写 public String lowerFirst(String str){ char[] chars = str.toCharArray(); if(chars[0] >= 'A' && chars[0] <= 'Z' ){ chars[0]+=32; } return String.valueOf(chars); } // scanPackage: com.test.demo package----> 磁盘上的文件夹(File) com/test/demo private void doScan(String scanPackage) { //磁盘路径,将 . 替换成 / String scanPackagePath = Thread.currentThread().getContextClassLoader().getResource("").getPath() + scanPackage.replaceAll("\.", "/"); File pack = new File(scanPackagePath.replaceAll("%20"," ")); File[] files = pack.listFiles(); for (File file : files) { if(file.isDirectory()){ doScan(scanPackage + "." + file.getName()); // com.test.demo.controller }else if(file.getName().endsWith(".class")){ String className = scanPackage + '.' + file.getName().replaceAll(".class", ""); classNames.add(className); } } } private void doLoadConfig(String contextConfigLocation) { InputStream resourceAsStream = this.getClass().getClassLoader().getResourceAsStream(contextConfigLocation); try { properties.load(resourceAsStream); } catch (IOException e) { e.printStackTrace(); } } @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 { // 处理请求:根据url,找到对应的Method方法,进行调用 // 获取uri // String requestURI = req.getRequestURI(); // Method method = handlerMapping.get(requestURI);// 获取到一个反射的方法 // 反射调用,需要传入对象,需要传入参数,此处无法完成调用,没有把对象缓存起来,也没有参数!!!!改造initHandlerMapping(); // method.invoke() // // 根据uri获取到能够处理当前请求的hanlder(从handlermapping中(list)) Handler handler = getHandler(req); if(handler == null) { resp.getWriter().write("404 not found"); return; } // 参数绑定 // 获取所有参数类型数组,这个数组的长度就是我们最后要传入的args数组的长度 Class<?>[] parameterTypes = handler.getMethod().getParameterTypes(); // 根据上述数组长度创建一个新的数组(参数数组,是要传入反射调用的) Object[] paraValues = new Object[parameterTypes.length]; // 以下就是为了向参数数组中塞值,而且还得保证参数的顺序和方法中形参顺序一致 Map<String, String[]> parameterMap = req.getParameterMap(); // 遍历request中所有参数 (填充除了request,response之外的参数) for(Map.Entry<String,String[]> param: parameterMap.entrySet()) { // name=1&name=2 name [1,2] String value = StringUtils.join(param.getValue(), ","); // 如同 1,2 // 如果参数和方法中的参数匹配上了,填充数据 if(!handler.getParamIndexMapping().containsKey(param.getKey())) {continue;} // 方法形参确实有该参数,找到它的索引位置,对应的把参数值放入paraValues Integer index = handler.getParamIndexMapping().get(param.getKey());//name在第 2 个位置 paraValues[index] = value; // 把前台传递过来的参数值填充到对应的位置去 /* ************************* zdySecurity ******************* */ properties.setProperty("name", value); properties.setProperty("security",handler.getSecured()==null? "true" :String.valueOf(handler.getSecured().contains(value))); /* ************************* zdySecurity ******************* */ } int requestIndex = handler.getParamIndexMapping().get(HttpServletRequest.class.getSimpleName()); // 0 paraValues[requestIndex] = req; int responseIndex = handler.getParamIndexMapping().get(HttpServletResponse.class.getSimpleName()); // 1 paraValues[responseIndex] = resp; resp.setContentType("text/html;charset=UTF-8"); String names = "当前用户"+properties.getProperty("name"); // 最终调用handler的method属性 try { if(properties.getProperty("security").equals("true")){ resp.getWriter().write(names + ",有访问权限"); handler.getMethod().invoke(handler.getController(),paraValues); System.out.println("============>" + names +",有访问权限"); }else { resp.getWriter().write(names + ",无访问权限"); System.out.println("============>" + names +",无访问权限"); return; } } catch (IllegalAccessException e) { e.printStackTrace(); } catch (InvocationTargetException e) { e.printStackTrace(); } } private Handler getHandler(HttpServletRequest req) { if(handlerMapping.isEmpty()){return null;} String url = req.getRequestURI(); for(Handler handler: handlerMapping) { Matcher matcher = handler.getPattern().matcher(url); if(!matcher.matches()){continue;} return handler; } return null; } }
pojo 类Handler对象
public class Handler { private Object controller; // method.invoke(obj,) private Method method; private Pattern pattern; // spring中url是支持正则的 private String secured ; // spring中url是支持正则的 private Map<String,Integer> paramIndexMapping; // 参数顺序,是为了进行参数绑定,key是参数名,value代表是第几个参数 <name,2> }
web.xml配置
<!DOCTYPE web-app PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN" "http://java.sun.com/dtd/web-app_2_3.dtd" > <web-app> <display-name>Archetype Created Web Application</display-name> <servlet> <servlet-name>lgoumvc</servlet-name> <servletclass>com.lagou.edu.mvcframework.servlet.LgDispatcherServlet</servletclass> <init-param> <param-name>contextConfigLocation</param-name> <param-value>springmvc.properties</param-value> </init-param> </servlet> <servlet-mapping> <servlet-name>lgoumvc</servlet-name> <url-pattern>/*</url-pattern> </servlet-mapping> </web-app
调用框架测试代码
DemoController
package com.test.demo.controller; import com.test.demo.service.IDemoService; import com.test.edu.mvcframework.annotation.zdyAutowired; import com.test.edu.mvcframework.annotation.zdyController; import com.test.edu.mvcframework.annotation.zdyRequestMapping; import com.test.edu.mvcframework.annotation.zdySecurity; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; /** * @Author: denghy * @DateTime: 2021/3/24 11:23 * @Description: TODO * @package: com.test.demo.controller */ @zdyController @zdyRequestMapping("/demo") public class DemoController { @zdyAutowired private IDemoService demoService; /** * URL: /demo/query?name=lisi * @param request * @param response * @param name * @return */ @zdySecurity({"admin","admin1"}) @zdyRequestMapping("/query") public String query(HttpServletRequest request, HttpServletResponse response, String name) { return demoService.get(name); } @zdyRequestMapping("/query2") public String query2(HttpServletRequest request, HttpServletResponse response, String name) { return demoService.get(name); } }
IDemoService 和 DemoServiceImpl
public interface IDemoService { String get(String name); } ========================== @zdyService("demoService") public class DemoServiceImpl implements IDemoService { @Override public String get(String name) { System.out.println("service 实现类中的name参数:" + name) ; return name; } }
springmvc.properties
scanPackage=com.test.demo 指定扫描包
测试
以上,说明,我们自定义mvc完成,可以正常运行