一個簡單的servlet框架

jopen 13年前發布 | 75K 次閱讀 Servlet Java開發

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