猿进化系列13——一文搞懂MVC相关框架套路
Posted 猿人工厂
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了猿进化系列13——一文搞懂MVC相关框架套路相关的知识,希望对你有一定的参考价值。
看完上一个章节,相信你已经掌握了JDBC访问数据库的基本操作,也学会了使用数据源和数据库连接池,还学会了一个小框架——SpringJbdcTemplete,回过头来看看,似乎你已经掌握了,不少东西了,java,jsp,servlet,jstl,jdbc……看起来已经有足够的实力去开发一个动态web站点了。不过在这之前,似乎有一些东西还需要思考一下。
在聊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 Map<String,MethodInfo> requestMapping = new HashMap<String,MethodInfo>();
private static Map<Class,Object> methodClassMapping = new HashMap<Class,Object>();
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<String> 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;i<params.length;i++){
Object obj=null;
try {
obj = params[i].newInstance();
} catch (InstantiationException e) {
} catch (IllegalAccessException e) {
}
if(obj instanceof HttpServletRequest || obj instanceof HttpServletResponse){
objArray[i]=params[i];
}else{
//todo 暂时就不搞对象转换了
}
}
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 List<String> getClassName(String packageName, boolean childPackage) {
List<String> 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<String> getClassNameByFile(String filePath, List<String> className, boolean childPackage) {
List<String> 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<String,Method> requestMapping = new HashMap<String,Method>();
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<String> 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));
}
}
}
Web.xml配置
<!DOCTYPE web-app PUBLIC
"-//SunMicrosystems, Inc.//DTD Web Application 2.3//EN"
"http://java.sun.com/dtd/web-app_2_3.dtd">
<web-app>
<servlet>
<servlet-name>DispacherServlet</servlet-name>
<servlet-class>com.pz.web.frame.servlet.DispacherServlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>DispacherServlet</servlet-name>
<url-pattern>*.pz</url-pattern>
</servlet-mapping>
</web-app>
测试一下
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";
}
}
下一次咱们可以开始实战了噢。
以上是关于猿进化系列13——一文搞懂MVC相关框架套路的主要内容,如果未能解决你的问题,请参考以下文章
RPC基础系列2一文搞懂gRPC和Thrift的基本原理和区别