javaweb定制化

感觉改造的历程还是非常有意思的

第一版

以模块为单位放到一个servlet中去,然后在servlet中创建对应的方法,并以method来标注调用的是哪个方法;

@WebServlet(urlPatterns="/linkMan")
public class LinkManServlet extends HttpServlet {

    @Override
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        try {
            //1.接收参数:action--要执行的方法名称
            String action = request.getParameter("action");
            //2.反射调用名称为action的方法
            Class clazz = this.getClass();
            Method method = clazz.getMethod(action, HttpServletRequest.class, HttpServletResponse.class);
            method.invoke(this, request, response);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        this.doPost(request, response);
    }
    
    public void queryAll(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        //....
    }
    
    public void add(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        //....
    }
}

缺陷:每个模块都会大量的来重用doGet/doPost方法,代码冗余。

解决方式:将多个模块之间的共同方法:doPost/doGet方法抽取到父类中去

第二版

解决模块化Servlet的缺点,抽取BaseServlet。每个模块的类,不要再继承HttpServlet,而是继承BaseServlet

public class BaseServlet extends HttpServlet {

    @Override
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        try {
            //1.接收参数:action--要执行的方法名称
            String action = request.getParameter("action");
            //2.反射调用名称为action的方法
            Class clazz = this.getClass();
            Method method = clazz.getMethod(action, HttpServletRequest.class, HttpServletResponse.class);
            method.invoke(this, request, response);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        this.doPost(request, response);
    }
}

然后每个模块创建对应的servlet来继承这个BaseServlet类

@WebServlet(urlPatterns="/linkMan")
public class LinkManServlet extends BaseServlet {
    
    public void queryAll(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        //....
    }
    
    public void add(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        //....
    }
}

上面的问题也很显然,多个模块之间的servlet类之间仍然存在着耦合性。所以还需要来进行改进。

BaseServlet的问题
  • 核心控制器:BaseServlet。
    • 与业务功能无关,是请求请求,调用业务功能方法的
  • 业务控制器:UserServlet和LinkManServlet...
    • 处理业务功能逻辑的
  • 问题是:这种方式不方便,并且耦合性比较高
    1. 无论什么功能,客户端都必须要传参一个action,指定方法名称;否则核心控制器无法找到要执行的方法。
    2. 业务控制器必须要继承BaseServlet,因为解析请求路径、调用对应方法的代码在BaseServlet方法中。
  • 理想的方式是:
    • 业务控制器就是普通的Java类,不需要继承任何父类,降低耦合性
    • 客户端不需要通过传参,核心控制器就能够区分请求的是哪个方法
解决方案
  • 给业务控制器里每个方法,都使用注解配置一个字符串值:虚拟访问路径(映射路径)
  • 客户端直接发请求,请求信息中有这个虚拟访问路径(映射路径),不需要传递任何参数
  • 在核心控制器里:拦截一切.do结尾的请求
    • 解析请求路径,得到requestPath
    • 根据requestPath,找到对应的方法Method
    • 调用执行这个方法

看一下对应的版本

V1版本

定义一个注解类Controller(暂时不用)和RequestMapping

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Controller {
    String value();
}
@Target(value={ElementType.METHOD,ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface RequestMapping {
    String value();
}

对应的servlet的代码:

@WebServlet(name = "DispatcherServlet", urlPatterns = "*.do")
public class DispatcherServlet extends HttpServlet {
    
    /**
     * 
     * @param request
     * @param response
     * @throws ServletException
     * @throws IOException
     */
    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        String requestURI = request.getRequestURI();
        String contextPath = request.getContextPath();
        int length = contextPath.length();
        int var2 = requestURI.lastIndexOf(".do");
        String methodPath = requestURI.substring(length, var2);
        // 获取得到对应的路径之后,然后来查找整个项目中的路径即可
        // System.out.println("解析出来的路径是:"+methodPath);
        // 在整个项目中来进行查找拼接对应的路径,获取得到所有的class文件,这些文件中保存了需要调用的方法和对应的路径
        List<Class<?>> classsFromPackage = ClassScannerUtils.getClasssFromPackage("com.guang.controller");
        for (Class<?> clazz : classsFromPackage) {
            Method[] methods = clazz.getMethods();
            for (Method method : methods) {
                // 如果有注解,获取得到注解上的值(这里保存的就是对应的路径,所以这里需要来对这里的路径来做一个处理方式)
                boolean annotationPresent = method.isAnnotationPresent(RequestMapping.class);
                if (annotationPresent){
                    RequestMapping annotation = method.getAnnotation(RequestMapping.class);
                    // 这里应该来做一个缓存思想
                    String value = annotation.value();
                    if (Objects.equals(methodPath,value)){
                        try {
                            // 创建类的对象进去
                            Object newInstance = clazz.newInstance();
                            method.invoke(newInstance,request,response);
                            return; // 找到了就将方法来进行关闭
                        } catch (Exception e) {
                            e.printStackTrace();
                        }
                    }
                }
            }
        }
    }

    @Override
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        this.doGet(request, response);
    }
}

弊端:

1、但是这样子来进行操作存在着一个弊端,就是每一次来进行查找的时候,都会扫描全部,所以可能会影响效率。所以应该做一个缓存思想来进行操作

2、不应该将路径写在代码中来,而是应该写在配置文件中来,在修改完成配置文件之后,直接从配置文件中加载即可。

3、servlet默认的是从第一次访问的时候来进行加载,所以我们可以修改在启动的时候来进行加载。

V2版本

public class DispatcherServlet extends HttpServlet {

    /**
     * key为path路径
     * value是保存的对象
     */
    private static Map<String,MethodBean> map = new ConcurrentHashMap<>();

   

    /**
     * 不应该来继承这个,而是应该继承另外一个Init重载的函数
     * @throws ServletException
     */
    @Override
    public void init() throws ServletException {
        super.init();
        try {
            // 这个配置不应该在这里来进行配置
            List<Class<?>> classsFromPackage = ClassScannerUtils.getClasssFromPackage("com.guang.controller");
            for (Class<?> clazz : classsFromPackage) {
                boolean annotationPresent1 = clazz.isAnnotationPresent(Controller.class);
                if (annotationPresent1){
                    Controller controllerAnnotation = clazz.getAnnotation(Controller.class);
                    String classPath = controllerAnnotation.value();
                    Method[] methods = clazz.getMethods();
                    for (Method method : methods) {
                        boolean annotationPresent2 = method.isAnnotationPresent(RequestMapping.class);
                        if (annotationPresent2){
                            MethodBean methodBean = new MethodBean();
                            methodBean.setMethod(method);
                            methodBean.setObject(clazz.newInstance());
                            RequestMapping requestMappingAnnotation = method.getAnnotation(RequestMapping.class);
                            String requestMappingPath = requestMappingAnnotation.value();
                            // 这里要以分割线来进行分隔即可
                            String requestPath = classPath+requestMappingPath;
                            // 在获取的时候应该是:method.invoke(类的实例对象,参数);
                            map.put(requestPath,methodBean);
                        }
                    }
                }
            }
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        }
    }

    /**
     * 但是这样子来进行操作存在着一个弊端,就是每一次来进行查找的时候,都会扫描全部,所以可能会影响效率。所以应该做一个缓存思想来进行操作
     * @param request
     * @param response
     * @throws ServletException
     * @throws IOException
     */
    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        String requestURI = request.getRequestURI();
        String contextPath = request.getContextPath();
        int length = contextPath.length();
        int var2 = requestURI.lastIndexOf(".do");
        String methodPath = requestURI.substring(length, var2);
        MethodBean methodBean = map.get(methodPath);
        Method method = methodBean.getMethod();
        Object object = methodBean.getObject();
        try {
            method.invoke(object,request,response);
        } catch (Exception e) {
            System.out.println("调用异常有误!!!!");
            e.printStackTrace();
        }

    }

    @Override
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        this.doGet(request, response);
    }
}

但是这样子来写依然无法避免上面出现的问题:

1、包名不应该直接写在代码中去,如果在项目进行运行的时候,进行修改的话非常之不方便。

关于这个比较容易解决,我们可以访问父类中的init重载方法

V3版本

public class DispatcherServlet extends HttpServlet {

    /**
     * key为path路径
     * value是保存的对象
     */
    private static Map<String,MethodBean> map = new ConcurrentHashMap<>();

    @Override
    public void init(ServletConfig config) throws ServletException {
        super.init(config);
        try {
            String scanpackage = config.getInitParameter("scanpackage");
            // 包的配置应该来写到配置文件中来,这里不需要来写对应的字节码文件中来,因为需要进行修改的时候不好进行修改
            List<Class<?>> classsFromPackage = ClassScannerUtils.getClasssFromPackage(scanpackage);
            for (Class<?> clazz : classsFromPackage) {
                boolean annotationPresent1 = clazz.isAnnotationPresent(Controller.class);
                if (annotationPresent1){
                    Controller controllerAnnotation = clazz.getAnnotation(Controller.class);
                    String classPath = controllerAnnotation.value();
                    Method[] methods = clazz.getMethods();
                    for (Method method : methods) {
                        boolean annotationPresent2 = method.isAnnotationPresent(RequestMapping.class);
                        if (annotationPresent2){
                            MethodBean methodBean = new MethodBean();
                            methodBean.setMethod(method);
                            methodBean.setObject(clazz.newInstance());
                            RequestMapping requestMappingAnnotation = method.getAnnotation(RequestMapping.class);
                            String requestMappingPath = requestMappingAnnotation.value();
                            // 这里要以分割线来进行分隔即可
                            String requestPath = classPath+requestMappingPath;
                            // 在获取的时候应该是:method.invoke(类的实例对象,参数);
                            map.put(requestPath,methodBean);
                        }
                    }
                }
            }
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        }
    }


    /**
     * 但是这样子来进行操作存在着一个弊端,就是每一次来进行查找的时候,都会扫描全部,所以可能会影响效率。所以应该做一个缓存思想来进行操作
     * @param request
     * @param response
     * @throws ServletException
     * @throws IOException
     */
    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        String requestURI = request.getRequestURI();
        String contextPath = request.getContextPath();
        int length = contextPath.length();
        int var2 = requestURI.lastIndexOf(".do");
        String methodPath = requestURI.substring(length, var2);
        MethodBean methodBean = map.get(methodPath);
        Method method = methodBean.getMethod();
        Object object = methodBean.getObject();
        try {
            method.invoke(object,request,response);
        } catch (Exception e) {
            System.out.println("调用异常有误!!!!");
            e.printStackTrace();
        }

    }

    @Override
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        this.doGet(request, response);
    }
}

对应的包应该配置在xml中:

    <servlet>
        <servlet-name>dispatcherservlet</servlet-name>
        <servlet-class>com.guang.servlet.DispatcherServlet</servlet-class>
        <init-param>
            <param-name>scanpackage</param-name>
            <!--这里的配置文件信息应该写在这里!表示的是扫描哪些文件路径-->
            <param-value>com.guang.controller</param-value>
        </init-param>
        <load-on-startup>1</load-on-startup>
    </servlet>
    <servlet-mapping>
        <servlet-name>dispatcherservlet</servlet-name>
        <url-pattern>*.do</url-pattern>
    </servlet-mapping>

如果有后续,还会来进行改进。

posted @ 2022-02-16 22:54  写的代码很烂  阅读(64)  评论(0编辑  收藏  举报