자바 의 springMvc 프레임 워 크 간단 실현

머리말
본 논문 에서 블 로 거들 은 servlet 에서 contrller 층 까지 간단 한 구 조 를 실현 했다.이 프레임 워 크 를 통 해 우 리 는 spring 처럼 다음 과 같은 기본 주 해 를 사용 할 수 있 습 니 다.
  • @XxgController
  • @XxgRequestMapping
  • @XxgParam
  • @XxgRequestBody

  • 본문 을 보기 전에 다음 과 같은 내용 을 먼저 알 아야 할 지도 모른다.
  • BeanUtils
  • ObjectMapper
  • Servlet 지식
  • 사고방식: 차단기 가 루트 배 포 를 실현 한다.주 해 를 이용 하 다
    생각:
  • 차단 기 는 servlet 전에 모든 요청 경 로 를 차단 할 수 있 습 니 다
  • 주석 에서 요청 경로 와 일치 하 는 방법 을 찾 을 수 있 습 니 다
  • 그리고 req, resp 를 이 방법 으로 전송
  • 질문:
    차단 기 는 어떻게 이 주 해 를 사용 하 는 방법 을 찾 았 습 니까?패키지 스 캔?어떻게 실현 합 니까?
    분석:
    패키지 스 캔 은 IO 흐름 과 관련 되 며, File 류 는 아래 의 모든 파일 을 재 귀적 으로 조회 할 수 있 습 니 다.
    필터 링 가능:
  • 접미사 이름 이 'class' 인 파일 만 있 으 면 className (패키지 경로 포함)
  • 을 가 져 옵 니 다.
  • 반 사 를 통 해 이 종 류 를 얻 고 지 정 된 주해 가 있 는 지 판단 하여 다시 여과
  • 이렇게 해서 차단기 에서 요청 경 로 를 차단 하면 우 리 는 이 방법 을 일치 시 키 고 호출 할 수 있다.
    게 으 름 피우다
    MVC 디자인 모델 로 인해 저 희 는 api 인 터 페 이 스 를 같은 가방 에 두 기 때문에 저 희 는 스 캔 가방 을 직접 지정 할 수 있 습 니 다. 다른 가방 은 상관 하지 않 습 니 다.
    스캐너 1.0 버 전의 실현
    public class FileScanner {
     private final String packetUrl = "com.dbc.review.controller";
     private final ClassLoader classLoader = FileScanner.class.getClassLoader();
     private List allClazz = new ArrayList<>(10); //            
    ​
     public List getAllClazz(){
       return this.allClazz;
     }
    ​
     public String getPacketUrl(){
       return this.packetUrl;
     }
    ​
     //              
     //      ,     class,   class          class
     public void loadAllClass(String packetUrl) throws Exception{
         String url = packetUrl.replace(".","/");
         URL resource = classLoader.getResource(url);
         if (resource == null) {
            return;
        }
         String path = resource.getPath();
         File file = new File(URLDecoder.decode(path, "UTF-8"));
         if (!file.exists()) {
            return;
         }
         if (file.isDirectory()){
         File[] files = file.listFiles();
         if (files == null) {
            return;
         }
         for (File f : files) {
             String classname = f.getName().substring(0, f.getName().lastIndexOf("."));
             if (f.isDirectory()) {
                loadAllClass(packetUrl + "." + classname);
             }
             if (f.isFile() && f.getName().endsWith(".class")) {
                 Class clazz = Class.forName(packetUrl + "." + classname);
                 dealClass( clazz);
             }
        }
         }
     }
    ​
     private void dealClass(Class clazz) {
     if ((clazz.isAnnotationPresent(Controller.class))) {
     allClazz.add(clazz);
     }
     }
    ​
     //        ,                   
     public boolean invoke(String url,String requestMethod) {
         for (Class clazz : allClazz){
         Controller controller = (Controller) clazz.getAnnotation(Controller.class);
         Method[] methods = clazz.getDeclaredMethods();
         for (Method method : methods) {
             RequestMapping requestMapping = method.getDeclaredAnnotation(RequestMapping.class);
             if (requestMapping == null) {
             continue;
             }
             for (String m : requestMapping.methods()) {
             m = m.toUpperCase();
             if (!m.toUpperCase().equals(requestMethod.toUpperCase())) {
             continue;
             }
             StringBuilder sb = new StringBuilder();
             String urlItem = sb.append(controller.url()).append(requestMapping.url()).toString();
             if (urlItem.equals(url)) {
             //         api     
             try {
            //                            method.getGenericParameterTypes() //                     
             method.invoke(clazz.newInstance());
             return true;
             } catch (InstantiationException | IllegalAccessException | InvocationTargetException e) {
             e.printStackTrace();
             return false;
             }
         }
         }
         }
         }
         return false;
     }
    ​
     @Test
     public void test() throws Exception {
         // 1.  Filter          
         FileScanner fileScanner = new FileScanner();
         // 2.     
         fileScanner.loadAllClass(fileScanner.getPacketUrl());
         // 3.       ,        
         //         post   /test/post      ,   false
         //       true
         fileScanner.invoke("/test/post","post");
         // 4.     ,  false,   405      。
        ​
         //    :  controller   ,     
         //           :  method       ,      ,       
         }
    }

    TestController
    @Controller(url = "/test")
    public class TestController {
    ​
     @RequestMapping(url = "/get",methods = "GET")
     public void get(){
     System.out.println(111);
     }
     @RequestMapping(url = "/post",methods = {"POST","get"})
     public void post(){
     System.out.println(22);
     }
    ​
     public void test(HttpServletRequest req, HttpServletResponse res){
     System.out.println(req.getPathInfo());
     }
    }

    스 캔 클래스 2.0 버 전
    1.0 버 전 을 통 해 우 리 는 스 캔 패키지 에 있 는 모든 contrller 를 초보 적 으로 실현 하고 경로 맵 을 통 해 접근 할 수 있 습 니 다.그러나 적어도 다음 과 같은 문제 가 분명 하 다.
  • 방법 을 집행 할 때 방법 에 매개 변수 가 있어 서 는 안 된다.업무 수요 에 부합 되 지 않 음
  • 매번 방문 할 때마다 Class 반 사 를 반복 적 으로 처리 하여 경로 매 핑 방법 을 찾 아야 하 는데 효율 이 낮다.

  • 상기 두 가지 문제 에 대해 우 리 는 2.0 판 에서 수정 을 진행 합 니 다.
  • contrller, requestmapping 대응 방법, 방법 에 대응 하 는 매개 변수 에 사용 할 수 있 는 관련 정 보 를 한 용기 에 저장 합 니 다.서버 가 처음 시 작 될 때 스 캔 을 하고 용기 에 설치 합 니 다.이렇게 하면 방문 할 때마다 이 용 기 를 옮 겨 다 니 며 1.0 판 용기 보다 편리 하 다.
  • 매개 변수 유형 을 정의 하고 주해 @XxgRequestBody@XxgParam 를 통 해 매개 변 수 를 요청 체 에서 가 져 오 거나 url 에서 가 져 오 는 것 을 구분 합 니까?뒤에서 들 어.전단 에서 전 달 된 데이터 가 져 오기
  • ObjectMapper 을 통 해 서로 다른 유형의 매개 변 수 를 조립 하고 마지막 으로 호출 방법 invoke 은 인삼 / 인삼 이 없 는 방법 으로 처리한다.

  • BeanDefinition
    /**
     *     controller      、   
     */
    @Data
    @NoArgsConstructor
    @AllArgsConstructor
    public class BeanDefinition {
     private Class typeClazz; //    
     private String typeName; //   
     private Object annotation; //   
     private String controllerUrlPath; // controller path  
     private List methodDefinitions; //   RequestMapping   
    }

    MethodDefinition
    /**
     *       
     */
    @Data
    @NoArgsConstructor
    @AllArgsConstructor
    public class MethodDefinition {
     private Class parentClazz; //      class
     private Method method; //   
     private String methodName; //    
     private Object annotation; //    
     private String requestMappingUrlPath; // url
     private String[] allowedRequestMethods; // allowedRequestMethods
     private List parameterDefinitions;  //     
     private Object result;  //     
    }

    ParameterDefinition
    /**
     *       
     */
    @Data
    @NoArgsConstructor
    @AllArgsConstructor
    public class ParameterDefinition {
     private Class paramClazz; //      
     private String paramName; //     
     private Object paramType; //     
     private boolean isRequestBody; //      body   
    }

    단일 모드 용기
    스 캔 패키지 부여 및 uri 에 따라 대응 하 는 방법 가 져 오기
    /**
     *            controller      
     *        
     */
    public class RequestPathContainer {
     private static List requestList = new ArrayList<>();
     private static final ClassLoader classLoader = RequestPathContainer.class.getClassLoader();
     private static volatile RequestPathContainer instance = null;
    ​
     public static RequestPathContainer getInstance() {
         if (instance == null) {
            synchronized(RequestPathContainer.class){
                if (instance == null) {
                    instance = new RequestPathContainer();
                }
             }
         }
         return instance;
     }
    ​
     private RequestPathContainer() {
    ​
     }
    ​
     public List getRequestList() {
        return requestList;
     }
    ​
     //    
     public void scanner(String packetUrl) throws UnsupportedEncodingException, ClassNotFoundException {
         String url = packetUrl.replace(".", "/");
         URL resource = classLoader.getResource(url);
         if (resource == null) {
             return;
         }
         String path = resource.getPath();
         File file = new File(URLDecoder.decode(path, "UTF-8"));
         if (!file.exists()) {
             return;
         }
         if (file.isDirectory()){
         File[] files = file.listFiles();
         if (files == null) {
            return;
         }
         for (File f : files) {
         if (f.isDirectory()) {
            scanner(packetUrl + "." + f.getName());
         }
         if (f.isFile() && f.getName().endsWith(".class")) {
             String classname = f.getName().replace(".class", ""); //   .class   
             Class clazz = Class.forName(packetUrl + "." + classname);
             dealClass(clazz);
             }
         }
         }
     }
    ​
     //       ,    List 
     private void dealClass(Class clazz) {
         if (!clazz.isAnnotationPresent(XxgController.class)) {
         //   controller  
            return;
         }
         List methodDefinitions = new ArrayList<>();
         Method[] methods = clazz.getDeclaredMethods();
         for (Method method : methods) {
             //          
             MethodDefinition methodDefinition = convertMethodToMethodDefinition(method, clazz);
             if (methodDefinition != null) {
                methodDefinitions.add(methodDefinition);
             }
             }
             if (methodDefinitions.size() == 0) {
                return;
             }
             //       
             BeanDefinition beanDefinition = convertBeanToBeanDefinition(clazz, methodDefinitions);
             requestList.add(beanDefinition);
        }
    ​
     //   uri              
     public MethodDefinition getMethodDefinition(String uri, String method) {
         for (BeanDefinition beanDefinition: requestList) {
         if (!uri.contains(beanDefinition.getControllerUrlPath())) {
            continue;
         }
         List methodDefinitions = beanDefinition.getMethodDefinitions();
         for (MethodDefinition methodDefinition: methodDefinitions) {
         StringBuilder sb = new StringBuilder().append(beanDefinition.getControllerUrlPath());
         sb.append(methodDefinition.getRequestMappingUrlPath());
         if (!sb.toString().equals(uri)) {
         continue;
         }
         String[] allowedRequestMethods = methodDefinition.getAllowedRequestMethods();
         for (String str : allowedRequestMethods) {
         if (str.toUpperCase().equals(method.toUpperCase())) {
         //                ,        
         return methodDefinition;
         }
         }
         }
         }
         return null;
     }
    ​
     /**
     *  controller           
     */
     private BeanDefinition convertBeanToBeanDefinition(Class clazz, List methodDefinitions) {
         BeanDefinition beanDefinition = new BeanDefinition();
         beanDefinition.setTypeName(clazz.getName());
         beanDefinition.setTypeClazz(clazz);
         XxgController controller = (XxgController) clazz.getAnnotation(XxgController.class);
         beanDefinition.setAnnotation(controller);
         beanDefinition.setControllerUrlPath(controller.value());
         beanDefinition.setMethodDefinitions(methodDefinitions);//      
         return beanDefinition;
     }
    ​
     /**
     *              
     */
     private MethodDefinition convertMethodToMethodDefinition(Method method, Class clazz) {
     if (!method.isAnnotationPresent(XxgRequestMapping.class)) {
     //   RequestMapping  
     return null;
     }
     method.setAccessible(true);
     Parameter[] parameters = method.getParameters();
     //        
     List parameterDefinitions = new ArrayList<>();
     for ( Parameter parameter : parameters) {
     ParameterDefinition parameterDefinition = convertParamToParameterDefinition(parameter);
     parameterDefinitions.add(parameterDefinition);
     }
     //        
     MethodDefinition methodDefinition = new MethodDefinition();
     methodDefinition.setParameterDefinitions(parameterDefinitions);  //       
     methodDefinition.setMethod(method);
     methodDefinition.setMethodName(method.getName());
     methodDefinition.setResult(method.getReturnType());
     XxgRequestMapping requestMapping = method.getAnnotation(XxgRequestMapping.class);
     methodDefinition.setRequestMappingUrlPath(requestMapping.value());
     methodDefinition.setAnnotation(requestMapping);
     methodDefinition.setAllowedRequestMethods(requestMapping.methods());
     methodDefinition.setParentClazz(clazz);
     return methodDefinition;
     }
    ​
     /**
     *              
     */
     private ParameterDefinition convertParamToParameterDefinition(Parameter parameter) {
     ParameterDefinition parameterDefinition = new ParameterDefinition();
     if ( parameter.isAnnotationPresent(XxgParam.class)) {
     parameterDefinition.setParamName(parameter.getAnnotation(XxgParam.class).value());
     } else {
     parameterDefinition.setParamName(parameter.getName());
     }
     parameterDefinition.setParamClazz(parameter.getType());
     parameterDefinition.setParamType(parameter.getType());
     parameterDefinition.setRequestBody(parameter.isAnnotationPresent(XxgRequestBody.class));
     return parameterDefinition;
     }
    ​
    }

    전역 servlet
    차단 기 를 사용 하지 않 고 servlet 를 사용 하여 경로 배 포 를 진행 합 니 다.이 servlet 감청 /
    public class DispatcherServlet extends HttpServlet {
     private ObjectMapper objectMapper = new ObjectMapper();
    ​
     @Override
     protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
    ​
     //     
     resp.setContentType("text/json;charset=utf-8");
     RequestPathContainer requestPathContainer = RequestPathContainer.getInstance();
     MethodDefinition methodDefinition = requestPathContainer.getMethodDefinition(req.getRequestURI(), req.getMethod());
    ​
     if (methodDefinition == null) {
     resp.setStatus(404);
     sendResponse(R.failed("       "), req, resp);
     return;
     }
    ​
     List parameterDefinitions = methodDefinition.getParameterDefinitions();
     List params = new ArrayList<>(parameterDefinitions.size());
     for (ParameterDefinition parameterDefinition : parameterDefinitions) {
     try {
     Object value = dealParam(parameterDefinition, req, resp);
     params.add(value);
     } catch (ParamException e) {
     resp.setStatus(404);
     sendResponse(R.failed(e.getMessage()), req, resp);
     return ;
     }
     }
    ​
     try {
     Object result = methodDefinition.getMethod().invoke(methodDefinition.getParentClazz().newInstance(), params.toArray());
     sendResponse(result, req, resp);
     } catch (IllegalAccessException | InvocationTargetException | InstantiationException e) {
     e.printStackTrace();
     sendResponse(e.getMessage(), req, resp);
     }
    ​
     }
    ​
     /**
     *     
     * @param parameterDefinition
     * @param req
     * @param resp
     */
     private Object dealParam(ParameterDefinition parameterDefinition, HttpServletRequest req, HttpServletResponse resp) throws ParamException, IOException {
     Object value;
     String data = "";
     if (parameterDefinition.isRequestBody()) {
     //     (request    )     
     data = getJsonString(req);
     } else if (parameterDefinition.getParamType() == HttpServletRequest.class) {
     return req;
     } else if (parameterDefinition.getParamType() == HttpServletResponse.class) {
     return resp;
     } else if (isJavaType(parameterDefinition)) {
     //  url     
     data = req.getParameter(parameterDefinition.getParamName());
     if(data == null) {
     throw new ParamException("           ,       ");
     }
     } else {
     //    url         
     try {
     Object obj = parameterDefinition.getParamClazz().newInstance();
     ConvertUtils.register(new DateConverter(), Date.class);
     BeanUtils.populate(obj, req.getParameterMap());
     return obj;
     } catch (InstantiationException | IllegalAccessException | InvocationTargetException e) {
     throw new ParamException("     '" + parameterDefinition.getParamName() + "'    ");
     }
     }
     try {
     value = objectMapper.readValue(data, parameterDefinition.getParamClazz());
     } catch (JsonProcessingException e) {
     String errMsg = "  '" + parameterDefinition.getParamName() +
     "'  '" + parameterDefinition.getParamType() +
     "  ";
     throw new ParamException(errMsg);
     }
     return value;
     }
    ​
     private void sendResponse(Object result, HttpServletRequest req, HttpServletResponse resp) throws IOException {
     if (result == null) {
     return;
     }
     resp.setContentType("text/json;charset=utf-8");
     objectMapper.writeValue(resp.getWriter(), result);
     }
    ​
     /**
     *            
     * @return
     */
     private boolean isJavaType(ParameterDefinition parameterDefinition) {
     Object[] javaTypes = MyJavaType.getJavaTypes();
     for (Object item : javaTypes) {
     if (item.equals(parameterDefinition.getParamClazz())) {
     return true;
     }
     }
     return false;
     }
    ​
     /**
     *       json   
     */
     private String getJsonString(HttpServletRequest req) throws IOException {
     BufferedReader br = new BufferedReader(new InputStreamReader(req.getInputStream(), "utf-8"));
     char[] chars = new char[1024];
     int len;
     StringBuilder sb = new StringBuilder();
     while ((len = br.read(chars)) != -1) {
     sb.append(chars, 0, len);
     }
     return sb.toString();
     }
    }

    servletcontext 모니터 용기 초기 화
    @Override
     public void contextInitialized(ServletContextEvent servletContextEvent) {
     RequestPathContainer requestPathContainer = RequestPathContainer.getInstance();
     String configClassName = servletContextEvent.getServletContext().getInitParameter("config");
     Class appListenerClass = null;
     try {
     appListenerClass = Class.forName(configClassName);
     XxgScanner xxgScanner = (XxgScanner)appListenerClass.getAnnotation(XxgScanner.class);
     if (xxgScanner != null) {
     try {
     requestPathContainer.scanner(xxgScanner.value()); //   controller ,   List
     } catch (UnsupportedEncodingException | ClassNotFoundException e) {
     e.printStackTrace();
     }
     }
     } catch (ClassNotFoundException e) {
     e.printStackTrace();
     }
     }

    남 겨 진 문제
    정적 자원 도 차단 되 었 습 니 다.
    정적 자원 처리
    default servlet
    tomcat 의 conf/web.xml 파일 을 열 면 tomcat 기본 값 default servlet 이 있 습 니 다. 다음 설정 이 있 습 니 다.
     
     default
     org.apache.catalina.servlets.DefaultServlet
     
     debug
     0
     
     
     listings
     false
     
     1
     

    그러나 그 는 servlet - mapping, 즉 처리 경로 와 일치 하지 않 습 니 다. 그러면 우리 프로젝트 의 웹. xml 에서 정적 자원 을 처리 할 수 있 습 니 다.
    
    
    
     DispatcherServlet
     /
     
    
     
     default
     *.html
     
     
     default
     *.js
     
     
     default
     *.css
     
     
     default
     *.jpg
     

    마지막.
    1. 본 고 는 사실 주로 다음 과 같은 두 가지 조작 을 했다.
  • 서버 가 시 작 될 때 contrller 패 키 지 를 스 캔 하면 우리 가 예상 하 는 클래스, 방법, 매개 변 수 를 용기 에 설치 합 니 다.
  • 전단 에서 서버 를 방문 하여 용기 에서 지정 한 경 로 를 가 져 오 는 방법 2.1. 방문 매개 변 수 를 서로 다른 유형 으로 매개 변수 목록 에 설치 하고 2.2 대응 방법 2.3 처리 방법 으로 데 이 터 를 되 돌려 줍 니 다
  • 참고 설명
  • 프로젝트 실현 과정
  • 1.0 버 전 은 블 로 거들 이 스스로 생각 하고 완성 한 것 이다.2. 0 판 은 블 로 거들 의 작은 고 선생님 이 블 로 거들 에 게 방향 을 알려 주 었 고 쓴 후에 작은 고 선생님 의 실현 을 본 다음 에 종합 적 으로 완 선 된 것 이다.
  • 글 을 쓴 후에 블 로 거들 은 프로젝트 의 서로 다른 결합 등 조작 을 했 고 코드 를 재 구성 했다. 주로 개폐 원칙, 단일 직책 원칙 등에 대응 하기 위해 서 이다.
  • 코드: https://github.com/dengbenche...
  • 전송 문
    다음 절: AutoWired 속성 에 사용 되 는 간단 한 구현

    좋은 웹페이지 즐겨찾기