手写MVC框架(二)-代码实现和使用示例
--------上一篇:手写MVC框架(一)-再出发-----
背景
书接上文,之前整理了实现MVC框架需要写哪些东西。这周粗看了一下,感觉也没多少工作量,所以就计划一天时间来完成。周末的时间,哪会那么老实的坐在电脑前写代码呢?看电影的时候应该是老实的。为了不给自己留遗憾,所以今天就接着写了,然后就写完了。
一、主要代码结构
. ├── annotation │ ├── XAutowired.java //用于依赖注入 │ ├── XComponent.java //资源管理 │ ├── XController.java //资源管理-controller │ ├── XRepository.java //资源管理-资源层 │ ├── XRequestMapping.java //资源uri │ └── XService.java //资源管理-service ├── bean │ ├── EntityBean.java //存储实例化的资源 │ └── SystemConst.java ├── handler │ └── XDispatcherServlet.java //核心调度类 ├── mapper │ ├── InstanceManager.java //资源实例管理 │ └── ServletMapper.java //请求路径-资源映射 └── util ├── ClazzUtil.java ├── CommonUtil.java └── RequestResolveUtil.java
二、主要流程
1、服务启动,加载XDispatcherServlet
2、XDispatcherServlet初始化,调用InstanceManager进行对象初始化、依赖注入
3、调用ServletMapper扫描编写的各个URI
4、请求到达XDispatcherServlet时,通过ServletMapper匹配到对应的方法
5、执行匹配到的方法
三、InstanceManager实现
package com.shuimutong.gmvc.mapper; import java.lang.annotation.Annotation; import java.lang.reflect.Field; import java.util.Collection; import java.util.HashMap; import java.util.HashSet; import java.util.Map; import java.util.Set; import org.apache.commons.lang3.StringUtils; import org.reflections.Reflections; import com.shuimutong.gmvc.annotation.XAutowired; import com.shuimutong.gmvc.annotation.XComponent; import com.shuimutong.gmvc.annotation.XController; import com.shuimutong.gmvc.annotation.XRepository; import com.shuimutong.gmvc.annotation.XService; import com.shuimutong.gmvc.bean.EntityBean; import com.shuimutong.gmvc.bean.SystemConst; import com.shuimutong.gmvc.util.ClazzUtil; /** * 实例管理 * @ClassName: InstanceManager * @Description:(这里用一句话描述这个类的作用) * @author: 水木桶 * @date: 2019年9月7日 下午10:08:27 * @Copyright: 2019 [水木桶] All rights reserved. */ public class InstanceManager { /**被注解的类**/ private static Map<String, EntityBean> CLASS_ENTITY_MAP = new HashMap(); /**被XController注解的类**/ private static Set<EntityBean> CONTROLLER_CLASS_ENTITY_MAP = new HashSet(); /** * 初始化 * @param conf * @throws InstantiationException * @throws IllegalAccessException */ public static void init(Map<String, String> conf) throws InstantiationException, IllegalAccessException { String basePackageStr = conf.get(SystemConst.BASE_PACKAGE); //扫描通过框架管理的资源
scanAnnotationedResources(basePackageStr);
//实例化通过框架管理的资源 generateAnnotationedEntity(); } /** * 获取controller类 * @return */ public static Set<EntityBean> getControllerClazzes() { return CONTROLLER_CLASS_ENTITY_MAP; } /** * 根据类(被框架管理的类)获取对应的实例对象 * @param clazz * @return */ public static EntityBean getEntityByClazz(Class clazz) { String className = ClazzUtil.getClazzName(clazz); return CLASS_ENTITY_MAP.get(className); } /** * 扫描需要框架管理的类 * @param basePackageStr */ private static void scanAnnotationedResources(String basePackageStr) { if(StringUtils.isBlank(basePackageStr)) { return; } String[] basePackages = basePackageStr.split(","); Reflections reflections = new Reflections(basePackages); Class<?>[] annotations = {XController.class, XService.class, XRepository.class, XComponent.class}; for(Class<?> annotation : annotations) { Set<Class<?>> resourceClazzes = reflections .getTypesAnnotatedWith((Class<? extends Annotation>) annotation); for(Class<?> resourceClazz : resourceClazzes) { String className = ClazzUtil.getClazzName(resourceClazz); CLASS_ENTITY_MAP.put(className, new EntityBean(className, resourceClazz)); if(resourceClazz.isAnnotationPresent(XController.class)) { CONTROLLER_CLASS_ENTITY_MAP.add(new EntityBean(className, resourceClazz)); } } } } /** * 对通过框架管理的类进行实例化 * @throws IllegalAccessException * @throws InstantiationException */ private static void generateAnnotationedEntity() throws InstantiationException, IllegalAccessException { //先根据构造方法初始化bean initBeanInstance(CLASS_ENTITY_MAP.values()); Set<String> clazzNames = CLASS_ENTITY_MAP.keySet(); for(String clazzName : clazzNames) { EntityBean entityBean = CLASS_ENTITY_MAP.get(clazzName); initBeanAutowired(entityBean); } } /** * 初始化实例对象 * @param classEntityMap * @throws IllegalAccessException * @throws InstantiationException */ private static void initBeanInstance(Collection<EntityBean> entityBeans) throws InstantiationException, IllegalAccessException { for(EntityBean entityBean : entityBeans) { if(entityBean.getO() == null) { Class<?> destClazz = entityBean.getClazz(); entityBean.setO(destClazz.newInstance()); } } } /** * 初始化bean中注入的类 * @param entityBean * @throws IllegalArgumentException * @throws IllegalAccessException * @throws InstantiationException */ private static void initBeanAutowired(EntityBean entityBean) throws IllegalArgumentException, IllegalAccessException, InstantiationException { if(entityBean.isFullAutowired()) { return; } Class<?> destClazz = entityBean.getClazz(); Field[] fields = destClazz.getDeclaredFields(); Object entityInstance = entityBean.getO(); Collection<EntityBean> entityBeans = CLASS_ENTITY_MAP.values(); for(Field field : fields) { if(!field.isAnnotationPresent(XAutowired.class)) { continue; } field.setAccessible(true); Object fieldVal = field.get(entityInstance); if(fieldVal != null) { continue; } Class<?> fieldClazz = field.getType(); EntityBean relayEntity = getEntityByClazz(fieldClazz); //依赖的对象能够直接查到 if(relayEntity != null) { field.set(entityInstance, relayEntity.getO()); } else { boolean find = false; for(EntityBean otherEntityBean : entityBeans) { //判断子类 if(fieldClazz.isAssignableFrom(otherEntityBean.getClazz())) { field.set(entityInstance, otherEntityBean.getO()); find = true; break; } } if(!find) { throw new IllegalArgumentException("autowiredEntityNotFoundException"); } } } entityBean.setFullAutowired(true); } }
四、ServletMapper实现
package com.shuimutong.gmvc.mapper; import java.lang.annotation.Annotation; import java.lang.reflect.Method; import java.util.HashMap; import java.util.Map; import java.util.Set; import com.shuimutong.gmvc.annotation.XRequestMapping; import com.shuimutong.gmvc.bean.EntityBean; import com.shuimutong.gmvc.util.CommonUtil; import com.shuimutong.gutil.common.GUtilCommonUtil; /** * servlet映射 * @ClassName: ServletMapper * @Description:(这里用一句话描述这个类的作用) * @author: 水木桶 * @date: 2019年9月7日 下午6:22:19 * @Copyright: 2019 [水木桶] All rights reserved. */ public class ServletMapper { /**uri-method映射**/ private static Map<String, Method> URI_MAP = new HashMap(); public static void init() { generateUriMap(InstanceManager.getControllerClazzes()); StringBuilder logSb = new StringBuilder("ServletMapper,scanUriPath:\n"); for(String uri : URI_MAP.keySet()) { logSb.append(uri).append("\n"); } logSb.append("\n").append("---scanUriPath-----end----"); System.out.println(logSb.toString()); } /** * 生成uri-方法映射 * @param controllerClazz */ private static void generateUriMap(Set<EntityBean> controllerClazzBeans) { if(GUtilCommonUtil.checkListEmpty(controllerClazzBeans)) { return; } Class<? extends Annotation> requestMappingClazz = XRequestMapping.class; for(EntityBean eb : controllerClazzBeans) { Class<?> controllerClazz = eb.getClazz(); String rootUri = ""; if(controllerClazz.isAnnotationPresent(requestMappingClazz)) { XRequestMapping xrm = (XRequestMapping) controllerClazz.getAnnotation(XRequestMapping.class); rootUri = xrm.value(); } Method[] methods = controllerClazz.getDeclaredMethods(); for(Method method : methods) { if(method.isAnnotationPresent(requestMappingClazz)) { XRequestMapping xrm = (XRequestMapping) method.getAnnotation(XRequestMapping.class); String methodUri = xrm.value(); String fullUri = rootUri + "/" + methodUri; URI_MAP.put(CommonUtil.formatUri(fullUri), method); } } } } /** * 获取uri对应的方法 * @param uri * @return */ public static Method getMethodByUri(String uri) { return URI_MAP.get(uri); } }
五、XDispatcherServlet-核心调度实现
package com.shuimutong.gmvc.handler; import java.io.IOException; import java.lang.reflect.Method; import java.util.HashMap; import java.util.Map; import javax.servlet.ServletConfig; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.shuimutong.gmvc.bean.EntityBean; import com.shuimutong.gmvc.bean.SystemConst; import com.shuimutong.gmvc.mapper.InstanceManager; import com.shuimutong.gmvc.mapper.ServletMapper; /** * 调度servlet * @ClassName: XDispatcherServlet * @Description:(这里用一句话描述这个类的作用) * @author: 水木桶 * @date: 2019年9月8日 上午11:58:37 * @Copyright: 2019 [水木桶] All rights reserved. */ public class XDispatcherServlet extends HttpServlet { private static final Logger log = LoggerFactory.getLogger(XDispatcherServlet.class); @Override public void init() throws ServletException { super.init(); //获取ServletConfig对象 ServletConfig config = this.getServletConfig(); //根据参数名获取参数值 // String basePackage = config.getInitParameter(SystemConst.BASE_PACKAGE); Map<String, String> confMap = new HashMap(); confMap.put(SystemConst.BASE_PACKAGE, config.getInitParameter(SystemConst.BASE_PACKAGE)); try { InstanceManager.init(confMap); } catch (Exception e) { throw new ServletException(e); } ServletMapper.init(); } /** * @see HttpServlet#doGet(HttpServletRequest request, HttpServletResponse response) */ protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { doPost(request, response); } /** * @see HttpServlet#doPost(HttpServletRequest request, HttpServletResponse response) */ protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { // System.out.println("Hello"); String requestUri = request.getRequestURI().replace(request.getContextPath(), ""); // System.out.println("requestUri:" + requestUri); Method resolveMethod = ServletMapper.getMethodByUri(requestUri); EntityBean entityBean = InstanceManager.getEntityByClazz(resolveMethod.getDeclaringClass()); if(entityBean == null) { throw new ServletException("uriNotFoundException"); } try { resolveMethod.invoke(entityBean.getO(), request, response); } catch (Exception e) { log.error("execute" + resolveMethod.getName() + "Exception", e); throw new ServletException(e); } } }
六、源码分享
gmvc:https://gitee.com/simpleha/gmvc.git
依赖:https://gitee.com/simpleha/gutil.git
七、使用示例
1、编译打包gutil
https://gitee.com/simpleha/gutil.git
2、编译打包gmvc
https://gitee.com/simpleha/gmvc.git
3、新建webapp,引入pom
<dependency> <groupId>com.shuimutong</groupId> <artifactId>gmvc</artifactId> <version>0.0.1-SNAPSHOT</version> </dependency>
4、修改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>gmvc</servlet-name> <servlet-class>com.shuimutong.gmvc.handler.XDispatcherServlet</servlet-class> <init-param> <param-name>basePackage</param-name> <param-value>com.shuimutong.testgmvc</param-value> //框架扫描的包名,多个路径以“,”连接 </init-param> <load-on-startup>2</load-on-startup> </servlet> <servlet-mapping> <servlet-name>gmvc</servlet-name> <url-pattern>/</url-pattern> </servlet-mapping> </web-app>
5、编写dao层
package com.shuimutong.testgmvc.dao; import com.shuimutong.testgmvc.bean.Person; //接口 public interface TestDao { Person findPerson(); } package com.shuimutong.testgmvc.dao.impl; import com.shuimutong.gmvc.annotation.XRepository; import com.shuimutong.testgmvc.bean.Person; import com.shuimutong.testgmvc.dao.TestDao; //实现类,需加注解 @XRepository public class TestDaoImpl implements TestDao { @Override public Person findPerson() { return new Person(); } }
6、编写service
package com.shuimutong.testgmvc.service; //接口 public interface Test2Service { void speak(); String convertString(String s); } package com.shuimutong.testgmvc.service.impl; import com.alibaba.fastjson.JSONObject; import com.shuimutong.gmvc.annotation.XAutowired; import com.shuimutong.gmvc.annotation.XService; import com.shuimutong.testgmvc.bean.Person; import com.shuimutong.testgmvc.dao.TestDao; import com.shuimutong.testgmvc.service.Test2Service; //实现类 @XService public class Test2ServiceImpl implements Test2Service { @XAutowired private TestDao testDao; @Override public void speak() { System.out.println("----Test2ServiceImpl-----speak----"); } @Override public String convertString(String s) { Person p = testDao.findPerson(); p.setName(p.getName() + s); return JSONObject.toJSONString(p); } }
7、编写controller
package com.shuimutong.testgmvc.controller; import java.util.Map; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import com.alibaba.fastjson.JSONObject; import com.shuimutong.gmvc.annotation.XAutowired; import com.shuimutong.gmvc.annotation.XController; import com.shuimutong.gmvc.annotation.XRequestMapping; import com.shuimutong.gmvc.util.RequestResolveUtil; import com.shuimutong.testgmvc.service.Test2Service; import com.shuimutong.testgmvc.service.TestService; @XController @XRequestMapping("/test") public class TestController { @XAutowired private Test2Service test2Service; @XAutowired private TestService testService; @XRequestMapping("/testA") public void testA(HttpServletRequest request, HttpServletResponse reponse) { System.out.println("Hi, this is TestA"); } @XRequestMapping("/testB") public void testB(HttpServletRequest request, HttpServletResponse reponse) { System.out.println("Hi, this is TestA"); JSONObject res = new JSONObject(); String tmpMsg = null; Map<String, String[]> map = request.getParameterMap(); for(String k : map.keySet()) { res.put(k, map.get(k)); if(tmpMsg == null) { tmpMsg = map.get(k)[0]; } } System.out.println("----------testService.speak()------------"); testService.speak(); System.out.println("----------test2Service.convertString()------------"); String person = test2Service.convertString(tmpMsg); res.put("person", person); RequestResolveUtil.returnJson(request, reponse, res.toJSONString()); } }
8、启动服务
9、示例代码地址
https://github.com/shuimutong/useDemo/tree/master/gmvc_demo