为什么编写一个自己的web框架,传统web编程模型有什么不好?
一个请求对应一个servlet,导致servlet过多,web.xml文件特别大,维护起来麻烦。
即使采用根据请求参数method在servlet里派发的方式,以减少servlet的个数。但这种方式也要求在每个servlet中写上很多重复的if…else…代码
Servlet的映射地址写在web.xml文件中,工程大时,如果映射地址发生修改,修改起来特别麻烦。
更为重要的是,servlet调用由web服务器负责,开发人员想控制servlet的调度,以加入一些类似于权限控制的功能特别困难。
SimpleWebFrameWordk的特点:
由一个中央控制器ControllerServlet处理所有的请求,并根据请求uri,派发不同的action进行处理。
全注解配置,处理请求的action由注解配置其映射地址。
支持method请求参数,根据客户机带来的method,调用action的相应方法进行处理,以减少action的数量。
2.编写ControllerServlet(web框架的核心类)
3.编写需要用到的注解Control(value 属性为http请求的uri)
4. 当我们访问的URL为 http://localhost:8088/project_name /control/customer/customer.do?method=list 时,
一个请求对应一个servlet,导致servlet过多,web.xml文件特别大,维护起来麻烦。
即使采用根据请求参数method在servlet里派发的方式,以减少servlet的个数。但这种方式也要求在每个servlet中写上很多重复的if…else…代码
Servlet的映射地址写在web.xml文件中,工程大时,如果映射地址发生修改,修改起来特别麻烦。
更为重要的是,servlet调用由web服务器负责,开发人员想控制servlet的调度,以加入一些类似于权限控制的功能特别困难。
SimpleWebFrameWordk的特点:
由一个中央控制器ControllerServlet处理所有的请求,并根据请求uri,派发不同的action进行处理。
全注解配置,处理请求的action由注解配置其映射地址。
支持method请求参数,根据客户机带来的method,调用action的相应方法进行处理,以减少action的数量。
难点:自动扫描类路径,并通过自定义类装载器加载所有处理请求的action,并关联到相应的处理uri上。
具体的实现步骤与代码:
1. 在web.xml文件配置一个servlet , 这个servlet 用来处理URL以.do 结尾的所有HTTP请求
<servlet>
<description>This is the description of my J2EE component</description>
<display-name>This is the display name of my J2EE component</display-name>
<servlet-name>ControllerServlet</servlet-name>
<servlet-class>com.alex.framework.ControllerServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>ControllerServlet</servlet-name>
<url-pattern>*.do</url-pattern>
</servlet-mapping>
2.编写ControllerServlet(web框架的核心类)
package com.alex.framework;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
public class ControllerServlet extends HttpServlet {
//Map中的key用来存放URI,value用来存放URI相对应的Action对象(Action指处理各类请求的控制器)
private Map<String,Object> map = new HashMap<String,Object>();
@Override
public void init() throws ServletException {
String classPath = this.getServletContext().getRealPath("/WEB-INF/classes");
scanClassPath(new File(classPath));
}
/*
* 扫描类路径所有类文件,如果类文件含有Control注解,则把注解的value(URI)放进Map中作为key,
* 并将类的实例对象作为Map当中的value
*/
private void scanClassPath(File file) {
try{
if(file.isFile()){
if(file.getName().endsWith(".class")){
String path = file.getPath();
MyClassLoader myClassLoader = new MyClassLoader(ControllerServlet.class.getClassLoader());
Class clazz = myClassLoader.load(path);
Control control = (Control)clazz.getAnnotation(Control.class);
if(control!=null){
String uri = control.value();
Object action = clazz.newInstance();
map.put(uri, action);
}
}
}else{
File[] files = file.listFiles();
for(File child:files){
scanClassPath(child);
}
}
}catch(Exception e){
throw new RuntimeException(e);
}
}
//自定义一个类加载器,使得类加载器能够通过类文件路径获得该类的字节码文件
class MyClassLoader extends ClassLoader{
public MyClassLoader(ClassLoader parent){
super(parent);
}
public Class load(String path){
FileInputStream fis = null;
try{
fis = new FileInputStream(path);
byte[] buf = new byte[fis.available()];
int len = 0;
int total = 0;
int fileLength = buf.length;
while(total<fileLength){
len=fis.read(buf, total,fileLength-total);
total=total+len;
}
return super.defineClass(null, buf, 0, fileLength);
}catch(Exception e){
throw new RuntimeException(e);
}finally{
if(fis!=null){
try {
fis.close();
} catch (IOException e) {
throw new RuntimeException(e);
}
fis=null;
}
}
}
}
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
request.setCharacterEncoding("UTF-8");
//获得http请求的URI并截掉后面的请求参数字符串
String uri = request.getRequestURI();
uri = uri.substring(request.getContextPath().length(), uri.length()-3);
if(map.containsKey(uri)){
//通过http请求uri获得相对应的Action对象
Object obj = map.get(uri);
//获得http请求的方法名
String methodName = request.getParameter("method");
//如果请求的方法null,则默认调用Action对象中的exec方法
if(methodName==null){
methodName="exec";
}
Method method = null;
try {
//通过反射获得要执行的方法对象
method = obj.getClass().getMethod(methodName, HttpServletRequest.class,HttpServletResponse.class);
} catch (Exception e){
throw new RuntimeException("在"+obj.getClass().getName()+"上找不到与" + methodName +"相对应的方法!!!");
}
try {
//执行Action对象中的方法
method.invoke(obj, request,response);
} catch (Exception e){
e.printStackTrace();
}
}
}
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
doGet(request,response);
}
}
3.编写需要用到的注解Control(value 属性为http请求的uri)
package com.alex.framework;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@Retention(RetentionPolicy.RUNTIME)
public @interface Control {
String value();
}
4. 当我们访问的URL为 http://localhost:8088/project_name /control/customer/customer.do?method=list 时,
框架便会调用CustomerAction中list方法对请求进行处理
@Control("/control/customer/customer")
public class CustomerAction {
private CustomerService customerService = new CustomerService();
public void list(HttpServletRequest request,HttpServletResponse response) throws Exception{
PageInfo pageInfo = WebUtils.request2Bean(request, PageInfo.class);
PageBean pageBean = customerService.pageQuery(pageInfo);
request.setAttribute("pageBean",pageBean);
request.getRequestDispatcher("/WEB-INF/pages/customer/listCustomer.jsp").forward(request, response);
}