搭载一个简单的servlet框架

本人一直想研究下servlet框架的原理,但是研究起来却格外的困难,因为每个web框架都非常的复杂。无意中在网上找到个简单的,好像是simplewebframework的作者写的,觉得不错,拿来稍微改了下,一个非常的简单的servlet就形成了。

再说servlet框架之前,我想说下思路,现在的主流MVC框架大都是继承一个HttpServlet,然后再实现的它的service方法。因为直接实现的是service方法,所以就无所谓什么doGet或doPost方法了。

我们平时使用的doGet 或doPost方法都是通过service来调用的,所以service才是HttpService最重要的一个方法,用来控制全局。

除此这外,我也看见有些框架居然直接实现Filter接口,以此来实现一个WEB框架,这样也是可以的。因为凡是能在HttpServlet中能完成的操作都可以在Filter的实现中完成,而且Filter更加的强大。不过实现起来也更加的复杂(有一个简单的,叫spark)。

下面再讲讲研究的这个简单的servlet框架,主要由两个类组成,一个就是最重要的控制转发类:

public class DispatcherServlet extends HttpServlet {
	private static final long serialVersionUID = 1L;

	@Override
	protected void doGet(HttpServletRequest request,
			HttpServletResponse response) throws ServletException, IOException {
		request.setCharacterEncoding("UTF-8");
		
		String uri = request.getRequestURI();
		//去掉工程路径与.do后缀名
		uri = uri.substring(request.getContextPath().length(), uri.length() - 3);
		
		@SuppressWarnings("unchecked")
		Map<String,Object> map = (Map<String,Object>) this.getServletContext().getAttribute("mapPath");
		
		if (map.containsKey(uri)) {
			// 通过http请求uri获得相对应的Action对象
			Object obj = map.get(uri);
			// 获得http请求的方法名
			String methodName = request.getParameter("method");
			// 如果请求的方法null,则默认调用Action对象中的index方法
			if (methodName == null) {
				methodName = "index";
			}
			Method method = null;
			try {
				// 通过反射获得要执行的方法对象
				method = obj.getClass().getMethod(methodName,
						HttpServletRequest.class, HttpServletResponse.class);
			} catch (Exception e) {
				throw new RuntimeException("在" + obj.getClass().getName()
						+ "上找不到与" + methodName + "相对应的方法!!!");
			}
			try {
				// 执行Controller对象中的方法
				method.invoke(obj, request, response);
			} catch (Exception e) {
				e.printStackTrace();
			}
		}
	}
	@Override
	protected void doPost(HttpServletRequest request,
			HttpServletResponse response) throws ServletException, IOException {
		doGet(request, response);
	}
}
这个类的思路是通过请求路径找到控制器对应的实例,然后再调用实例里面的具体方法。而这个实例的生成则是在侦听器中完成的,用侦听器来生成实例有个好处,侦听器是在启动web服务器时自动加载的,这样的话,一开始就服务器启动时就把所有控制器实例化,然后存在servletContext中。当然如果工程过大或者控制器过多的话,这样服务

器的内存会被耗掉不少,不过既然是简单的servlet就不需要考虑那么多了。


public class LoadServletListener implements ServletContextListener{
	// Map中的key用来存放URI,value用来存放URI相对应的Action对象(Action指处理各类请求的控制器)
	private static Map<String, Object> map = new HashMap<String, Object>();
	@Override
	public void contextDestroyed(ServletContextEvent event) {
		if(map!=null)
			map=null;
	}
	@Override
	public void contextInitialized(ServletContextEvent event) {
		ServletContext context = event.getServletContext();
		String servletPackage = context.getInitParameter("servletPackage");
		String classPath = context.getRealPath(
				"/WEB-INF/classes/"+servletPackage.replace('.',File.separatorChar));
		scanClassPath(new File(classPath));
		context.setAttribute("mapPath", map);
		System.out.println(map);
	}
	
	/*
	 * 扫描类路径所有类文件,如果类文件含有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(
							this.getClass().getClassLoader());
					Class<?> clazz = myClassLoader.load(path);
					Controller controller = (Controller) clazz.getAnnotation(Controller.class);
					if (controller != null) {
						String uri = controller.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;
				}
			}
		}
	}
}
这就是那个实例化控制器类的侦听器,实例化类这个过程有点复杂,先要通过路径找到相应的class文件,再通过ClassLoader加载这个class文件的内容从而生成一个Class类,再通过这个Class类生成相应的实例。

然后再将controller里面的url与这个实例对应起来,存放在一个map里面,再将这个map放入servletContext里面。

这个Controller注解非常的简单:

@Retention(RetentionPolicy.RUNTIME)
public @interface Controller {
	String value();
}
其实个人觉得这个Controller可要可不要,只能加上更好一些,下面再把基本的web.xml配置贴上来:
        <context-param>
		<param-name>servletPackage</param-name>
		<param-value>com.controller</param-value>
	</context-param>
	<listener>
		<listener-class>com.zit.LoadServletListener</listener-class>
	</listener>
	<servlet>
		<servlet-name>DispatcherServlet</servlet-name>
		<servlet-class>com.zit.DispatcherServlet</servlet-class>
	</servlet>
	<servlet-mapping>
		<servlet-name>DispatcherServlet</servlet-name>
		<url-pattern>*.do</url-pattern>
	</servlet-mapping>

servletPackage这个参数的意思是指定控制器对应的包,这样的话listener扫描文件效率会更高一些。

这样东西东西都装备好了以后就可以写个实际的控制器了:

@Controller("/hello")
public class HelloController {

	public void index(HttpServletRequest request, HttpServletResponse response)
			throws Exception {

		request.getRequestDispatcher("/pages/hello.jsp")
				.forward(request, response);
	}
}
这样一个简单的servlet框架也就形成 了,至少比直接写servlet要稍微简单些,也为以后研究其它的servlet框架提供了一个不错的参照。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值