一個簡單的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框架提供了一個不錯的參照。
本文由用戶 jopen 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!