Сервлеты и Reflection

Совсем недавно, в статье Сервлеты — маленький трюк с Reflection, разбирался прием, с помощью которого можно получить url вида:
host/servletName/methodName.

Если пойти дальше, то с помощью Reflection можно реализовать конструкцию следующего вида:
Controllers


Где, у нас в проекте есть несколько классов контроллеров, и у каждого из них свои действия. А самое главное, в методы контроллеров мы можем передавать параметры из URI. Например:
host/servletName/projects/add?title=hello



Контроллеры


Сначала разберёмся с сервлетом. У нас есть все тот же класс BaseServlet, наследуемый от HttpServlet.

package projectManagment;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import projectManagment.controllers.Controller;

public class BaseServlet extends HttpServlet {

    private ControllerFactory factory;
    
    public BaseServlet () {
        super();
        factory = new ControllerFactory();
    }
    
    @Override
    protected void service(HttpServletRequest request, HttpServletResponse response) {
        try {
            String name = getControllerName(request);
            if (name == null)
                name = "index";
            Controller controller = factory.create(name);
            Controller.setHttp(request, response, getServletContext());   
            String method = getMethodName(request); 
            if (method == null)
                method = "index";
            callMethod(controller, method, request, response);                   
        } catch (Exception e) {
            System.out.println("Error: " + e.getMessage());
            e.printStackTrace();
        }
    }
    
    public String getControllerName (HttpServletRequest request) {
        String path = request.getRequestURI().substring(request.getContextPath().length()); 
        if (path == null || "".equals(path) || "/".equals(path))
            return null;

        String controller = path.substring(1, path.lastIndexOf("/"));
        if (controller != null && !"".equals(controller))
            return controller;
        
        return null;
    }
    
    private String getMethodName (HttpServletRequest request) {
        String method = request.getRequestURI().substring(request.getContextPath().length());
        if (method != null && !"".equals(method) && !"/".equals(method))    
            return method.substring(method.lastIndexOf("/") + 1, method.length() );
        
        return null;
    }
      
    private void callMethod (Controller controller, String methodName,
            HttpServletRequest request, HttpServletResponse response) throws 
            IllegalArgumentException, SecurityException,
            IllegalAccessException,    InvocationTargetException,
            NoSuchMethodException {

        for (Method method : controller.getClass().getMethods()) {
            if (method.getName().equalsIgnoreCase(methodName)) {
                 controller.getClass().getMethod(methodName, method.getParameterTypes()).invoke(controller);
            } 
        }         
    }
}


Самое интересное происходит в методе service. Сначала мы получаем имя контроллера с помощью вспомогательного метода getControllerName, где парсится URI. После мы используем ControllerFactory – фабрику контроллеров, которая создает объект нужного нам класса контроллера. Код ControllerFactory очень прост:

package projectManagment;
import java.util.HashMap;
import java.util.Map;
import projectManagment.controllers.*;

public class ControllerFactory {
    public ControllerFactory() {
        map = defaultMap();
    }
    
    public Controller create (String controllerName) {
        Class ControllerClass = (Class) map.get(controllerName);
        if (ControllerClass == null) 
            throw new RuntimeException(getClass() + " was unable to find an controller named '" + controllerName + "'.");
        Controller controllerInstance = null;
        try {
            controllerInstance = (Controller) ControllerClass.newInstance();
        } catch (Exception e) {
            e.printStackTrace();
        }
        return controllerInstance;
    }
        
    protected Map<String, Class<?>> defaultMap() {
        Map<String, Class<?>> map = new HashMap<String, Class<?>>();    
        map.put("index", Index.class);
        return map;
    }

    protected Map<String, Class<?>> map;
}


Остается разобраться с вызовом метода. Для начала получаем его имя с помощью getMethodName, а дальше передаем необходимые параметры в callMethod. Тут происходит поиск подходящего метода в контроллере, а после его вызов.

Все контроллеры, которые мы хотим использовать должны быть прописаны в методе defaultMap у ControllerFactory.

Перейдем к базовому классу контроллера.

package projectManagment.controllers;

import javax.servlet.ServletContext;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

public class Controller {
    static protected HttpServletResponse response;
    static protected HttpServletRequest request;
    static protected ServletContext context;
    
    public Controller () {
        request = null;
        response = null;
        context = null;
    }

    static public void setHttp (HttpServletRequest req, HttpServletResponse res, ServletContext con) {
        request     = req;
        response     = res;
        context     = con;
    }
}


Метод setHttp позволяет нам научить разговаривать наш контроллер. Сюда же можно добавить методы для авторизации, редиректа и т.д.

Теперь сделаем тестовый контроллер под именем Index. В нем будут два метода index и show.
package projectManagment.controllers;

public class Index extends Controller {
    public static void index () {
        System.out.println("Index method");
    }
    
    public static void show () {
        String value = request.getParameter("name");
        System.out.println("Hello " + value);
    }
}


Не забываем настроить mapping в web.xml следующим образом:
<?xml version="1.0" encoding="UTF-8"?>
<web-app version="3.0" xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd">
    <servlet>
        <servlet-name>Demo</servlet-name>
        <servlet-class>projectManagment.BaseServlet</servlet-class>
    </servlet>
  
    <servlet-mapping>
        <servlet-name>Demo</servlet-name>
         <url-pattern>/</url-pattern>
    </servlet-mapping>
</web-app>


Протестируем наш сервлет. По запросу /host/demo/ мы увидим в консоли строку “Index method”, а по запросу /host/demo/index/show?name=Anton — “Hello Anton”.

Передача параметров в метод


Для парсинга параметров из URI в методах приходится использовать конструкцию вида:

String strValue = request.getParametr("intParamName");
Integer value = Intger.parseInt(strValue);

Совершать такие простые действия по многу раз довольно нудно, да и красота кода страдает. Проблему парсинга параметров можно решить, передовая их как параметры к методу. Для этого нужно использовать аннотации.


package projectManagment.controllers;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;

@Retention(RetentionPolicy.RUNTIME)
public  @interface Param {
    String name();
}


Метод show теперь выглядит следующим образом:

public static void show (@Param(name = "name") String name, @Param(name = "age") Integer age) {
        System.out.println("Hello " + name + ", you are " + age + " years old");
    }


Это еще не все, необходимо в методе callMethod передавать параметры из URI в вызываемый метод.

private void callMethod(Controller controller, String methodName,
            HttpServletRequest request, HttpServletResponse response) throws
            NoSuchMethodException, RuntimeException,
            SecurityException, IllegalAccessException,
            InvocationTargetException {                
        for (Method method : controller.getClass().getMethods()) {
            if (method.getName().equalsIgnoreCase(methodName)) {
                Object[] params = new Object[method.getParameterTypes().length];
                Annotation[][] parameterAnnotations = method.getParameterAnnotations();
                Class[] parameterTypes = method.getParameterTypes();
                int i = 0;
                for(Annotation[] annotations : parameterAnnotations){
                    Class parameterType = parameterTypes[i];                
                    for(Annotation annotation : annotations){
                        if(annotation instanceof Param){
                               Param myAnnotation = (Param) annotation;
                               Object value = null;
                            if (parameterType.getSimpleName().equals("Integer")) {
                                try {
                                value =  Integer.parseInt(request.getParameter(myAnnotation.name()));
                                } catch (NumberFormatException e) {
                                    value = null;
                                }
                            }
                            else {
                                value = request.getParameter(myAnnotation.name());
                            }
                            params[i++] = value;
                            
                              System.out.println("param: " + parameterType.getSimpleName());
                              System.out.println("name : " + myAnnotation.name());
                              System.out.println("value: " + value);
                        }
                     }
                }
                controller.getClass().getMethod(method.getName(), method.getParameterTypes()).invoke(controller, params);
            }     
        }             
    }


Теперь на запрос /host/demo/index/show?name=Anton&age=20, консоль выводит “Hello Anton, you are 20 years old”.

Комментарии 7

  • НЛО прилетело и опубликовало эту надпись здесь
      0
      Дело в том, что я изначально разрабатывал проект, где исключалось использование фреймворков. А до этого использовал play framework. Там и подсмотрел данную конструкцию.
      Когда начала капать в сторону реализации, ничего путного в интернете не нашел. Пришлось самому выдумывать.
        0
        *начел
          0
          Кхм. Начал
        • НЛО прилетело и опубликовало эту надпись здесь
            0
            Нет, конечно про библиотеку было ни слова. Про RESTful я просто был не в курсе. Сначала я реализовал простую Model 2. Проект разрастался, становился сложноуправляемым. Приходилось проводить рефакторинг. И так несколько раз. В итоге пришел к таким вещам.
      • НЛО прилетело и опубликовало эту надпись здесь

        Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

        Самое читаемое