Java Web框架编写(一)

   “框架”一词对我们来说并不陌生,比如建房子的时候,经常都是先把基本的结构弄好,然后再往里面添加门窗之类的,基本的结构就是框架。

   我觉得框架和工具是不同的作用,简单来说,工具就是我们使用它来帮我们完成某些工作,比如输入法。而框架是将我们写的东西给它调用来完成工作,两者是有区别的。

   下面就简单的来完成一个WEB框架,基于注解方式完成。当然一个人的力量有限,所以很多问题没有考虑进去,比如上传文件,异常处理等等,只是一个非常简单初级的框架,也请大家多多提意见。

1. 配置servlet

   对于WEB应用来说,主要有两种形式来进行用户请求拦截操作,一个是Filter,一个是servlet,这里我采用servlet来完成,所以应该在web.xml中对servlet进行配置,并且要在tomcat启动的时候完成初始化工作。

 

<!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>dispater</servlet-name>
  	<servlet-class>com.jacksoft.web.servlet.DispatcherServlet</servlet-class>
  	<load-on-startup>1</load-on-startup>
  </servlet>
  
  <servlet-mapping>
  	<servlet-name>dispater</servlet-name>
  	<url-pattern>*.do</url-pattern>
  </servlet-mapping>
</web-app>

   这里我的servlet的名称是不是有点熟悉?嘿嘿

 

2. 导入jar

   我的项目采用maven来管理,所以需要导入servlet-api的包,在pom.xml里面进行配置,将该jar导入

 

 
  <properties>
  		<servlet-api.version>2.5</servlet-api.version>
  </properties>
  
  <dependencies>
 		<dependency>
 			  <groupId>javax.servlet</groupId>
			  <artifactId>servlet-api</artifactId>
			  <version>${servlet-api.version}</version>
 		</dependency>
  </dependencies>

 3. 编码

 

   这里先进行分析具体的流程

   当客户端发起请求的时候,比如:http://127.0.0.1:8080/web/test.do,其中web是项目的path,而test.do是访问的导航路径,所以我们需要根据这个test.do来查找对应的类,然后进行处理,最后再返回相应的页面得到结果,这就是一个大概的流程,当然这之间问题很多,都一一简单处理。

   (1)这里采用注解的形式,所以得先写个注解来说明某个类是专门处理请求的,Controller

package com.jacksoft.web.annotation;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 *  指定controller配置
 *  可以配置路径,默认为空
 * @author Jack
 *
 */
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface Controller {
	String value() default "";
}

    该注解名称是controller,用来定义处理请求的类

   (2)定义URL 访问路径对应注解

   

package com.jacksoft.web.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 UrlValue {
	/**
	 *  指定具体路径信息
	 * @return
	 */
	String url();
	
	Method method() default Method.GET;
}

   注意: 这里的method方法返回的是一个枚举类型数据,也就是我们提交请求对应的方法,所以还需要创建一个枚举,便于管理数据

   

package com.jacksoft.web.annotation;

public enum Method {

	GET, POST, DELETE, PUT
}

   简单定义几种请求方式。

   (3)编写DispatcherServlet 

     这里采用扫描的形式来查找那些类是标记了Controller注解的,这里就全部采用硬编码的形式来设置变量,由于初始扫描包的路径可能还要子包,所以需要递归的形式来读取所以的文件信息

   

private void initContext(){
		try{
			List<String> classList = new ArrayList<String>(); 
			String filePath = SCAN_PACKAGE.replace(".", "/");
			URL url = Thread.currentThread().getContextClassLoader().getResource(filePath);
			File f = new File(url.toURI());
			File[] files = f.listFiles();
			for(File file : files){
				listFile(file, classList, SCAN_PACKAGE);
			}
			
			for(String className : classList){
				System.out.println(className);
			}
			
		}catch(Exception ex){
			ex.printStackTrace();
		}
	}
	/**
	 *  递归查找文件,发现是文件夹,那么继续查找
	 * @param f
	 * @param classList
	 * @param filePath
	 */
	private void listFile(File f,List<String> classList,String filePath){
		if(f.isFile() && f.getName().endsWith(".class")){
			classList.add(filePath + "." + f.getName().replace(".class", ""));
		}else{
			File[] files = f.listFiles();
			String tmpFilePath = filePath + "." +f.getName();
			for(File file : files){
				listFile(file,classList,tmpFilePath);
			}
		}
	}

    首先将包名转换为路径,然后查找下面的文件,如果是目录,那么继续查找,同时路径名称也要变化。

    这样就找到了指定包下面的所以class名称,然后再通过反射来查找那些类是我们需要的。

    这里创建个Controller类

    

package com.jacksoft.web.controller;


import com.jacksoft.web.annotation.Controller;
import com.jacksoft.web.annotation.Method;
import com.jacksoft.web.annotation.UrlValue;


@Controller
public class TestController {


	
	
	
	@UrlValue(url="test.do",method=Method.GET)
	public String test(){
		System.out.println("------TestController.test正在处理----------------");
		System.out.println(req.getParameter("username"));
		return "test";
	}
}

   该类已经加了controller标记,同时也添加了urlvalue的标记,先来处理controller标记,这里我们希望将访问路径,Controller以及对应的方法关联起来,所以定义了一个java bean

package com.jacksoft.web.utils;

import java.lang.reflect.Method;

public class MethodUtils {

	private String path;
	
	private String requestMethod;
	
	private Object controller;
	
	private Method method;

	public String getPath() {
		return path;
	}

	public void setPath(String path) {
		this.path = path;
	}

	public String getRequestMethod() {
		return requestMethod;
	}

	public void setRequestMethod(String requestMethod) {
		this.requestMethod = requestMethod;
	}

	public Object getController() {
		return controller;
	}

	public void setController(Object controller) {
		this.controller = controller;
	}

	public Method getMethod() {
		return method;
	}

	public void setMethod(Method method) {
		this.method = method;
	}

	@Override
	public String toString() {
		return "path=" + path + "   requestMethod=" + requestMethod
				+ "   controller=" + controller + "   method=" + method;
	}
	
	
}

    这样我们查找到需要的数据时,可以对象化后再放入map中,其中key为我们访问的servletUrl,value为刚刚创建的MethodUtils.

private static Map<String,MethodUtils> controllerMap = null;

    在initContext方法中进行初始化操作

    

controllerMap = new HashMap<String, MethodUtils>();

   将查找了又controller和urlvalue标记的信息放入mao中,因此initContext方法如下:

private void initContext(){
		try{
			controllerMap = new HashMap<String, MethodUtils>();
			List<String> classList = new ArrayList<String>(); 
			String filePath = SCAN_PACKAGE.replace(".", "/");
			URL url = Thread.currentThread().getContextClassLoader().getResource(filePath);
			File f = new File(url.toURI());
			File[] files = f.listFiles();
			for(File file : files){
				listFile(file, classList, SCAN_PACKAGE);
			}
			for(String className : classList){
				Class<?> clazz = Class.forName(className);
				Object obj = clazz.newInstance();
				//判断是否加了controller的注解
				//只处理该注解,如果是,那么接着将其方法中的urlvalue注解拿到
				if(clazz.isAnnotationPresent(Controller.class)){
					Method[] methods = clazz.getMethods();
					for(Method method : methods){
						//判断方法是否加了UrlValue的注解,如果加了,那么获取对应的url和method方法
						if(method.isAnnotationPresent(UrlValue.class)){
							UrlValue value = method.getAnnotation(UrlValue.class);
							MethodUtils util = new MethodUtils();
							util.setPath(value.url());
							util.setRequestMethod(value.method().name());
							util.setMethod(method);
							util.setController(obj);
							controllerMap.put(value.url(), util);
						}
					}
				}
			}
			
		}catch(Exception ex){
			ex.printStackTrace();
		}
	}

   这样就把信息连在一起了,当用户访问test.do的时候,那么取该map中查找key=test.do,找出来之后,再通过反射就可以执行对应的方法,同时返回数据。该方法只需要在启动tomcat的时候执行,所以把他给init方法调用

	@Override
	public void init() throws ServletException {
		initContext();
	}

   接下来就是service方法了,将接受请求的路径到map中去获取,如果能拿得到数据,那么就可以正常调用,否则返回错误页面

  

	@Override
	protected void service(HttpServletRequest req, HttpServletResponse resp)
			throws ServletException, IOException {
		try{
			String servletUrl = req.getServletPath().substring(1);
			MethodUtils utils = controllerMap.get(servletUrl);
			if(utils == null){
				req.getRequestDispatcher("/WEB-INF/jsp/error/error404.jsp");
				return;
			}
			Method method = utils.getMethod();
			Object obj = method.invoke(utils.getController());
			req.getRequestDispatcher("/WEB-INF/jsp/"+obj+".jsp").forward(req, resp);
		}catch(Exception ex){
			ex.printStackTrace();
		}
	}

    到这里一个简单的框架就算完成了,启动tomcat来进行访问,访问test.do 能够正确返回test.jsp页面

 

4. 加上service注解

    请求分发给controller后,controller还需要调用service来处理具体的事情,这里也通过注解的形式来为字段赋值,设计原理就是Service注解的名称和controller字段注解的值一样,那么就进行赋值处理

   (1)定义Service注解

    

package com.jacksoft.web.annotation;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Service {

	String value() default "";
}

    该注解用于service上面

   (2)定义myservice,在controller中标记字段

   

package com.jacksoft.web.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.FIELD)
public @interface MyService {

	String value();
}

   (3)创建service

     既然用到service,那么就创建个,简单的打印一句话

   

package com.jacksoft.web.service;

import com.jacksoft.web.annotation.Service;

@Service("testService")
public class TestService {

	public void sayHello(){
		System.out.println("----------TestService.sayHello()--------------------------");
	}
}

    (4) 接下来接着处理DispaterServlet,将Service注解的类也找出来存储起来

       定义一个Map<String,Object>来存储数据

      

	private Map<String,Object> serviceMap = null;

    (5)在initContext方法处理controller之前,就需要处理service注解类

      

			/**
			 *  处理server注解
			 */
			for(String str : classList){
				Class<?> clazz = Class.forName(str);
				if(clazz.isAnnotationPresent(Service.class)){
					Service service = clazz.getAnnotation(Service.class);
					serviceMap.put(service.value(), clazz.newInstance());
				}
			}

 

   这样我们在处理controller时,如果发现字段上面有MyService注解,并且名称一样,那么我们就为其设值

   修改Controller,添加service字段

  

package com.jacksoft.web.controller;


import com.jacksoft.web.annotation.Controller;
import com.jacksoft.web.annotation.Method;
import com.jacksoft.web.annotation.MyService;
import com.jacksoft.web.annotation.UrlValue;
import com.jacksoft.web.service.TestService;

@Controller
public class TestController {

	@MyService("testService")
	private TestService service;
	
	@UrlValue(url="test.do",method=Method.GET)
	public String test(){
		System.out.println("------TestController.test正在处理----------------");
		service.sayHello();
		return "test";
	}
}

   (6)处理Service注解

    

					// 处理service注解
					Field[] fields = clazz.getDeclaredFields();
					for(Field field : fields){
						if(field.isAnnotationPresent(MyService.class)){
							MyService service = field.getAnnotation(MyService.class);
							String serverName = service.value();
							Object serverParam = serviceMap.get(serverName);
							if(serverParam != null){
								//为标记有注解 且名字相同的字段赋值
								field.setAccessible(true);
								field.set(obj, serverParam);
							}
						}
					}

    将Controller中标记有MyService注解的字段,看名称是否在map中存在,如果存在,那么就赋值

   (7) 测试

   其他的就不修改了,启动tomcat,进行访问test.do,调用service处理也能工作了。

 

5. 封装参数

   在分发到controller对应方法时,还需要设置参数,在service中处理的时候,通过method对象来设置参数信息,最后通过反射将参数传递过去。

		Method method = utils.getMethod();
			Class<?>[] parameterClass = method.getParameterTypes();
			Object[] parameter = new Object[parameterClass.length];
			int i = 0;
			for(Class<?> clazz : parameterClass){
				if(clazz.equals(HttpServletRequest.class)){
					parameter[i] = req; 
				}else if(clazz.equals(HttpServletResponse.class)){
					parameter[i] = resp;
				}
				i++;
			}
			Object page = utils.getMethod().invoke(utils.getController(), parameter);

 这里简单的写两种类型的参数,当然参数很多,可以通过javabean的特性来赋值。

 

到这里一个简单的框架就差不多了,接下来就是不断的去修改,添加,尽可能的完美。

附上DispaterServlet全部代码:

package com.jacksoft.web.servlet;

import java.io.File;
import java.io.IOException;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.net.URL;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import com.jacksoft.web.annotation.Controller;
import com.jacksoft.web.annotation.MyService;
import com.jacksoft.web.annotation.Service;
import com.jacksoft.web.annotation.UrlValue;
import com.jacksoft.web.utils.MethodUtils;

public class DispatcherServlet extends HttpServlet {

	/**
	 * 
	 */
	private static final long serialVersionUID = 1L;
	
	private static final String SCAN_PACKAGE = "com.jacksoft.web";
	
	
	private static Map<String,MethodUtils> controllerMap = null;
	
	private Map<String,Object> serviceMap = null;
	
	@Override
	public void init() throws ServletException {
		initContext();
	}
	
	
	private void initContext(){
		try{
			controllerMap = new HashMap<String, MethodUtils>();
			serviceMap = new HashMap<String, Object>();
			List<String> classList = new ArrayList<String>(); 
			String filePath = SCAN_PACKAGE.replace(".", "/");
			URL url = Thread.currentThread().getContextClassLoader().getResource(filePath);
			File f = new File(url.toURI());
			File[] files = f.listFiles();
			for(File file : files){
				listFile(file, classList, SCAN_PACKAGE);
			}
			
			/**
			 *  处理server注解
			 */
			for(String str : classList){
				Class<?> clazz = Class.forName(str);
				if(clazz.isAnnotationPresent(Service.class)){
					Service service = clazz.getAnnotation(Service.class);
					serviceMap.put(service.value(), clazz.newInstance());
				}
			}
			
			
			for(String className : classList){
				Class<?> clazz = Class.forName(className);
				//判断是否加了controller的注解
				//只处理该注解,如果是,那么接着将其方法中的urlvalue注解拿到
				if(clazz.isAnnotationPresent(Controller.class)){
					Object obj = clazz.newInstance();
					
					// 处理service注解
					Field[] fields = clazz.getDeclaredFields();
					for(Field field : fields){
						if(field.isAnnotationPresent(MyService.class)){
							MyService service = field.getAnnotation(MyService.class);
							String serverName = service.value();
							Object serverParam = serviceMap.get(serverName);
							if(serverParam != null){
								//为标记有注解 且名字相同的字段赋值
								field.setAccessible(true);
								field.set(obj, serverParam);
							}
						}
					}
					
					
					
					
					Method[] methods = clazz.getMethods();
					for(Method method : methods){
						//判断方法是否加了UrlValue的注解,如果加了,那么获取对应的url和method方法
						if(method.isAnnotationPresent(UrlValue.class)){
							UrlValue value = method.getAnnotation(UrlValue.class);
							MethodUtils util = new MethodUtils();
							util.setPath(value.url());
							util.setRequestMethod(value.method().name());
							util.setMethod(method);
							util.setController(obj);
							controllerMap.put(value.url(), util);
						}
					}
				}
			}
			
		}catch(Exception ex){
			ex.printStackTrace();
		}
	}
	/**
	 *  递归查找文件,发现是文件夹,那么继续查找
	 * @param f
	 * @param classList
	 * @param filePath
	 */
	private void listFile(File f,List<String> classList,String filePath){
		if(f.isFile() && f.getName().endsWith(".class")){
			classList.add(filePath + "." + f.getName().replace(".class", ""));
		}else{
			File[] files = f.listFiles();
			String tmpFilePath = filePath + "." +f.getName();
			for(File file : files){
				listFile(file,classList,tmpFilePath);
			}
		}
	}

	@Override
	protected void service(HttpServletRequest req, HttpServletResponse resp)
			throws ServletException, IOException {
		try{
			String servletUrl = req.getServletPath().substring(1);
			MethodUtils utils = controllerMap.get(servletUrl);
			if(utils == null){
				req.getRequestDispatcher("/WEB-INF/jsp/error/error404.jsp");
				return;
			}
			Method method = utils.getMethod();
			Object obj = method.invoke(utils.getController());
			req.getRequestDispatcher("/WEB-INF/jsp/"+obj+".jsp").forward(req, resp);
		}catch(Exception ex){
			ex.printStackTrace();
		}
	}
	
	

}

 

 

  

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值