框架结构说明
框架采用MVC模式,Ioc控制反转技术。通过框架来对应用进行初始化和管理,提高开发效率。
若使用传统的Servlet来开发Java Web,Servlet的数量会随着业务功能的扩展而不断增加,系统变得庞大,然以维护,有必要减少Servlet数量,
将某类业务交给Controller来处理,Service负责给Controller提供服务。Service不是通过new方式来创建的,而是通过"依赖注入"的方式,由框架来创建所需要的对象。框架结构图:
DispatherServlet: 请求转发器,通过service()方法转发所有的请求。
Loader: 在DispatherServlet的init()方法调用,进行应用的初始化工作。
ClassHelper:通过ClassUtil类加载应用基础包下所有的类。
BeanContainer:通过BeanFactory将ClassHelper加载获取的所有带有Controller,Service注解等需要容器管理的类进行实例化并保存在容器中。
IocHelper:Controller中定义Service成员变量,需要通过框架自身来实例化。IocHelper将Controller中定义 Service成员变量进行依赖注入。
ControllerHelper:将Controller中带有RequestMapping注解的方法与其要处理的请求路径和请求方法建立映射关系。
创建Maven Web工程 framework-diy,在pom.xml文件引入需要的包。servlet-api,jsp-api,jstl,commons-lang3,commons-collections4,jackson等。
javax.servlet javax.servlet-api 3.0.1 javax.servlet.jsp jsp-api 2.2 javax.servlet jstl 1.2 org.apache.commons commons-lang3 3.3.2 org.apache.commons commons-collections4 4.0 com.fasterxml.jackson.core jackson-databind 2.4.5
加载配置项
首先我们来看下Spring框架,Spring框架定义了一个spring-config.xml配置文件,可以在配置文件定义基础包名,JSP基础路径,静态资源文件的路径等等。
现在我们来仿造一下,为框架定义一个简单的配置文件config.properties(默认配置文件为config.properties),放在/src/main/resources目录下。框架需要根据这些配置项来初始化应用。
//config.properties#基础包名app.base_package = com.hubwiz.web #jsp路径app.jsp_path = /jsp/ #静态资源路径app.asset_path = /asset/有了配置文件,我们需要编写一个PropsUtil类来加载配置文件config.properties。获取当前线程的类加载器,getContextClassLoader().getResourceAsStream(fileName)根据文件名称来加载配置文件。
//加载配置文件public class PropsUtil { /** * 加载配置文件 * * @param fileName 配置文件名 * @return */ public static Properties loadProps(String fileName) { Properties props = null; InputStream is = null; try { is = Thread.currentThread().getContextClassLoader().getResourceAsStream(fileName); if (is == null) { throw new FileNotFoundException(fileName + " file is not found"); } props = new Properties(); props.load(is); } catch (IOException ioe) { ioe.printStackTrace(); } finally { if (is != null) { try { is.close(); } catch (IOException ioe) { ioe.printStackTrace(); } } } return props; } /** * 获取字符型属性(默认值为空字符串) * * @param props * @param key * @return */ public static String getString(Properties props, String key) { return getString(props, key, ""); } /** * 获取字符型属性(可指定默认值) * * @param props * @param key * @param defaultValue * @return */ public static String getString(Properties props, String key, String defaultValue) { String value = defaultValue; if (props.containsKey(key)) { value = props.getProperty(key); } return value; } }
加载了配置文件,需要编写一个类ConfigHelper来获取配置文件属性。框架初始化需要根据配置文件来初始化应用。框架默认配置文件为config.properties,只有创建了名为config.properties的配置文件,框架才能运行。
/** * 获取配置文件属性 */public class ConfigHelper { private static final Properties CONFIG_PROPS = PropsUtil.loadProps("config.properties");//默认配置文件为config.properties /** * 获取应用基础包名 * * @return */ public static String getAppBasePackage() { return PropsUtil.getString(CONFIG_PROPS, Constant.APP_BASE_PACKAGE); } /** * 获取JSP路径 * * @return */ public static String getAppJspPath() { return PropsUtil.getString(CONFIG_PROPS, Constant.APP_JSP_PACKAGE); } /** * 获取应用静态资源路径 * * @return */ public static String getAppAssetPath() { return PropsUtil.getString(CONFIG_PROPS, Constant.APP_ASSET_PATH, "/asset/"); } }
实现一个类加载器ClassUtil来加载基础包下的所有的类。只有加载了这些类,框架才能对其进行初始化。获取当前线程的ClassLoader通过这个类加载器来加载类,获取指定包名下的所有的类,需要指定根据包名并将其转换为文件路径,读取class文件或jar包,获取指定的类名去加载类。
/** * 获取类加载器 * * @return */ public static ClassLoader getClassLoader() { return Thread.currentThread().getContextClassLoader(); } /** * 加载类 * * @param className 类名称 * @param isInitialized 是否执行类的静态代码块 * @return */ public static Class loadClass(String className, boolean isInitialized) { Class clazz; try { clazz = Class.forName(className, isInitialized, getClassLoader()); } catch (ClassNotFoundException cnfe) { cnfe.printStackTrace(); throw new RuntimeException(cnfe); } return clazz; } /** * 获取指定包下所有类 * * @param packageName * @return */ public static ArrayList> getClasses(String packageName) { ArrayList > classes = new ArrayList<>(); try { Enumeration urls = getClassLoader().getResources(packageName.replace(".", "/")); while (urls.hasMoreElements()) { URL url = urls.nextElement(); if (url != null) { String protocol = url.getProtocol(); if (protocol.equals("file")) { String packagePath = url.getPath().replaceAll("%20", " "); addClass(classes, packagePath, packageName); } else if (protocol.equals("jar")) { JarURLConnection jarURLConnection = (JarURLConnection) url.openConnection(); if (jarURLConnection != null) { JarFile jarFile = jarURLConnection.getJarFile(); if (jarFile != null) { Enumeration jarEntries = jarFile.entries(); while (jarEntries.hasMoreElements()) { JarEntry jarEntry = jarEntries.nextElement(); String jarEntryName = jarEntry.getName(); if (jarEntryName.equals(".class")) { String className = jarEntryName.substring(0, jarEntryName.lastIndexOf(".")).replaceAll("/", "."); doAddClass(classes, className); } } } } } } } } catch (Exception e) { e.printStackTrace(); throw new RuntimeException(e); } return classes; } ... ... ...}
本框架采用注解来标识Controller,Service类。所以需要定义注解来标识那些类是Controller,Controller类中那些方法响应url请求等。
控制器类上使用Controller注解,在控制器类的方法上使用RequestMapping注解,使用Autowired注解将服务类依赖注入进来。服务类使用Service注解。
/** * 控制器注解 */@Target(ElementType.TYPE)@Retention(RetentionPolicy.RUNTIME)public @interface Controller {} /** * 请求方法注解 */@Target(ElementType.METHOD)@Retention(RetentionPolicy.RUNTIME)public @interface RequestMapping { //请求类型路径 String path(); //请求方法 String method();} /** * 服务类注解 */@Target(ElementType.TYPE)@Retention(RetentionPolicy.RUNTIME)public @interface Service {} /** * 依赖注入注解 */@Target(ElementType.FIELD)@Retention(RetentionPolicy.RUNTIME)public @interface Autowired {}
Controller,Service类是框架需要管理的类,把他们统称为Bean类。实现一个类可以获取应用所有的Controller,Service类。首先调用ConfigHelper.getAppBasePackage()获取基础包名,然后调用ClassUtil.getClasses(basePackage)加载该基础包名下所有的类,保存在变量ArrayList> classes中;再遍历ArrayList> classes获取Controller,Service等类。
//获取类public class ClassHelper { //基础包名下所有的类 private static final ArrayList> classes; static { String basePackage = ConfigHelper.getAppBasePackage(); classes = ClassUtil.getClasses(basePackage); } /** * 获取基础包名下所有的类 * * @return */ public static ArrayList > getClasses() { return classes; } /** * 获取所有Service类 * * @return */ public static ArrayList > getServiceClasses() { ArrayList > sc = new ArrayList<>(); //补全代码 return sc; } /** * 获取所有Controller类 * * @return */ public static ArrayList > getControllerClasses() { ArrayList > cc = new ArrayList<>(); for (Class c : classes) { if (c.isAnnotationPresent(Controller.class)) { cc.add(c); } } return cc; } /** * 框架Bean容器主要管理Service,Controller类 * * @return */ public static ArrayList > getBeanClasses() { ArrayList > bc = new ArrayList<>(); bc.addAll(getServiceClasses()); bc.addAll(getControllerClasses()); return bc; }}现在来测试类加载器和获取类是否正确,配置文件将基础包名设为app.base_package=com.hubwiz.web.controller,在这个包下,实现了三个类HomeController类(带Controller注解),PersonService类(带Service注解),Person类。public static void main(String[] args) { ArrayList > ces = ClassHelper.getClasses(); for (Class c : ces) { System.out.println(c.getSimpleName()); } }
回顾下前面的知识,通过加载配置文件获取应用基础包名,加载基础包名下所有的类,获取Controller,Service类。到目前为止,我们只是加载了类,但是无法通过获取的类来实例化对象。因此需要一个反射工具,来实例化类。
创建一个Bena工厂,来生产(实例化Bean类对象)Bean。newInstance()方法,实例化目标类;invokeMethod()通过反射机制来调用类中的方法;setField()通过反射机制为类成员遍历赋值。
//Bean工厂public class BeanFactory { /** * 创建实例 * * @param clazz * @return */ public static Object newInstance(Class clazz) { Object instance; try { instance = clazz.newInstance(); } catch (Exception e) { throw new RuntimeException(e); } return instance; } /** * 方法调用 * * @param obj * @param method * @param args * @return */ public static Object invokeMethod(Object obj, Method method, Object... args) { Object result; try { method.setAccessible(true); result = method.invoke(obj, args); } catch (Exception e) { throw new RuntimeException(e); } return result; } /** * 设置成员变量值 * * @param obj * @param field * @param value */ public static void setField(Object obj, Field field, Object value) { try { field.setAccessible(true); field.set(obj, value); } catch (Exception e) { throw new RuntimeException(e); } }}
前面在ClassHelper定义了方法getBeanClasses()来获取Bean容器需要管理的所有Controller,Service类,获取这些类以后,调用BeanFactory.newInstance(Class clazz)方法来实例化类的对象,缓存在Map beanContainer中,需要随时获取。Map说明:使用类全名称作为key,类的实例对象作为value值。
BeanContainer初始化:首先调用ClassHelper.getBeanClasses()获取所有的Bean类,调用Bean工厂方法newInstance()方法来实例化Bean类,保存在beanContainer中。通过类全名称从beanContainer获取需要的Bean实例对象。
/** * Bean容器 */public class BeanContainer { /** * 存放Bean类名称和Bean实例的映射关系 */ private static final MapbeanContainer = new HashMap<>(); static { ArrayList > beanClasses = ClassHelper.getBeanClasses(); for (Class beanClass : beanClasses) { Object obj = BeanFactory.newInstance(beanClass); beanContainer.put(beanClass.getName(), obj); } } /** * 获取Bean映射 * * @return */ public static Map getBeanContainer() { return beanContainer; } /** * 获取Bean实例 */ public static T getBean(String className) { if (!beanContainer.containsKey(className)) { throw new RuntimeException("can not get bean by className: " + className); } return (T) beanContainer.get(className); } /** * 设置Bean实例 */ public static void setBean(String className, Object obj) { //补全代码 }}
首先来了解下Spring框架的核心机制:依赖注入。当某个Java实例(调用者)需要另一个Java实例(被调用者)时,在传统的程序设计过程中,通常由调用者来创建被调用者的实例。在依赖注入模式下,创建调用者的工作不再由调用者来完成,因此称为控制反转(Ioc);创建被调用者实例的工作通常由Spring容器来完成,然后注入调用者,因此也成为依赖注入(DI)。
Controller中定义Service成员变量,需要通过框架自身来实例化。
首先通过beanContainer获取所有的Bean类全名称和Bean实例,遍历获取所有的Controller类,通过反射获取类中的成员变量,遍历这些成员变量看是否有Autowired注解,若有,从beanContainer中获取Bean实例,然后调用Bean工厂setField()方法将获取的Bean实例赋值给成员变量。
/** * 依赖注入 */public final class IocHelper { static { MapbeanContainer = BeanContainer.getBeanContainer(); if (CollectionUtil.isNotEmpty(beanContainer)) { initIOC(beanContainer); } } private static void initIOC( Map beanContainer) { for (Map.Entry beanEntry : beanContainer.entrySet()) { String className = beanEntry.getKey(); Object beanInstance = beanEntry.getValue(); Class beanClass = null; try { beanClass = Class.forName(className); System.out.println(className); } catch (ClassNotFoundException e) { e.printStackTrace(); } //Controller类中定义的属性 Field[] beanFields = beanClass.getDeclaredFields(); if (ArrayUtil.isNotEmpty(beanFields)) { for (Field beanField : beanFields) { //带有Autowired注解的成员变量 if (beanField.isAnnotationPresent(Autowired.class)) { //成员变量的类 Class beanFieldClass = beanField.getType(); Object beanFieldInstance = beanContainer.get(beanFieldClass.getName()); if (beanFieldInstance != null) { //依赖注入 BeanFactory.setField(beanInstance, beanField, beanFieldInstance); } } } } } }}
Controller类中带有RequestMapping注解的方法处理特定URL请求,如何判断当前请求 URL&Method 对应那个Controller & method,这是接下来要实现的。通过反射可以获取Controller中带有RequestMapping注解的方法,进而获得RequestMapping注解中请求方法和请求路径。封装一个请求对象request与处理request对象handler,将request与handler建立一个映射关系。
请求对象request:包括请求路径path;请求方法method两个属性。
处理request对象handler:包括Controller类;带有RequestMapping注解的方法method。
/** * 封装请求信息 */public class Request { //请求方法 private String requestMethod; //请求路径 private String requestPath; public Request(String requestMethod,String requestPath) { this.requestMethod = requestMethod; this.requestPath = requestPath; } public String getRequestMethod() { return requestMethod; } public String getRequestPath() { return requestPath; } @Override public int hashCode() { return HashCodeBuilder.reflectionHashCode(this); } @Override public boolean equals(Object obj) { return EqualsBuilder.reflectionEquals(this,obj); }}/** * 处理Request请求对应Controller & method */public class Handler { private Class controllerClass; private Method method; public Handler(Class controllerClass,Method method) { this.controllerClass = controllerClass; this.method = method; } public Class getControllerClass() { return controllerClass; } public Method getMethod() { return method; }}
public class ControllerHelper { //请求request与处理请求handler映射关系 private static final MapRequestMap = new HashMap<>(); static { ArrayList > controllerClasses = ClassHelper.getControllerClasses(); if (CollectionUtil.isNotEmpty(controllerClasses)) { initRequestMapp(controllerClasses); } } private static void initRequestMapp(ArrayList > controllerClasses) { for (Class controllerClass : controllerClasses) { Method[] methods = controllerClass.getDeclaredMethods(); if (ArrayUtil.isNotEmpty(methods)) { for (Method method : methods) { //带有RequestMapping注解的方法 if (method.isAnnotationPresent(RequestMapping.class)) { RequestMapping rm = method.getAnnotation(RequestMapping.class); //请求路径与请求方法 Request request = new Request(rm.method(), rm.path()); //对应请求路径与请求方法的Controller和method Handler handler = new Handler(controllerClass, method); RequestMap.put(request, handler); } } } } } /** * 获取handler * * @param requestMethod * @param requestPath * @return */ public static Handler getHandler(String requestMethod, String requestPath) { Request request = new Request(requestMethod, requestPath); return RequestMap.get(request); }}
框架基本搭建起来了,现在需要编写一个类来初始化应用。
初始化步骤:
1、加载ClassHelper类,通过这个类加载基础包名下所有的类。
2、加载BeanContainer类,将基础包名下所有Bean类,通过Bean工厂实例化保存在Bean容器。
3、加载IocHelper类,实例化Bean类,需要为Controller类中带有Autowired注解的属性赋值。
4、加载ControllerHelper类,将Controller类中带有RequestMapping注解的方法,建立与请求路径和请求方法的映射关系,这样框架才能找到处理请求对应的方法。
/** * 初始化框架 */public class Loader { public static void init() { Class [] cs = {ClassHelper.class, BeanContainer.class, IocHelper.class, ControllerHelper.class}; for (Class c: cs) { ClassUtil.loadClass(c.getName(),true); } }}
通常前端通过form表格的形式向后台发送数据,需要一个类封装从HttpServletRequest请求对象中获取所有的参数,然后传递给处理方法。首先解析请求参数(form表单数据),将其封装成Param类中,传递给Controller的方法处理。
通过request.getParameterNames()将发送请求页面中form表单所具有name属性的表单对象获取,request.getParameterValues(name)获取其值,生成FormParam保存在Param中。
/** * 解析请求参数,form表单数据 */public class ParameterUtil { public static Param createParam(HttpServletRequest request) throws IOException { return new Param(parseParameterNames(request)); } private static MapparseParameterNames(HttpServletRequest request) { Map formParams = new HashMap<>(); //将发送请求页面中form表单所具有name属性的表单对象获取 Enumeration paramNames = request.getParameterNames(); while (paramNames.hasMoreElements()) { String fieldName = paramNames.nextElement(); String[] fieldValues = request.getParameterValues(fieldName); if (ArrayUtil.isNotEmpty(fieldValues)) { Object fieldValue; if (fieldValues.length == 1) { fieldValue = fieldValues[0]; } else { StringBuilder sb = new StringBuilder(""); for (int i = 0; i < fieldValues.length; ++i) { sb.append(fieldValues[i]); if (i != fieldValues.length - 1) { sb.append(StringUtil.separator); } } fieldValue = sb.toString(); } formParams.put(fieldName,fieldValue); } } return formParams; } }
/** * 请求参数对象 */public class Param { private MapformParams; public Param(Map formParams) { this.formParams = formParams; } /** * 判断参数是否为空 * * @return boolean */ public boolean isEmpty() { return CollectionUtil.isEmpty(formParams); } public Map getFormParams() { return formParams; } /** * 根据参数名取String型参数值 * * @param name * @return */ public String getString(String name) { return CastUtil.castString(formParams.get(name)); } public double getDouble(String name) { return CastUtil.castDouble(formParams.get(name)); } public long getLong(String name) { return CastUtil.castLong(formParams.get(name)); } public int getInt(String name) { return CastUtil.castInt(formParams.get(name)); } public Boolean getBoolean(String name) { return CastUtil.castBoolean(formParams.get(name)); } }
在处理请求时,通常会返回视图JSP页面和数据。所以现在需要将视图JSP路径和数据封装在一起返回。如果只返回数据,则返回JSON格式数据。
返回视图JSP,视图中包含视图JSP路径和视图中所需的数据:
public class ModelAndView { //返回JSP路径 private String path; //模型数据 private MapmData; public ModelAndView(String path) { this.path = path; mData = new HashMap<>(); } public ModelAndView addmData(String key, Object obj) { mData.put(key,obj); return this; } public String getPath() { return path; } public Map getmData() { return mData; } }
/** * 返回数据 */public class Data{ private T datas; public Data(T datas) { this.datas = datas; } public T getDatas() { return datas; }}
由于返回的数据格式为JSON,现在需要将POJO转换成JSON格式。采用Jackson框架。
/** * POJO转换为JSON */public class JsonUtil { /** * 将POJO转化为JSON * * @param obj * @param* @return */ public static String toJSON(T obj) { String json = null; try { ObjectMapper mapper = new ObjectMapper(); json = mapper.writeValueAsString(obj); } catch (Exception e) { e.printStackTrace(); } return json; } /** * 将JSON转化为POJO * * @param json * @param clazz * @param * @return */ public static T fromJSON(String json, Class clazz) { T pojo = null; try { ObjectMapper mapper = new ObjectMapper(); pojo = mapper.readValue(json, clazz); } catch (Exception e) { e.printStackTrace(); } return pojo; }}
请求转发器是框架的核心。请求转发器转发器用来处理所有的请求。DispatherServlet继承HttpServlet,在init()方法调用Loader.init()来初始化框架和应用。service()方法响应所有的请求。
请求转发过程:
1、通过req.getMethod().toLowerCase()获取请求方法(get,post,put delete);req.getContextPath()获取请求路径,根据请求路径和请求方法调用ControllerHelper.getHandler()方法获取处理这个请求对应的handler。
2、Hanler封装处理请求的Controller和方法,所有获取的handler,就可以获取处理请求的Controller和方法method。
3、获取了处理这个请求的Controller类,现在需要在Bean获取这个Controller类的实例对象,调用BeanContainer.getBean()来获取Controller类的实例对象。
4、调用ParameterUtil.createParam(req)来解析请求参数。
5、调用handler.getMethod()获取处理这个请求的方法,通过反射机制BeanFactory.invokeMethod()来调用这个方法,处理这个请求的方法就会来处理这个请求。
6、根据返回的结果是ModelAndView还是Data来处理返回问题。返回结果为ModelAndView,调用req.getRequestDispatcher(ConfigHelper.getAppJspPath() + path).forward(req, resp)将页面响应转发到ConfigHelper.getAppJspPath() + path;返回结果为Data,将返回结果POJO转换为JSON格式,写入HttpServletRespone对象中,输出到客户端浏览器。
/** * 请求转发器 */@WebServlet(urlPatterns = "/",loadOnStartup = 0)public class DispatherServlet extends HttpServlet { @Override public void init(ServletConfig config) throws ServletException { Loader.init();//初始化框架&应用 ServletContext sc = config.getServletContext(); //注册JSP的Servlet ServletRegistration jspServlet = sc.getServletRegistration("jsp"); jspServlet.addMapping(ConfigHelper.getAppJspPath() + "*"); //注册处理静态资源的Servlet ServletRegistration defaultServlet = sc.getServletRegistration("default"); defaultServlet.addMapping(ConfigHelper.getAppAssetPath() + "*"); } @Override protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { //获取请求方法 String requestMethod = req.getMethod().toLowerCase(); //请求路径url String url = req.getRequestURI(); String contextPath = req.getContextPath(); String requestPath = null; if (contextPath != null && contextPath.length() > 0) { requestPath = url.substring(contextPath.length()); } //获取处理这个请求的handler Handler handler = ControllerHelper.getHandler(requestMethod, requestPath); // System.out.println(requestMethod + " " + requestPath); if (handler != null) { Class controllerClass = handler.getControllerClass(); Object controllerBean = BeanContainer.getBean(controllerClass.getName()); //解析请求参数 Param param = ParameterUtil.createParam(req); Object result;//请求返回对象 Method method = handler.getMethod();//处理请求的方法 if (param.isEmpty()) { result = BeanFactory.invokeMethod(controllerBean, method); } else { result = BeanFactory.invokeMethod(controllerBean, method, param); } if (result instanceof ModelAndView) { handleViewResult((ModelAndView) result, req, resp); } else { handleDataResult((Data) result, resp); } } } //返回为JSP页面 private static void handleViewResult(ModelAndView view, HttpServletRequest req, HttpServletResponse resp) throws IOException, ServletException { String path = view.getPath(); if (StringUtil.isNotEmpty(path)) { if (path.startsWith("/")) { resp.sendRedirect(req.getContextPath() + path); } else { Mapdata = view.getmData(); for (Map.Entry entry : data.entrySet()) { req.setAttribute(entry.getKey(), entry.getValue()); } //forward将页面响应转发到ConfigHelper.getAppJspPath() + path //补全代码 } } } //返回JSON数据 private static void handleDataResult(Data data, HttpServletResponse resp) throws IOException { Object model = data.getData(); if (model != null) { resp.setContentType("application/json"); resp.setCharacterEncoding("UTF-8"); PrintWriter writer = resp.getWriter(); String json = JsonUtil.toJSON(model); writer.write(json); writer.flush(); writer.close(); } } }
---汇智网