【JavaWeb】封装一个MVC框架
框架参考自:
https://www.bilibili.com/video/BV1gV411r7ct
在老师的基础上添加了
1、POST参数处理
2、Tomcat8版本下中文乱码处理
3、可声明请求方式
框架需要的全部依赖:
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>cn.jack2048</groupId> <artifactId>MVC-Framework</artifactId> <version>1.0-SNAPSHOT</version> <properties> <maven.compiler.source>8</maven.compiler.source> <maven.compiler.target>8</maven.compiler.target> </properties> <packaging>jar</packaging> <dependencies> <!-- https://mvnrepository.com/artifact/commons-io/commons-io --> <dependency> <groupId>commons-io</groupId> <artifactId>commons-io</artifactId> <version>2.11.0</version> </dependency> <dependency> <groupId>commons-beanutils</groupId> <artifactId>commons-beanutils</artifactId> <version>1.7.0</version> </dependency> <dependency> <groupId>org.javassist</groupId> <artifactId>javassist</artifactId> <version>3.26.0-GA</version> <!-- <version>3.12.1-GA</version> --> </dependency> <dependency> <groupId>com.alibaba</groupId> <artifactId>fastjson</artifactId> <version>1.2.78</version> </dependency> <dependency> <groupId>javax.servlet</groupId> <artifactId>javax.servlet-api</artifactId> <version>4.0.1</version> <scope>provided</scope> </dependency> <dependency> <groupId>javax.servlet</groupId> <artifactId>jstl</artifactId> <version>1.2</version> </dependency> <dependency> <groupId>javax.servlet.jsp</groupId> <artifactId>javax.servlet.jsp-api</artifactId> <version>2.3.3</version> </dependency> <!-- https://mvnrepository.com/artifact/org.reflections/reflections --> <dependency> <groupId>org.reflections</groupId> <artifactId>reflections</artifactId> <version>0.9.11</version> <!-- <version>0.9.12</version> 此版本 Reflections已经不适用了字符串构造 --> </dependency> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.12</version> <scope>test</scope> </dependency> </dependencies> </project>
注解一栏:
@Controller仅用于标记注册类
package cn.jack2048.framework.annotation; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; /** * 只能注解在类上面 * 用于标识为Controller,提供给Servlet进行调用 * */ @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) public @interface Controller { }
@RequestMapping标记
package cn.jack2048.framework.annotation; import cn.jack2048.framework.constant.MethodType; 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 RequestMapping { String value() default ""; MethodType methodType() default MethodType.GET; }
请求方式枚举类:
package cn.jack2048.framework.constant; /** * 定义请求方式 * MethodType * */ public enum MethodType { GET("doGet"), POST("doPost"), DELETE("doDelete"), PUT("doPut") , HEAD("doHEAD") , TRACE("doTrace"); private final String type; MethodType(String type) { this.type = type; } public String getType() { return type; } }
中文乱码过滤器:
过滤器参考自乐字节
https://www.bilibili.com/video/BV1SP4y147wJ?p=69
对于Tomcat版本的确认进行了改进
package cn.jack2048.framework.filter; import cn.jack2048.framework.constant.ServletConstants; import cn.jack2048.framework.util.RequestWrapper; import cn.jack2048.framework.util.ServletUtil; import javax.servlet.*; import javax.servlet.http.HttpServletRequest; import java.io.IOException; /** * @WebFilter(value = {"/*", "/**"}) * 过滤器注解不能决定过滤链的顺序,不推荐使用注解过滤器,所以还是要手动配置 */ public class CharacterEncodingFilter implements Filter { @Override public void init(FilterConfig filterConfig) throws ServletException { } /** * 乱码处理 * * @param servletRequest * @param servletResponse * @param filterChain * @throws IOException * @throws ServletException */ @Override public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException { HttpServletRequest req = (HttpServletRequest) servletRequest; String method = req.getMethod(); String tomcatMajorVersion = ServletUtil.getTomcatMajorVersion(req); Integer tmv = Integer.valueOf(tomcatMajorVersion); // 如果Tomcat版本小于8 (8以下的版本) 且为 GET请求 if (8 > tmv && ServletConstants.REQUEST_METHOD_GET.equalsIgnoreCase(method)) { RequestWrapper requestWrapper = new RequestWrapper(req); filterChain.doFilter(requestWrapper, servletResponse); return; } /** * Tomcat所有版本都不会处理非GET请求的乱码,默认设置即可 * */ if (! ServletConstants.REQUEST_METHOD_GET.equals(method)) { req.setCharacterEncoding(ServletConstants.CHARSET_TYPE_UTF8); } filterChain.doFilter(req, servletResponse); } @Override public void destroy() { } }
主版本获取方法:
/** * 获取Tomcat主版本号 * @param request * @return */ public static String getTomcatMajorVersion(HttpServletRequest request) { ServletContext servletContext = request.getServletContext(); String serverInfo = servletContext.getServerInfo(); // Apache Tomcat/8.5.70 获取完整版本信息 int i = serverInfo.indexOf("/"); serverInfo = serverInfo.substring(i + 1); // 8.5.70 获取版本号 int i2 = serverInfo.indexOf("."); String majorVersion = serverInfo.substring(0, i2); // 8 获取主版本号 return majorVersion; }
Wrrapper包装类:
package cn.jack2048.framework.util; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequestWrapper; import java.nio.charset.StandardCharsets; public class RequestWrapper extends HttpServletRequestWrapper { private HttpServletRequest httpServletRequest; public RequestWrapper(HttpServletRequest request) { super(request); this.httpServletRequest = request; } /** * GET 请求重写Parameter方法处理 * @param name * @return */ @Override public String getParameter(String name) { String value = this.httpServletRequest.getParameter(name); if(null != value && !"".equals(value)) { try { value = new String(value.getBytes(StandardCharsets.ISO_8859_1), StandardCharsets.UTF_8); } catch (Exception exception) { exception.printStackTrace(); } } return value; } }
注解的作用在于被反射机制获取到被注解标注的类,以方便保存反射需要使用的信息
package cn.jack2048.framework.util; import cn.jack2048.framework.annotation.Controller; import cn.jack2048.framework.annotation.RequestMapping; import cn.jack2048.framework.constant.MethodType; import cn.jack2048.framework.model.Mapping; import javassist.ClassClassPath; import javassist.ClassPool; import javassist.CtMethod; import javassist.bytecode.AttributeInfo; import javassist.bytecode.CodeAttribute; import javassist.bytecode.LocalVariableAttribute; import javassist.bytecode.MethodInfo; import org.reflections.Reflections; import java.lang.reflect.Method; import java.lang.reflect.Modifier; import java.math.BigDecimal; import java.time.LocalDate; import java.time.LocalDateTime; import java.util.*; public class ClassUtil { public static Set<Class<?>> generalReferType; // 基本常用类型 public static Set<String> allBasicType; // 基本数据类型 public static Set<Class<?>> dateType; // 日期类型 private static Map<String, Mapping> allMapping; // 创建集中容器 static { allMapping = getAllRequestMapping("cn.jack2048.controller"); generalReferType = new HashSet<>(); generalReferType.add(String.class); generalReferType.add(Byte.class); generalReferType.add(Character.class); generalReferType.add(Short.class); generalReferType.add(Integer.class); generalReferType.add(Long.class); generalReferType.add(BigDecimal.class); generalReferType.add(Float.class); generalReferType.add(Double.class); generalReferType.add(Boolean.class); allBasicType = new HashSet<>(); allBasicType.add("boolean"); allBasicType.add("byte"); allBasicType.add("char"); allBasicType.add("short"); allBasicType.add("int"); allBasicType.add("long"); allBasicType.add("float"); allBasicType.add("double"); dateType = new HashSet<>(); dateType.add(Date.class); dateType.add(LocalDate.class); dateType.add(LocalDateTime.class); } public static Map<String, Mapping> getAllRequestMapping(String packagePath) { try { Reflections reflections = new Reflections(packagePath); Map<String, Mapping> mappingMap = new HashMap<>(); ClassPool classPool = ClassPool.getDefault(); // 获取指定包名下面所有的带有@Controller注解的类对象 Set<Class<?>> controllerClassSet = reflections.getTypesAnnotatedWith(Controller.class); // 对每一个@Controller注解的类处理 for (Class<?> controllerClass : controllerClassSet) { // System.out.println(controllerClass.getName()); Object newInstance = controllerClass.newInstance(); String baseUri = ""; // 检查这个类是否还有@RequestMapping注解 RequestMapping requestMappingForClass = controllerClass.getDeclaredAnnotation(RequestMapping.class); // 如果类中有此注解 if (null != requestMappingForClass) { baseUri = requestMappingForClass.value(); } // 获取所有私有方法 Method[] declaredMethods = controllerClass.getDeclaredMethods(); // 寻找带有@RequestMapping注解的方法 for (Method declaredMethod : declaredMethods) { RequestMapping requestMappingForMethod = declaredMethod.getAnnotation(RequestMapping.class); if (null == requestMappingForMethod) continue; // 该方法没有标注此注解,直接忽略 String methodUri = requestMappingForMethod.value(); // 有此注解,获取对应的uri MethodType methodType = requestMappingForMethod.methodType(); // 有此注解,获取对应的请求方法 final String FULL_URI = baseUri + methodUri; // 拼接完整uri // 除了方法之外,方法的参数信息也需要进行保存 classPool.insertClassPath(new ClassClassPath(controllerClass)); CtMethod ctMethod = classPool.getMethod(controllerClass.getName(), declaredMethod.getName()); MethodInfo methodInfo = ctMethod.getMethodInfo(); CodeAttribute codeAttribute = methodInfo.getCodeAttribute(); AttributeInfo attribute = codeAttribute.getAttribute(LocalVariableAttribute.tag); LocalVariableAttribute attribute2 = (LocalVariableAttribute) attribute; Map<String, Class<?>> methodParams = new LinkedHashMap<>(); // 需要保证顺序 Class<?>[] parameterTypes = declaredMethod.getParameterTypes(); if (null != attribute2) { int pos = Modifier.isStatic( ctMethod.getModifiers()) ? 0 : 1; // for (int i = 0; i < declaredMethod.getParameterCount(); i++) { for (int i = 0; i < declaredMethod.getParameterCount(); i++) { String paraName = attribute2.variableName(i + pos); methodParams.put(paraName, parameterTypes[i]); } } mappingMap.put(FULL_URI, new Mapping(newInstance, declaredMethod, methodType, methodParams)); } } return mappingMap; } catch (Exception exception) { exception.printStackTrace(); return null; } } public static Mapping getRequestMapping(String uri) { return allMapping.get(uri); } }
用于存储调用方法的实体类Bean:
package cn.jack2048.framework.model; import cn.jack2048.framework.constant.MethodType; import java.lang.reflect.Method; import java.util.Map; /** * 用于存储 url路径对应的类和方法? * */ public class Mapping { private Object Object; // 需要执行的Controller实例 private Method method; // 需要执行的Controller实例方法 private MethodType methodType; // 该方法声明的请求方式 // key代表方法名称 value表示数据类型 约定要求:不允许使用重载! private Map<String, Class<?>> methodParams; // 方法的参数列表信息 public Mapping(java.lang.Object object, Method method, MethodType methodType, Map<String, Class<?>> methodParams) { Object = object; this.method = method; this.methodType = methodType; this.methodParams = methodParams; } public java.lang.Object getObject() { return Object; } public Method getMethod() { return method; } public MethodType getMethodType() { return methodType; } public Map<String, Class<?>> getMethodParams() { return methodParams; } }
核心中枢:派发Servlet
这里处理的东西有点多
1、根据uri匹配寻找对应封装对象
2、执行前的校验,例如路径是否符合, 请求方式是否符合
3、封装请求参数(Post参数)
4、为了做参数可扩展,对参数注入写了一大段包装参数的逻辑在其中
5、调用封装对象携带的要执行方法(开发的业务逻辑)
6、执行完成之后对返回结果要处理的信息(字符串 走 重定向 和转发, 其他类型一律转JSON返回)
老师还是非常厉害的,最后还特意对数组类型做了处理,支持了逗号分割
我自己写为了精炼表达内容,和老师视频里面的写法就不一样了
package cn.jack2048.framework.servlet; import cn.jack2048.framework.model.Mapping; import cn.jack2048.framework.util.ClassUtil; import cn.jack2048.framework.util.DateConverter; import cn.jack2048.framework.util.DateUtil; import cn.jack2048.framework.util.ServletUtil; import com.alibaba.fastjson.JSON; import org.apache.commons.beanutils.BeanUtils; import org.apache.commons.beanutils.ConvertUtils; 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.IOException; import java.io.PrintWriter; import java.lang.reflect.Method; import java.math.BigDecimal; import java.text.ParseException; import java.text.SimpleDateFormat; import java.time.LocalDate; import java.time.LocalDateTime; import java.time.format.DateTimeFormatter; import java.util.*; import static cn.jack2048.framework.constant.ServletConstants.POST_PARAM_KEY; public class DispatchServlet extends HttpServlet { // private Class<?> thisClass = this.getClass(); private static Mapping currentMapping; private static Map<String, Mapping> allRequestMapping; public static final String REDIRECT = "redirect:"; public static String dispatchPath = "/WEB-INF/jsp/"; public static String fileSuffix = ".jsp"; @Override public void init(ServletConfig config) throws ServletException { super.init(config); // 注册转换器 ConvertUtils.register(DateConverter.dateConverter, Date.class); ConvertUtils.register(DateConverter.dateConverter, LocalDate.class); ConvertUtils.register(DateConverter.dateConverter, LocalDateTime.class); String cp = config.getInitParameter("controllerPackage"); String dp = config.getInitParameter("dispatchPath"); String fs = config.getInitParameter("fileSuffix"); allRequestMapping = ClassUtil.getAllRequestMapping(null == cp ? "cn.jack2048.controller" : cp); if (dp != null && !"".equals(dp)) dispatchPath = dp.trim(); if (fs != null && !"".equals(fs)) fileSuffix = fs.trim(); } private Object parseToCustomType(Class<?> parameterType, HttpServletRequest request) { Object o = null; try { o = parameterType.newInstance(); BeanUtils.populate(o, request.getParameterMap()); BeanUtils.populate(o, (Map) request.getAttribute(POST_PARAM_KEY)); } catch (Exception e) { e.printStackTrace(); } return o; } /** * 对于其他类型参数专门定义一个方法进行处理 * @param parameterType * @return */ private Object processingOtherType(Class<?> parameterType, String paramName, HttpServletRequest request) throws ParseException { // 请求对象解析请求参数名称,返回该参数值 String paramValue = request.getParameter(paramName); String typeName = parameterType.getTypeName(); Map<String, Object> postParam = (Map<String, Object>)request.getAttribute(POST_PARAM_KEY); Object paramValueInPost = postParam.get(paramName); Class<?> componentType = parameterType.getComponentType(); // 获取组合类型 String[] values = request.getParameterValues(paramName); // 请求存放的一组值 boolean isBasicType = ClassUtil.allBasicType.contains(typeName); boolean isGeneralReferType = ClassUtil.generalReferType.contains(parameterType); boolean isDateType = ClassUtil.dateType.contains(parameterType); boolean isComponentType = null != componentType; boolean isEmptyValue = (null == paramValue) || "".equals(paramValue); boolean isEmptyPostValue = (null == paramValueInPost) || "".equals(paramValueInPost); Object param = null; // 上述类型全不不匹配 且get参数和post参数也找不到此名称,则执行此自定义引用类型处理 if (isEmptyValue && isEmptyPostValue && !isBasicType && !isGeneralReferType && ! isDateType && !isComponentType) param = parseToCustomType(parameterType, request); if (isEmptyValue && !isEmptyPostValue) { // 如果get参数找不到,但是post参数有,也就是说get有,就不用post paramValue = paramValueInPost.toString(); } paramValue = paramName.trim(); if (isBasicType) { // 基本数据类型处理 param = "int".equals(typeName) ? Integer.valueOf(paramValue).intValue() : "long".equals(typeName) ? Long.valueOf(paramValue).longValue() : "short".equals(typeName) ? Short.valueOf(paramValue).shortValue() : "byte".equals(typeName) ? Byte.valueOf(paramValue).byteValue() : "boolean".equals(typeName) ? Boolean.valueOf(paramValue).booleanValue() : "float".equals(typeName) ? Float.valueOf(paramValue).floatValue() : "double".equals(typeName) ? Double.valueOf(paramValue).doubleValue() : "char".equals(typeName) ? Character.valueOf(paramValue.charAt(0)) : 0; } else if (isGeneralReferType) { // 一般对象类型处理 param = parameterType == String.class ? paramValue : parameterType == BigDecimal.class ? BigDecimal.valueOf(Long.valueOf(paramValue)) : parameterType == Long.class ? Long.valueOf(paramValue) : parameterType == Integer.class ? Integer.valueOf(paramValue) : parameterType == Double.class ? Double.valueOf(paramValue) : parameterType == Float.class ? Float.valueOf(paramValue) : parameterType == Short.class ? Short.valueOf(paramValue) : parameterType == Byte.class ? Byte.valueOf(paramValue) : parameterType == Boolean.class ? Boolean.valueOf(paramValue) : parameterType == Character.class ? paramValue.charAt(0) : null; } else if (isDateType) { // 日期格式判断 param = parameterType == java.util.Date.class && DateUtil.PATTEN_DATE.matcher(paramValue).matches() ? DateUtil.parseToDate(paramValue, DateUtil.FORMAT_DATE) : parameterType == java.util.Date.class && DateUtil.PATTEN_DATETIME.matcher(paramValue).matches() ? DateUtil.parseToDate(paramValue, DateUtil.FORMAT_DATETIME) : parameterType == java.time.LocalDate.class && DateUtil.PATTEN_DATE.matcher(paramValue).matches() ? DateUtil.parseToLocalDate(paramValue) : parameterType == java.time.LocalDateTime.class && DateUtil.PATTEN_DATETIME.matcher(paramValue).matches()? DateUtil.parseToLocalDateTime(paramValue) : null; } // 数组类型,条件 1反射得到的参数是组合类型, 2请求得到的参数组不能空 3参数组个数大于0 else if (isComponentType && null != values && values.length > 0) { int len = values.length; // if (! ClassUtil.generalReferType.contains(componentType)) return null; if (componentType == String.class) { List<String> stringList = new ArrayList<>(); for (int i = 0; i < len; i++) { String[] split = values[i].split(","); // 需要支持逗号分割处理 for (String s : split) stringList.add(s); } param = stringList.toArray(new String[stringList.size()]); } else if (componentType == Integer.class) { List<Integer> integerList = new ArrayList<>(); for (int i = 0; i < len; i++) { String[] split = values[i].split(","); // 需要支持逗号分割处理 for (String s : split) integerList.add(Integer.valueOf(s)); } param = integerList.toArray(new Integer[integerList.size()]); } else if (componentType == Long.class) { List<Long> longList = new ArrayList<>(); for (int i = 0; i < len; i++) { String[] split = values[i].split(","); // 需要支持逗号分割处理 for (String s : split) longList.add(Long.valueOf(s)); } param = longList.toArray(new Long[longList.size()]); } else if (componentType == Character.class) { List<Character> characterList = new ArrayList<>(); for (int i = 0; i < len; i++) { String[] split = values[i].split(","); // 需要支持逗号分割处理 for (String s : split) characterList.add(Character.valueOf(s.charAt(0))); } param = characterList.toArray(new Character[characterList.size()]); } else if (componentType == Double.class) { List<Double> doubleList = new ArrayList<>(); for (int i = 0; i < len; i++) { String[] split = values[i].split(","); // 需要支持逗号分割处理 for (String s : split) doubleList.add(Double.valueOf(s)); } param = doubleList.toArray(new Double[doubleList.size()]); } else if (componentType == Float.class) { List<Float> floatList = new ArrayList<>(); for (int i = 0; i < len; i++) { String[] split = values[i].split(","); // 需要支持逗号分割处理 for (String s : split) floatList.add(Float.valueOf(s)); } param = floatList.toArray(new Float[floatList.size()]); } else if (componentType == BigDecimal.class) { List<BigDecimal> bigDecimalList = new ArrayList<>(); for (int i = 0; i < len; i++) { String[] split = values[i].split(","); // 需要支持逗号分割处理 for (String s : split) bigDecimalList.add(BigDecimal.valueOf(Long.valueOf(s))); } param = bigDecimalList.toArray(new BigDecimal[bigDecimalList.size()]); } else if (componentType == Short.class) { List<Short> shortList = new ArrayList<>(); for (int i = 0; i < len; i++) { String[] split = values[i].split(","); // 需要支持逗号分割处理 for (String s : split) shortList.add(Short.valueOf(s)); } param = shortList.toArray(new Short[shortList.size()]); } else if (componentType == Byte.class) { List<Byte> byteList = new ArrayList<>(); for (int i = 0; i < len; i++) { String[] split = values[i].split(","); // 需要支持逗号分割处理 for (String s : split) byteList.add(Byte.valueOf(s)); } param = byteList.toArray(new Byte[byteList.size()]); } else if (componentType == Date.class) { SimpleDateFormat simpleDateFormat = new SimpleDateFormat(DateUtil.FORMAT_DATE); List<Date> dateList = new ArrayList<>(); for (int i = 0; i < len; i++) { String[] split = values[i].split(","); // 需要支持逗号分割处理 for (String s : split) dateList.add(simpleDateFormat.parse(s)); // 这里有异常,让方法抛出去了 } param = dateList.toArray(new Date[dateList.size()]); } else if (componentType == LocalDate.class) { List<LocalDate> localDateList = new ArrayList<>(); for (int i = 0; i < len; i++) { String[] split = values[i].split(","); // 需要支持逗号分割处理 for (String s : split) localDateList.add(LocalDate.parse(s, DateTimeFormatter.ofPattern(DateUtil.FORMAT_DATE))); } param = localDateList.toArray(new LocalDate[localDateList.size()]); } else if (componentType == LocalDateTime.class) { List<LocalDateTime> localDateTimeList = new ArrayList<>(); for (int i = 0; i < len; i++) { String[] split = values[i].split(","); // 需要支持逗号分割处理 for (String s : split) localDateTimeList.add(LocalDateTime.parse(s, DateTimeFormatter.ofPattern(DateUtil.FORMAT_DATETIME))); // 这里有异常,让方法抛出去了 } param = localDateTimeList.toArray(new LocalDateTime[localDateTimeList.size()]); } } return param; } @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) { try { System.out.println("doGet detected"); // 得到此映射的要执行的方法 Method currentMappingMethod = this.currentMapping.getMethod(); Map<String, Class<?>> methodParams = this.currentMapping.getMethodParams(); // 参数处理 Object result = null; int parameterCount = currentMappingMethod.getParameterCount(); if (0 != parameterCount) { // 如果存在参数 Object[] paramList = new Object[parameterCount]; // 预备入参容器 int idx = 0; for (String paramName : methodParams.keySet()) { Class<?> paramType = methodParams.get(paramName); paramList[idx ++] = // request对象和response对象方法要求了,就直接丢进去 paramType == HttpServletRequest.class ? req : paramType == HttpServletResponse.class ? resp // 其他要做特殊处理 : processingOtherType(paramType, paramName, req); } result = currentMappingMethod.invoke(currentMapping.getObject(), paramList); } else { // 无参直接获取 result = currentMappingMethod.invoke(currentMapping.getObject()); } // 不做参数处理, 约定只有Request & Response 直接调用 // Object invoke = currentMappingMethod.invoke(currentMapping.getObject(), req, resp); // 对结果的处理 if (null == result) return; else if (result instanceof String) { String string = (String) result; // 先判断是不是重定向 if (string.startsWith(REDIRECT)) { String path = string.substring(REDIRECT.length()); // 再判断是不是外部链接? if( path.startsWith("http")) { resp.sendRedirect(path); return; } resp.sendRedirect(req.getContextPath() + path); return; } // 默认是做跳转处理 跳转不支持对html静态文件处理乱码 // req.getRequestDispatcher(string).forward(req, resp); req.getRequestDispatcher(this.dispatchPath + string + this.fileSuffix).forward(req, resp); return; } else { // 剩余类型全部转换成JSON字符串 resp.setContentType("application/json; charset=utf-8;"); String jsonString = JSON.toJSONString(result); PrintWriter writer = resp.getWriter(); writer.write(jsonString); return; } } catch (Exception e) { e.printStackTrace(); } } @Override protected void doHead(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { System.out.println("doHead detected"); this.doGet(req, resp); } @Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { System.out.println("doPost detected"); this.doGet(req, resp); } @Override protected void doPut(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { System.out.println("doPut detected"); this.doGet(req, resp); } @Override protected void doDelete(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { System.out.println("doDelete detected"); this.doGet(req, resp); } @Override protected void doOptions(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { System.out.println("doOptions detected"); this.doGet(req, resp); } @Override protected void doTrace(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { System.out.println("doTrace detected"); this.doGet(req, resp); } @Override protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { try { // 获取请求的URI String requestURI = req.getRequestURI(); requestURI = requestURI.replace(req.getContextPath(), "");// 移除工程路径 // 根据此路径获取对应的映射模型 Mapping mapping = this.allRequestMapping.get(requestURI); resp.setHeader("Content-Type", "text/html; charset=utf-8;"); // 响应中文乱码处理, 请求乱码还没处理,放在过滤器中实现 // 用户不一定正确请求到Uri, 无法访问则需要走404 if (null == mapping) { // resp.setStatus(404); PrintWriter writer = resp.getWriter(); writer.write("请求的路径不存在:404" + requestURI); writer.close(); return; } this.currentMapping = mapping; // 获取来自请求的请求方法 String methodFromRequest = req.getMethod(); // 获取映射模型描述的请求方法 String methodFromMapping = mapping.getMethodType().toString(); // 获取对应要执行的方法名称 String methodName = mapping.getMethodType().getType(); // 如果并不匹配请求方法 if(!methodFromMapping.equals(methodFromRequest)) { Class<BackupServlet> backupServletClass = BackupServlet.class; Method declaredMethod = backupServletClass.getDeclaredMethod(methodName, HttpServletRequest.class, HttpServletResponse.class); declaredMethod.invoke(backupServletClass.newInstance(), req, resp); return; } // 通过@MethodType注解存储的名称 来调用具体某一个请求方式的方法 Method doMethod = DispatchServlet.class.getDeclaredMethod(methodName, HttpServletRequest.class, HttpServletResponse.class); // doMethod.invoke(this, req, resp); Map<String, Object> postParam = ServletUtil.getPostParam(req); req.setAttribute(POST_PARAM_KEY, postParam); // DispatchServlet instance = DispatchServlet.class.newInstance(); // instance.currentMapping = mapping; doMethod.invoke(this, req, resp); } catch (Exception e) { e.printStackTrace(); // 控制台输出异常信息 resp.setStatus(500); PrintWriter writer = resp.getWriter(); writer.println("服务器内部异常:500"); e.printStackTrace(writer); writer.close(); } } }
这里BackupServlet是因为 设置405不匹配请求方式的需要才设置的
因为反射机制不可以调用抽象类,所以写子类来套用
package cn.jack2048.framework.servlet; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; public class BackupServlet extends HttpServlet { @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { super.doGet(req, resp); } @Override protected void doHead(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { super.doHead(req, resp); } @Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { super.doPost(req, resp); } @Override protected void doPut(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { super.doPut(req, resp); } @Override protected void doDelete(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { super.doDelete(req, resp); } @Override protected void doOptions(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { super.doOptions(req, resp); } @Override protected void doTrace(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { super.doTrace(req, resp); } }
关于自定义类型处理的补充
自定义类型在老师的视频中使用了BeanUtil自动装填参数
但是日期类型是不支持的,这里是我对照的一点写法
package cn.jack2048.framework.util; import org.apache.commons.beanutils.Converter; import java.text.SimpleDateFormat; import java.time.LocalDate; import java.time.LocalDateTime; import java.time.format.DateTimeFormatter; import java.util.Date; public class DateConverter implements Converter { @Override public Object convert(Class aClass, Object o) { try { if (null == o || "".equals(o)) return null; if (o instanceof String) { String s = o.toString().trim(); if (aClass.equals(Date.class)) return new SimpleDateFormat(DateUtil.FORMAT_DATE).parse(s); else if (aClass.equals(LocalDate.class)) return LocalDate.parse(s, DateTimeFormatter.ofPattern(DateUtil.FORMAT_DATE)); else if (aClass.equals(LocalDateTime.class)) return LocalDateTime.parse(s, DateTimeFormatter.ofPattern(DateUtil.FORMAT_DATETIME)); } return o; } catch (Exception e) { e.printStackTrace(); return null; } } public static DateConverter dateConverter = new DateConverter(); }
日期工具类:
package cn.jack2048.framework.util; import java.text.ParseException; import java.text.SimpleDateFormat; import java.time.LocalDate; import java.time.LocalDateTime; import java.time.format.DateTimeFormatter; import java.util.Date; import java.util.regex.Matcher; import java.util.regex.Pattern; public class DateUtil { // 格式1 yyyy-MM-dd public static final String FORMAT_DATE = "yyyy-MM-dd"; public static final String FORMAT_REGEXP_DATE = "\\d{4}-\\d{2}-\\d{2}"; public static final Pattern PATTEN_DATE = Pattern.compile(FORMAT_REGEXP_DATE); // 格式2 yyyy-MM-dd HH:mm:ss public static final String FORMAT_DATETIME = "yyyy-MM-dd HH:mm:ss"; public static final String FORMAT_REGEXP_DATETIME = "\\d{4}-\\d{2}-\\d{2}\\s{1}\\d{2}:\\d{2}:\\d{2}"; public static final Pattern PATTEN_DATETIME = Pattern.compile(FORMAT_REGEXP_DATETIME); public static Date parseToDate(String timeString, String dateFormat) { SimpleDateFormat simpleDateFormat = new SimpleDateFormat(dateFormat); try { return simpleDateFormat.parse(timeString); } catch (ParseException e) { e.printStackTrace(); return null; } } public static LocalDate parseToLocalDate(String timeString) { return LocalDate.parse(timeString, DateTimeFormatter.ofPattern(FORMAT_DATE)); } public static LocalDateTime parseToLocalDateTime(String timeString) { return LocalDateTime.parse(timeString, DateTimeFormatter.ofPattern(FORMAT_DATETIME)); } public static boolean matchDateFormat(String val) { // 编译正则表达式 Pattern pattern1 = Pattern.compile(FORMAT_REGEXP_DATE); Pattern pattern2 = Pattern.compile(FORMAT_REGEXP_DATETIME); // 忽略大小写的写法 // Pattern pat = Pattern.compile(regEx, Pattern.CASE_INSENSITIVE); Matcher matcher1 = PATTEN_DATE.matcher(val); Matcher matcher2 = PATTEN_DATETIME.matcher(val); return matcher1.matches() || matcher2.matches(); } }
封装Post参数摘要自某个博客,我也忘记了。。。
/** * 从POST请求中获取参数 * @param request * @return * @throws Exception */ public static Map<String, Object> getPostParam(HttpServletRequest request) throws Exception { // 返回参数 Map<String, Object> params = new HashMap<>(); // 获取内容格式 String contentType = request.getContentType(); if (null == contentType || "".equals(contentType)) throw new ServletException("没有设置请求头项Content-Type!!!"); else contentType = contentType.split(";")[0]; // form表单格式 表单形式可以从 ParameterMap中获取 if (ServletConstants.CONTENT_TYPE_VALUE_URL_ENCODED2.equalsIgnoreCase(contentType)) { // 获取参数 Map<String, String[]> parameterMap = request.getParameterMap(); if (parameterMap != null) { for (Map.Entry<String, String[]> entry : parameterMap.entrySet()) { params.put(entry.getKey(), entry.getValue()[0]); } } } // json格式 json格式需要从request的输入流中解析获取 if (ServletConstants.CONTENT_TYPE_VALUE_JSON2.equalsIgnoreCase(contentType)) { // 使用 commons-io中 IOUtils 类快速获取输入流内容 String paramJson = IOUtils.toString(request.getInputStream(), StandardCharsets.UTF_8); Map parseObject = JSON.parseObject(paramJson, Map.class); params.putAll(parseObject); } return params ; }
一些写了一点点的常量:
package cn.jack2048.framework.constant; public interface ServletConstants { int HTTP_STATUS_REDIRECT = 302; String CONTENT_TYPE_KEY = "Content-Type"; String CONTENT_TYPE_VALUE_JSON = "application/json;charset=utf-8;"; String CONTENT_TYPE_VALUE_JSON2 = "application/json"; String CONTENT_TYPE_VALUE_URL_ENCODED = "application/x-www-form-urlencoded;charset=utf-8;"; String CONTENT_TYPE_VALUE_URL_ENCODED2 = "application/x-www-form-urlencoded"; String CONTENT_TYPE_VALUE_FORM_DATA = "multipart/form-data;charset=UTF-8;"; String REQUEST_METHOD_GET = "GET"; String REQUEST_METHOD_POST = "POST"; String REQUEST_METHOD_DELETE = "DELETE"; String REQUEST_METHOD_PUT = "PUT"; String POST_PARAM_KEY = "postParam"; String CHARSET_TYPE_UTF8 = "UTF-8"; String STATIC_RESOURCE = "svg,png,jpg,jpeg,gif,bmp,css,js"; }
测试的Controller
package cn.jack2048.controller; import cn.jack2048.framework.annotation.Controller; import cn.jack2048.framework.annotation.RequestMapping; import cn.jack2048.framework.constant.MethodType; import cn.jack2048.framework.constant.ServletConstants; import cn.jack2048.framework.servlet.DispatchServlet; import cn.jack2048.model.Account; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.util.Arrays; import java.util.Date; import java.util.HashMap; import java.util.Map; @Controller @RequestMapping("/test") public class TestController { /** * 成功跳转测试 * @param request * @param response */ // @RequestMethod(MethodType.GET) @RequestMapping("/testMethod") public void testMethod(HttpServletRequest request, HttpServletResponse response) { System.out.println("testMethod"); } /** * * POST请求测试 * @param request * @param response */ // @RequestMethod(MethodType.POST) @RequestMapping(value = "/testMethod2", methodType = MethodType.POST) public void testMethod2(HttpServletRequest request, HttpServletResponse response) { System.out.println("testMethod2"); } /** * 内部重定向测试 * http://localhost:8080/mvc-framework/test/innerRedirect * @return */ @RequestMapping("/innerRedirect") public String innerRedirect() { return DispatchServlet.REDIRECT + "/html/index.html"; } /** * 外部重定向测试 * http://localhost:8080/mvc-framework/test/outerRedirect * @return */ @RequestMapping("/outerRedirect") public String outerRedirect() { return DispatchServlet.REDIRECT + "https://www.baidu.com/"; } /** * jsp跳转测试 * http://localhost:8080/mvc-framework/test/dispatchTest * @return */ @RequestMapping("/dispatchTest") public String dispatchTest() { return "index"; } /** * 响应JSON数据测试 * http://localhost:8080/mvc-framework/test/jsonTest * @return */ @RequestMapping("/jsonTest") public Map<String, Object> jsonTest() { Map<String, Object> map = new HashMap<>(); map.put("status", 100); map.put("msg", "ok"); map.put("data", "json数据响应成功!"); return map; } /** * 获取参数测试 * http://localhost:8080/mvc-framework/test/getParamTest * @return */ @RequestMapping("/getParamTest") public void getParamTest(String a, int b, boolean c) { System.out.println(a + " " + b + " " + c); } /** * 日期数据测试 * http://localhost:8080/mvc-framework/test/getDateTest * @return */ @RequestMapping("/getDateTest") public void getDateTest(Date date) { System.out.println(date); } /** * 自定义对象测试 * http://localhost:8080/mvc-framework/test/getAccount * @return */ @RequestMapping("/getAccount") public void getAccount(Account account) { System.out.println(account); } /** * 数组类型 * http://localhost:8080/mvc-framework/test/getArray * @return */ @RequestMapping("/getArray") public void getArray(Integer[] ints) { System.out.println(Arrays.toString(ints)); } /** * POST数据处理 * http://localhost:8080/mvc-framework/test/postParamTest * @return */ @RequestMapping("/postParamTest") public void postParamTest(HttpServletRequest request) { Map<String, Object> params = (Map<String, Object>)request.getAttribute(ServletConstants.POST_PARAM_KEY); for (String s : params.keySet()) { System.out.println(new StringBuffer(s).append(" -> ").append( params.get(s))); } } }
测试的自定义类型:
package cn.jack2048.model; import java.time.LocalDate; import java.util.Date; public class Account { private String username; private String password; private Integer id; public Date getBirth() { return birth; } public void setBirth(Date birth) { this.birth = birth; } public Account(String username, String password, Integer id, Date birth) { this.username = username; this.password = password; this.id = id; this.birth = birth; } private Date birth; public Account() { } public Account add(String username, String password, Integer id) { return new Account(username, password, id); } public String getUsername() { return username; } public void setUsername(String username) { this.username = username; } public String getPassword() { return password; } public void setPassword(String password) { this.password = password; } public Account(String username, String password, Integer id) { this.username = username; this.password = password; this.id = id; } public Integer getId() { return id; } public void setId(Integer id) { this.id = id; } @Override public String toString() { return "Account{" + "username='" + username + '\'' + ", password='" + password + '\'' + ", id=" + id + ", birth=" + birth + '}'; } }
Web.xml配置的信息:
<?xml version="1.0" encoding="UTF-8"?> <web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd" version="4.0"> <!-- 阻止静态资源被DispatchServlet访问到 提前放行,注意,不要改变这个排放顺序 --> <servlet-mapping> <servlet-name>default</servlet-name> <url-pattern>*.html</url-pattern> <url-pattern>*.js</url-pattern> <url-pattern>*.css</url-pattern> <url-pattern>*.png</url-pattern> <url-pattern>*.jpg</url-pattern> <url-pattern>*.jpeg</url-pattern> <url-pattern>*.gif</url-pattern> </servlet-mapping> <servlet> <servlet-name>DispatchServlet</servlet-name> <servlet-class>cn.jack2048.framework.servlet.DispatchServlet</servlet-class> <load-on-startup>1</load-on-startup> </servlet> <servlet-mapping> <servlet-name>DispatchServlet</servlet-name> <url-pattern>/</url-pattern> </servlet-mapping> <!-- 请求乱码过滤 --> <filter> <filter-name>CharacterEncodingFilter</filter-name> <filter-class>cn.jack2048.framework.filter.CharacterEncodingFilter</filter-class> </filter> <filter-mapping> <filter-name>CharacterEncodingFilter</filter-name> <url-pattern>/**</url-pattern> </filter-mapping> </web-app>
工具类的测试:
import cn.jack2048.framework.model.Mapping; import cn.jack2048.framework.util.ClassUtil; import cn.jack2048.framework.util.DateUtil; import cn.jack2048.model.Account; import javassist.*; import javassist.bytecode.AttributeInfo; import javassist.bytecode.CodeAttribute; import javassist.bytecode.LocalVariableAttribute; import javassist.bytecode.MethodInfo; import org.junit.Test; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.lang.reflect.Modifier; import java.lang.reflect.Parameter; import java.util.Map; public class ClassUtilTest { /** * 根据包名获取指定路径下所有带某一注解的类 * */ @Test public void getAllClassUnderPackagePath() throws InvocationTargetException, IllegalAccessException { Map<String, Mapping> allRequestMapping = ClassUtil.getAllRequestMapping("cn.jack2048.controller"); Mapping mapping = allRequestMapping.get("/test/testMethod2"); Object object = mapping.getObject(); Method method = mapping.getMethod(); // Object invoke = method.invoke(object); // System.out.println(mapping.getMethodType()); } @Test public void substringTest() { final String s = "redirect:"; String a = "redirect:asd1ewd2ee3"; System.out.println(a.substring(s.length())); } @Test public void paramNameTest() { Class<Account> accountClass = Account.class; Method[] declaredMethods = accountClass.getDeclaredMethods(); for (Method declaredMethod : declaredMethods) { Parameter[] parameters = declaredMethod.getParameters(); System.out.print(declaredMethod.getName() + " : "); for (Parameter parameter : parameters) { String name = parameter.getName(); // JDK编译会缩减标识符号名称 1编译配置不简化标识符, 2使用第三方组件实现 Class<?> type = parameter.getType(); System.out.print(type.getName() + " : " + name + ", "); } System.out.println(" \n"); } } @Test public void paramNameTest2() throws Exception { Class<Account> accountClass = Account.class; Method addMethod = accountClass.getDeclaredMethod("add", String.class, String.class, Integer.class); ClassPool classPool = ClassPool.getDefault(); ClassPath classPath = classPool.insertClassPath(new ClassClassPath(accountClass)); CtMethod ctMethod = classPool.getMethod(accountClass.getName(), addMethod.getName()); MethodInfo methodInfo = ctMethod.getMethodInfo(); CodeAttribute codeAttribute = methodInfo.getCodeAttribute(); AttributeInfo attribute = codeAttribute.getAttribute(LocalVariableAttribute.tag); LocalVariableAttribute attribute2 = (LocalVariableAttribute) attribute; if (null != attribute2) { int pos = Modifier.isStatic( ctMethod.getModifiers()) ? 0 : 1; for (int i = 0; i < addMethod.getParameterCount(); i++) { String paraName = attribute2.variableName(i + pos); System.out.println(paraName); } } } @Test public void dateTest() { String time = "2021-09-31 16:24:33"; String time2 = "2021-09-31"; System.out.println( DateUtil.matchDateFormat(time)); System.out.println( DateUtil.matchDateFormat(time2)); } }
写到这里发现还有一些问题:
1、老师视频里面是没有处理html跳转的,结果发现html跳转是乱码的
原因是因为静态资源已经交给defaultServlet来处理放行的,也就是Tomcat来处理
解决办法是给defaultServlet再配置一个字符编码参数
2、写的是一个单体服务的架构,所以,不存在跨域访问的情况,但是如果要跨域肯定是需要设置的
使用过滤器再实现跨域访问的放行
package cn.dzz.framework.web.filter; import javax.servlet.*; import javax.servlet.http.HttpServletResponse; import java.io.IOException; public class CorsFilter implements Filter { @Override public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException { // 强转HttpServletResponse对象使用 HttpServletResponse resp = (HttpServletResponse) servletResponse; // resp.setContentType("text/html;charset=UTF-8"); // 一些请求是响应数据做接口调用,不应该写死 //禁用缓存,确保网页信息是最新数据 resp.setHeader("Pragma","No-cache"); resp.setHeader("Cache-Control","no-cache"); // 添加参数,允许任意domain访问 resp.setHeader("Access-Control-Allow-Origin", "*"); resp.setHeader("Access-Control-Allow-Methods", "POST, GET, OPTIONS, HEAD, DELETE, PUT"); resp.setHeader("Access-Control-Max-Age", "3600"); resp.setHeader("Access-Control-Allow-Headers", "X-Requested-With, Content-Type, Authorization, Accept, Origin, User-Agent, Content-Range, Content-Disposition, Content-Description"); resp.setDateHeader("Expires", -10); filterChain.doFilter(servletRequest, resp); } }
对web.xml的配置
<?xml version="1.0" encoding="UTF-8"?> <web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd" version="4.0"> <!-- 阻止静态资源被DispatchServlet访问到 提前放行,注意,不要改变这个排放顺序 --> <servlet> <servlet-name>default</servlet-name> <!-- 防止静态资源乱码配置 --> <init-param> <param-name>fileEncoding</param-name> <param-value>UTF-8</param-value> </init-param> </servlet> <servlet-mapping> <servlet-name>default</servlet-name> <url-pattern>*.html</url-pattern> <url-pattern>*.js</url-pattern> <url-pattern>*.css</url-pattern> <url-pattern>*.png</url-pattern> <url-pattern>*.jpg</url-pattern> <url-pattern>*.jpeg</url-pattern> <url-pattern>*.gif</url-pattern> </servlet-mapping> <welcome-file-list> <welcome-file>index.jsp</welcome-file> </welcome-file-list> <servlet> <servlet-name>DispatchServlet</servlet-name> <servlet-class>cn.dzz.framework.web.servlet.DispatchServlet</servlet-class> <init-param> <param-name>controllerPackage</param-name> <param-value>cn.dzz.controller</param-value> </init-param> <load-on-startup>1</load-on-startup> </servlet> <servlet-mapping> <servlet-name>DispatchServlet</servlet-name> <url-pattern>/</url-pattern> </servlet-mapping> <filter> <filter-name>CorsFilter</filter-name> <filter-class>cn.dzz.framework.web.filter.CorsFilter</filter-class> </filter> <filter-mapping> <filter-name>CorsFilter</filter-name> <url-pattern>/**</url-pattern> <url-pattern>/*</url-pattern> <url-pattern>/</url-pattern> </filter-mapping> <!-- 请求乱码过滤 --> <filter> <filter-name>CharacterEncodingFilter</filter-name> <filter-class>cn.dzz.framework.web.filter.CharacterEncodingFilter</filter-class> </filter> <filter-mapping> <filter-name>CharacterEncodingFilter</filter-name> <url-pattern>/**</url-pattern> <url-pattern>/*</url-pattern> <url-pattern>/</url-pattern> </filter-mapping> </web-app>