看完上一个章节,相信你已经掌握了JDBC访问数据库的基本操作,也学会了使用数据源和数据库连接池,还学会了一个小框架——SpringJbdcTemplete,回过头来看看,似乎你已经掌握了,不少东西了,java,jsp,servlet,jstl,jdbc……看起来已经有足够的实力去开发一个动态web站点了。不过在这之前,似乎有一些东西还需要思考一下。
猿进化是一个原创系列文章,帮助你从一只小白快速进化为一个猿人,更多精彩内容,敬请大家关注公主号猿人工厂,点击猿人养成获取!今天,搞一个小活动,转发本文,凭借朋友圈截图,加本人微信:shangaladepangzi,备注:号脉。前5名转发者(以加本人微信为准),本人可以和你聊聊技术和面试相关的问题,一对一,找出你个人的技术症结所在,并开出处方,相信本人11年大厂996的互联网生涯的经验可以帮助到你。
在聊web框架之前,我们先来思考一个事情——为什么会有JSP?我们知道JSP运行在服务端,可以在页面中编写java代码,甚至可以在页面中访问数据库,然后生成一段HTML代码,然后发给客户端,大大的简化了远古时期的应用开发问题——在servlet中使用out输出HTML代码。后来有的人认为这样真的很爽,JSP页面相互就能完成跳转,servlet几乎用不上,还专门给起了个名字——JSP Model1.
JSP Model1刚开始的时候,谁用谁爽,简单直接敞开撸,可是慢慢的系统越来越庞大,java代码,html代码,css代码,js代码……都在一个页面里,各种标签还满天飞,没法维护了。最后,大家发现各种代码还是分开写比较好。于是servlet就被再次利用了起来——好歹是个堆代码的地方,代码和标签不用放在一起,于是就搞出了下面这种模式——MVC.
MVC是Model ViewController(模型-视图-控制器)的缩写,被广泛的应用到开发工作中。
Model(模型)是应用程序中用于处理应用程序数据逻辑的部分。通常模型对象负责在数据库中存取数据。
View(视图)是应用程序中处理数据显示的部分。通常视图是依据模型数据创建的。
Controller(控制器)是应用程序中处理用户交互的部分。通常控制器负责从视图读取数据,控制用户输入,并向模型发送数据。
MVC模式对软件开发有着极为重要的意义:有助于程序的分层开发,可以让人专注于某一方面的逻辑,比如不依赖业务逻辑也能开发视图层面的东西。对程序来说也是一个解耦的过程,各类程序分开编写,有助于代码的维护。更可贵的是,正是因为有了这样的分层,让职业更加细分,最早开发程序,大家都是一个人从头撸到尾,现在可以让擅长展示层面的人发挥特长,做前端工程师,让擅长数据处理,逻辑处理的人做后端工程师,干活的人更加专业、高效。在人力资源充足的情况下,做一个完整的项目,时间上大大缩短,质量还更加可靠。
JavaBean充当了模型的概念,有些人误认为模型就是JavaBean,JavaBean是什么?JavaBean只包含了构造方法、私有成员变量、公共的getter和setter方法。如何能够负担程序的数据逻辑呢?如果硬要说承担,也只能是数据的载体。准确的说法就是Model,因为它不止有属性,还包含了数据的访问程序。
Servlet作为控制器的角色,承接了和用户的交互,获取数据,并向模型发送数据。这倒是没什么毛病,虽然有很多web框架,但是几乎所有的web框架都实际上是对Servlet进行封装,提供了很多框架性的工具,完成框架的职责。
JSP作为视图层处理处理数据显示,但是实际上在视图这个层面来讲,处理展示JSP到现在几乎没什么用武之地了。因为用户看到的永远都是HTML.而渲染HTML出来的技术有很多,JSP只是其中一种,现在用得相对较多的是模板渲染技术,比如velocity、free marker等等。JSP也好,模板技术也好,使用它们做web开发时的作用绝大多数只有一个——在服务端渲染出相应的视图(最多的时候是HTML)返回给客户端,客户端拿到HTML之后再进行渲染,从而呈现出多姿多彩的界面。
但实际上web开发发展到今天,由于用户的机器越来越好,带宽也越来越大,浏览器的职责也越来越多。在过去,浏览器不能做(毕竟浏览器搞太多的事情容易卡死,你家也不是1024,卡死用户就不来了)的计算,可以更多的放在浏览器去做了——服务端返回数据和HTML,浏览器根据HTML和数据进行渲染得最后的页面展示,前后端的开发也彻底分家,这样做有些好处:
1.HTML无需后端程序的过多响应,返回就好,页面内容有兜底,在极端情况下(比如后端程序错误),不会看到丑陋的500或者404,用户体验相对好。
2.JavaScript是一个动态语言,运行在浏览器(搞懂代码到底在哪里执行是需要大家长期关注的问题),使用的是用户的机器,从某种意义上讲,降低了服务器资源的开销(渲染页面最终是IO操作产生的HTML),用别人家的电费就是爽。
3.在处理文件IO这件事情上,web服务器比应用服务器强悍多了,规避应用服务器的IO操作(莫信那些JAVA IO很强的谣言,至少目前还没哪个JAVA开发的应用服务器,在处理IO上面超过C语言的)的短板。
当然,也有不足,这样做的开发代价更高(一份工给两份钱),需要用户的基本要求也更高(要求更好的机器,更好的带宽,只是现在大家的机器都比较好,网络比以前好很多,是个能上网的机器就行)。
JAVA反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意方法和属性;这种动态获取信息以及动态调用对象方法的功能称为java语言的反射机制。
听起来有点绕口,简单点讲,你可以在程序运行的时候,获取一个类的属性、方法、Constructor、annotation,并且可以调用一个类的方法。java.lang.reflect包下定义了几个类分别代表对类的抽象。
java.lang.Class 抽象类的信息,通过它可以获取类的属性,方法,构造器
java.lang.Method抽象方法的信息,通过它可以直接调用某个对象的方法。
java.lang.Field 抽象属性的信息。
java.lang.Constructor抽象类的构造方法的信息。
java.lang.Annotation抽象Annotation的信息,Annotation是给程序提供元数据的东西,可以把程序的配置放在里面。
具体的API本文就不具体阐述了,自行度娘或者等猿人工厂君的后续专栏。
下面我就讲讲一个自己定义简单的web框架,功能很简陋基本意思要有——解决url的映射、参数简单封装、数据转发,便于大家以后的学习。
框架都有自己的配置,通常都是基于xml的,当然,现在基于元数据(annotion)的越来越流行,各有各的优点吧,这次我们采用annotion。
先定义两个annotion:
packagecom.pz.web.frame.annotation; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; @Retention(RetentionPolicy.RUNTIME) @Target({ ElementType.METHOD }) public @interface RequestURI { String url() default ""; }
package com.pz.web.frame.annotation; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; @Retention(RetentionPolicy.RUNTIME) @Target({ ElementType.TYPE }) public @interface WebController { }
有WebController这个标记的就代表我们处理业务的servlet类了。
有RequestURI这个标记的就代表我们处理业务的具体方法了,使用它去定义请求和方法映射。
考虑到反射的性能问题,简单的把一个类的方法抽象出来,放在内存里,这样提升一些性能。
packagecom.pz.web.frame.config; import java.lang.reflect.Method; public class MethodInfo { /** * method对象 */ private Method method; private Class[] getParameterTypes; public Method getMethod() { return method; } public void setMethod(Method method) { this.method = method; } public Class[] getGetParameterTypes() { return getParameterTypes; } public void setGetParameterTypes(Class[] getParameterTypes) { this.getParameterTypes = getParameterTypes; } }
接下来的事情就是抽象和加载我们的配置了。
package com.pz.web.frame.config; import com.pz.web.frame.annotation.RequestURI; import com.pz.web.frame.annotation.WebController; import com.pz.web.frame.util.ClassUtils; import java.io.IOException; import java.io.InputStream; import java.lang.reflect.Method; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Properties; public class WebConfiguration {
//暂时就不定义拦截器这类的东西了 private static MaprequestMapping = new HashMap (); private static Map methodClassMapping = new HashMap (); public static void init() throws InstantiationException, IllegalAccessException{ InputStream stream = ClassUtils.class.getClassLoader().getResourceAsStream("web-frame.properties"); Properties properties = new Properties(); try { properties.load(stream); } catch (IOException e) { e.printStackTrace(); } String packageName=properties.getProperty("package").toString(); List controllerList= ClassUtils.getClassName(packageName, true); for(String controller:controllerList){ System.out.println(controller); try { Class clazz=Class.forName(controller); methodClassMapping.put(clazz, clazz.newInstance()); if(clazz.isAnnotationPresent(WebController.class)){ System.out.println("找到了controller..."); Method[] methods = clazz.getMethods(); if(null!=methods){ for(Method method:methods){ if(method.isAnnotationPresent(RequestURI.class)){ System.out.println("找到了method..."+method.getName()); RequestURI uriData=method.getAnnotation(RequestURI.class); String url=uriData.url(); MethodInfo methodInfo = new MethodInfo(); methodInfo.setGetParameterTypes(method.getExceptionTypes()); methodInfo.setMethod(method); requestMapping.put(url, methodInfo); } } } } } catch (ClassNotFoundException e) { // TODO Auto-generated catch block e.printStackTrace(); } } for(String url:requestMapping.keySet()){ System.out.println(url+":"+requestMapping.get(url)); System.out.println("Object:"+getTargetObject(url)); } } /** * 获取请求对应的方法 * @param uri */ public static MethodInfo getMethod(String uri){ return requestMapping.get(uri); } /** * 获取请求对应的方法的对象 * @param uri */ public static Object getTargetObject(String uri){ return methodClassMapping.get(requestMapping.get(uri).getMethod().getDeclaringClass()); } public static void main(String args[]) throws InstantiationException, IllegalAccessException{ init(); } }
web服务器给我们留的请求处理接口实际上就是servlet,我们做下简单封装,这次仅仅实现了请求的统一转发,调用对应的方法,如果需要对流程进行统一处理,简单点搞,可以定义一个叫做handler接口,提供初始化,调用前,调用后的方法,供外部实现,然后封装个集合,方到配置中,然后改造下面的执行流程,在调用业务方法前后,循环调用即可,倒不一定用反射了。我是怎么想到的?猜的吧,反正web框架啊的套路基本就那些的。翻翻MVC框架源码,struts,struts2,springMVC,大家大体这个意思。
packagecom.pz.web.frame.servlet; import com.pz.web.frame.config.MethodInfo; import com.pz.web.frame.config.WebConfiguration; 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 DispacherServlet extends HttpServlet { @Override public void init() throws ServletException { try { WebConfiguration.init(); } catch (InstantiationException | IllegalAccessException e) { // TODO Auto-generated catch block e.printStackTrace(); } } @Override protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { String uri=request.getRequestURI().toString(); MethodInfo targetMethodInfo= WebConfiguration.getMethod(uri); Object targetObject=WebConfiguration.getTargetObject(uri); //todo 404 if(null==targetMethodInfo||null==targetObject){ response.sendError(404); } try { Class[] classes =targetMethodInfo.getGetParameterTypes(); Object[] args=handelParameter(targetMethodInfo,request,response); //这里可以加入统一的前置流程处理噢 Object viewObject= targetMethodInfo.getMethod().invoke(targetObject,args);
//这里可以加入统一的后置流程处理噢
if(null==viewObject){ return ; } request.getRequestDispatcher((String) viewObject).forward( request , response ); } catch (Exception e) { // TODO Auto-generated catch block e.printStackTrace(); } } private Object[] handelParameter(MethodInfo methodInfo,HttpServletRequest request, HttpServletResponse response) { Class[] params=methodInfo.getGetParameterTypes(); Object[] objArray=new Object[params.length]; for(int i=0;itodo 暂时就不搞对象转换了 } } return objArray; } }
packagecom.pz.web.frame.util; import com.pz.web.frame.annotation.RequestURI; import com.pz.web.frame.annotation.WebController; import java.io.File; import java.io.IOException; import java.io.InputStream; import java.lang.reflect.Method; import java.net.URL; import java.util.*; public class ClassUtils { /** * 获取某包下所有类 * * @param packageName * 包名 * @param childPackage * 是否遍历子包 * @return 类的完整名称 */ public static ListgetClassName(String packageName, boolean childPackage) { List fileNames = null; ClassLoader loader = Thread.currentThread().getContextClassLoader(); String packagePath = packageName.replace(".", "/"); URL url = loader.getResource(packagePath); if (url != null) { String type = url.getProtocol(); if (type.equals("file")) { fileNames = getClassNameByFile(url.getPath(), null, childPackage); } } return fileNames; } /** * 从项目文件获取某包下所有类 * * @param filePath * 文件路径 * @param className * 类名集合 * @param childPackage * 是否遍历子包 * @return 类的完整名称 */ private static List getClassNameByFile(String filePath, List className, boolean childPackage) { List myClassName = new ArrayList<>(); File file = new File(filePath); File[] childFiles = file.listFiles(); for (File childFile : childFiles) { if (childFile.isDirectory()) { if (childPackage) { myClassName.addAll(getClassNameByFile(childFile.getPath(), myClassName, childPackage)); } } else { String childFilePath = childFile.getPath(); if (childFilePath.endsWith(".class")) { childFilePath = childFilePath.substring(childFilePath.indexOf("/classes") + 9, childFilePath.lastIndexOf(".")); childFilePath = childFilePath.replace("/", "."); myClassName.add(childFilePath); } } } return myClassName; } public static void main(String args[]){ Map requestMapping = new HashMap (); InputStream stream = ClassUtils.class.getClassLoader().getResourceAsStream("web-frame.properties"); Properties properties = new Properties(); try { properties.load(stream); } catch (IOException e) { e.printStackTrace(); } String packageName=properties.getProperty("package").toString(); List controllerList= ClassUtils.getClassName(packageName, true); for(String controller:controllerList){ System.out.println(controller); try { Class clazz=Class.forName(controller); if(clazz.isAnnotationPresent(WebController.class)){ System.out.println("找到了controller..."); Method[] methods = clazz.getMethods(); if(null!=methods){ for(Method method:methods){ if(method.isAnnotationPresent(RequestURI.class)){ System.out.println("找到了method..."+method.getName()); RequestURI uriData=method.getAnnotation(RequestURI.class); String url=uriData.url(); requestMapping.put(url, method); } } } } } catch (ClassNotFoundException e) { // TODO Auto-generated catch block e.printStackTrace(); } } for(String url:requestMapping.keySet()){ System.out.println(url+":"+requestMapping.get(url)); } } }
"-//SunMicrosystems, Inc.//DTD Web Application 2.3//EN"
"http://java.sun.com/dtd/web-app_2_3.dtd">
packagecom.pz.web.biz; import com.pz.web.frame.annotation.RequestURI; import com.pz.web.frame.annotation.WebController; @WebController public class MyTestController { @RequestURI(url="/mytest.pz") public String mytest(){ return "index.jsp"; } }
下一次咱们可以开始实战了噢。