2012-07-20 18 views
12

Este es un mensaje cruzado. También publiqué la misma pregunta en los foros de primavera. http://forum.springsource.org/showthread.php?128579-Database-driven-Controller-Mapping¿Cómo se pueden mapear las rutas spring-webmvc de forma dinámica?

Hola estoy tratando de hacer base de datos mapa de controladores accionados para que puedan cambiar en tiempo de ejecución .

Hasta ahora lo que tengo es el siguiente.

Adaptador de controlador personalizado que siempre se puede optimizar más adelante.

@Component 
public class DatabasePageUrlHandlerMapping extends AbstractUrlHandlerMapping implements PriorityOrdered { 


    @Override 
    protected Object getHandlerInternal(HttpServletRequest request) 
      throws Exception { 
     String lookupPath = getUrlPathHelper().getLookupPathForRequest(request); 
     List<Page> pages = Page.findAllPages(); 
     for (Page page : pages) { 
      if (lookupPath.equals(page.getSeoPath())) { 
       Object handler = getApplicationContext().getBean("_pageViewController"); 
       return new HandlerExecutionChain(handler); 
      } 
     } 
     return super.getHandlerInternal(request); 
    } 

} 

mi webmvc-config se ve de la siguiente manera (la parte pertinente) Código

:

<context:component-scan base-package="com.artiststogether" 
    use-default-filters="false"> 
    <context:include-filter expression="org.springframework.stereotype.Controller" 
     type="annotation" /> 
</context:component-scan> 

<!-- If I don't put an order into this it doesn't fail over to the implementation why? --> 
<bean class="com.artiststogether.web.DatabasePageUrlHandlerMapping" p:order="-1" /> 
<bean class="org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter"/> 

Esto parece estar recogiendo el controlador correcto. Sin embargo Recibo un error cuando va a una trayectoria definida base de datos (por ejemplo, "/ a")

java.lang.NullPointerException 
    at org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter$ServletHandlerMethodResolver.useTypeLevelMapping(AnnotationMethodHandlerAdapter.java:675) 
    at org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter$ServletHandlerMethodResolver.resolveHandlerMethod(AnnotationMethodHandlerAdapter.java:585) 
    at org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter.invokeHandlerMethod(AnnotationMethodHandlerAdapter.java:431) 
    at org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter.handle(AnnotationMethodHandlerAdapter.java:424) 
    at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:900) 
    at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:827) 
    at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:882) 
    at org.springframework.web.servlet.FrameworkServlet.doGet(FrameworkServlet.java:778) 
    at javax.servlet.http.HttpServlet.service(HttpServlet.java:621) 
    at javax.servlet.http.HttpServlet.service(HttpServlet.java:722) 
     .... 

¿Es necesario definir un controlador de anotación personalizada?

Para ser honesto, todo este proceso parece más difícil de lo que debería. Quiero que 1 controlador maneje todas las solicitudes a una ruta de URL definida externamente, esta es la forma correcta de hacerlo.

También me gustaría pasar el objeto que coincide con el controlador si esto es posible en lugar de hacer una nueva búsqueda en el controlador. Esto básicamente formará mi modelo para la vista.

¿Algún consejo sobre cómo hacerlo funcionar?

EDITAR Para el registro de la NPE es aquí

private boolean useTypeLevelMapping(HttpServletRequest request) { 
     if (!hasTypeLevelMapping() || ObjectUtils.isEmpty(getTypeLevelMapping().value())) { 
      return false; 
     } 
     return (Boolean) request.getAttribute(HandlerMapping.INTROSPECT_TYPE_LEVEL_MAPPING); 
    } 

otra edición números de versión de la pom.xml

<properties> 
    <aspectj.version>1.6.12</aspectj.version> 
    <java.version>6</java.version> 
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> 
    <roo.version>1.2.1.RELEASE</roo.version> 
    <slf4j.version>1.6.4</slf4j.version> 
    <spring.version>3.1.0.RELEASE</spring.version> 
<spring-security.version>3.1.0.RELEASE</spring-security.version> 
</properties> 

que he respondido a la pregunta a mí mismo más adelante pero todavía me interesan las personas que analizan la forma correcta de hacerlo.

+0

@fmucar qué es exactamente la base de datos de conducir los patrones de URL? Su 90% no su conclusión el controlador correcto simplemente no es el método correcto en el controlador. Si estás hablando de pasar la variable al controlador supongo que en el peor de los casos, me inyectaría ct como un atributo de solicitud y buscar en el controlador desde allí, pero eso se siente torpe. – Wes

+0

Lamento haber malinterpretado tu q. Eliminaré el comentario anterior – fmucar

+0

¿Puede confirmar su versión de Spring MVC –

Respuesta

2

Sólo para superar este problema específico, permítanme recomendar una salida por ahora -

Crear su propio handlerAdapter componer internamente la AnnotationMethodHandlerAdapter:

public DBAnnotationMethodHandlerAdapter implements HandlerAdapter,{ 
    private AnnotationHandlerAdapter target; 

    @Override 
    public boolean supports(Object handler) { 
     return this.target.supports(handler); 
    } 

    @Override 
    public ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { 
     request.setAttribute(HandlerMapping.INTROSPECT_TYPE_LEVEL_MAPPING, true); 
     return this.target.handle(request, response, handler); 
    } 

    public void setTarget(AnnotationHandlerAdapter target){ 
     this.target = target; 
    } 

} 

    <bean class="mypkg.DBAnnotationMethodHandlerAdapter"> 
     <property name="target"> 
      <bean class="org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter"/> 
     </property> 
    </bean> 

Esto debería resolver el problema actual, pero es probable que encuentre otros problemas

+0

No obtuve otras respuestas. Tú tienes la recompensa. – Wes

3

Aparentemente por la falta de respuestas en contrario aquí y en el spring forums parece que no hay una forma más sencilla de hacerlo dentro de el marco de primavera.

Sin embargo, he logrado ponerlo en funcionamiento y he compartido un proyecto en github que se puede construir con maven que agrega 4 clases para facilitar el proceso de agregar dinámicamente la clase. Este proyecto se puede encontrar en https://github.com/Athas1980/MvcBackingBean. También compartiré otro proyecto para demostrar que funciona.

Gracias a Marten Deinum y Rossen Stoyanchev


Para los interesados ​​en la forma de lograr esto a sí mismos que tiene que hacer lo siguiente

  1. Implementar una instancia de HandlerMapper Esto da usted la asignación entre una clase de controlador y la url que está mapeando.

    // Copyright 2012 Wesley Acheson 
    // 
    // Licensed under the Apache License, Version 2.0 (the "License"); 
    // you may not use this file except in compliance with the License. 
    // You may obtain a copy of the License at 
    // 
    //  http://www.apache.org/licenses/LICENSE-2.0 
    // 
    // Unless required by applicable law or agreed to in writing, software 
    // distributed under the License is distributed on an "AS IS" BASIS, 
    // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 
    // See the License for the specific language governing permissions and 
    // limitations under the License. 
    
    package com.wesley_acheson.spring; 
    
    import javax.servlet.http.HttpServletRequest; 
    import javax.servlet.http.HttpServletResponse; 
    
    import org.springframework.core.PriorityOrdered; 
    import org.springframework.web.servlet.HandlerExecutionChain; 
    import org.springframework.web.servlet.handler.AbstractUrlHandlerMapping; 
    import org.springframework.web.servlet.handler.HandlerInterceptorAdapter; 
    
    /** 
    * A Handler mapper that delegates to a {@link UrlBackingBeanMapper} to know 
    * whether it should match a url. If it does match a url then it adds the bean 
    * which matches the url to the request. 
    * 
    * @author Wesley Acheson 
    * 
    */ 
    public class BackingBeanUrlHandlerMapper extends AbstractUrlHandlerMapping 
         implements PriorityOrdered { 
    
        private UrlBackingBeanMapper<?> urlMapper; 
    
        /** 
        * 
        * @param urlMapper 
        *   The bean which matches urls with other beans. 
        */ 
        public void setUrlMapper(UrlBackingBeanMapper<?> urlMapper) { 
         this.urlMapper = urlMapper; 
        } 
    
        protected UrlBackingBeanMapper<?> getUrlMapper() { 
         return urlMapper; 
        } 
    
        public static final String BACKING_BEAN_ATTRIBUTE = BackingBeanUrlHandlerMapper.class 
          .getName() + ".backingBean"; 
    
        /** 
        * The controller which control will be passed to if there is any beans 
        * matching in @{link {@link #setUrlMapper(UrlBackingBeanMapper)}. 
        */ 
        public Object controller; 
    
        /** 
        * @param controller 
        *   <p> 
        *   The controller which control will be passed to if there is any 
        *   beans matching in @{link 
        *   {@link #setUrlMapper(UrlBackingBeanMapper)}. 
        */ 
        public void setController(Object controller) { 
         this.controller = controller; 
        } 
    
        /* 
        * (non-Javadoc) 
        * 
        * @see org.springframework.web.servlet.handler.AbstractUrlHandlerMapping# 
        * lookupHandler(java.lang.String, javax.servlet.http.HttpServletRequest) 
        */ 
        @Override 
        protected Object lookupHandler(String urlPath, HttpServletRequest request) 
          throws Exception { 
    
         if (urlMapper.isPathMapped(urlPath)) { 
          Object bean = urlMapper.retrieveBackingBean(urlPath); 
          return buildChain(bean, urlPath); 
         } 
    
         return super.lookupHandler(urlPath, request); 
        } 
    
        /** 
        * Builds a handler execution chain that contains both a path exposing 
        * handler and a backing bean exposing handler. 
        * 
        * @param bean 
        *   The object to be wrapped in the handler execution chain. 
        * @param urlPath 
        *   The path which matched. In this case the full path. 
        * @return The handler execution chain that contains the backing bean. 
        * 
        * @see {@link AbstractUrlHandlerMapping#buildPathExposingHandler(Object, String, String, java.util.Map)} 
        *  
        */ 
        protected HandlerExecutionChain buildChain(Object bean, String urlPath) { 
         // I don't know why but the super class declares object but actually 
         // returns handlerExecution chain. 
         HandlerExecutionChain chain = (HandlerExecutionChain) buildPathExposingHandler(
           controller, urlPath, urlPath, null); 
         addBackingBeanInteceptor(chain, bean); 
         return chain; 
        } 
    
        /** 
        * Adds an inteceptor which adds the backing bean into the request to an 
        * existing HandlerExecutionChain. 
        * 
        * @param chain 
        *   The chain which the backing bean is being added to. 
        * @param bean 
        *   The object to pass through to the controller. 
        */ 
        protected void addBackingBeanInteceptor(HandlerExecutionChain chain, 
          Object bean) { 
         chain.addInterceptor(new BackingBeanExposingInteceptor(bean)); 
    
        } 
    
        /** 
        * An Interceptor which adds a bean to a request for later consumption by a 
        * controller. 
        * 
        * @author Wesley Acheson 
        * 
        */ 
        protected class BackingBeanExposingInteceptor extends 
          HandlerInterceptorAdapter { 
         private Object backingBean; 
    
         /** 
         * @param backingBean 
         *   the bean which is passed through to the controller. 
         */ 
         public BackingBeanExposingInteceptor(Object backingBean) { 
          this.backingBean = backingBean; 
         } 
    
         @Override 
         public boolean preHandle(HttpServletRequest request, 
           HttpServletResponse response, Object handler) throws Exception { 
          request.setAttribute(BACKING_BEAN_ATTRIBUTE, backingBean); 
          return true; 
         } 
        } 
    
    } 
    
  2. Implementar un HandlerMethodArgumentResolver a buscar el valor de la sesión. (Asumiendo que son intrested en el establecimiento de la sesión)

    // Copyright 2012 Wesley Acheson 
    // 
    // Licensed under the Apache License, Version 2.0 (the "License"); 
    // you may not use this file except in compliance with the License. 
    // You may obtain a copy of the License at 
    // 
    //  http://www.apache.org/licenses/LICENSE-2.0 
    // 
    // Unless required by applicable law or agreed to in writing, software 
    // distributed under the License is distributed on an "AS IS" BASIS, 
    // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 
    // See the License for the specific language governing permissions and 
    // limitations under the License. 
    
    package com.wesley_acheson.spring; 
    
    import javax.servlet.http.HttpServletRequest; 
    
    import org.springframework.core.MethodParameter; 
    import org.springframework.web.bind.support.WebDataBinderFactory; 
    import org.springframework.web.context.request.NativeWebRequest; 
    import org.springframework.web.method.support.HandlerMethodArgumentResolver; 
    import org.springframework.web.method.support.ModelAndViewContainer; 
    
    /** 
    * Resolves method parameters which are annotated with {@link BackingBean}. 
    * 
    * <b>Note:</b> Only works for Http requests. 
    * 
    * @author Wesley Acheson 
    * 
    */ 
    public class BackingBeanValueResolver implements HandlerMethodArgumentResolver { 
    
        /** 
        * Constructor. 
        */ 
        public BackingBeanValueResolver() { 
        } 
    
        /** 
        * Implementation of 
        * {@link HandlerMethodArgumentResolver#supportsParameter(MethodParameter)} 
        * that returns true if the method parameter is annotatated with 
        * {@link BackingBean}. 
        */ 
        @Override 
        public boolean supportsParameter(MethodParameter parameter) { 
         return parameter.hasParameterAnnotation(BackingBean.class); 
        } 
    
        @Override 
        public Object resolveArgument(MethodParameter parameter, 
          ModelAndViewContainer mavContainer, NativeWebRequest webRequest, 
          WebDataBinderFactory binderFactory) throws Exception { 
         return webRequest.getNativeRequest(HttpServletRequest.class) 
           .getAttribute(
             BackingBeanUrlHandlerMapper.BACKING_BEAN_ATTRIBUTE); 
        } 
    
    } 
    
  3. Implementar un WebArgumentResolver personalizada para buscar la instancia de la haba pasado. Establezca esto como una propiedad en una instancia de AnnotationMethodHandler.

    /** 
    * 
    */ 
    package com.wesley_acheson.spring; 
    
    import javax.servlet.http.HttpServletRequest; 
    
    import org.springframework.core.MethodParameter; 
    import org.springframework.web.bind.support.WebArgumentResolver; 
    import org.springframework.web.context.request.NativeWebRequest; 
    
    
    /** 
    * @author Wesley Acheson 
    * 
    */ 
    public class BackingBeanArgumentResolver implements WebArgumentResolver { 
    
        /* (non-Javadoc) 
        * @see org.springframework.web.bind.support.WebArgumentResolver#resolveArgument(org.springframework.core.MethodParameter, org.springframework.web.context.request.NativeWebRequest) 
        */ 
        @Override 
        public Object resolveArgument(MethodParameter methodParameter, 
          NativeWebRequest webRequest) throws Exception { 
         if (methodParameter.hasParameterAnnotation(BackingBean.class)) 
         { 
          HttpServletRequest request = webRequest.getNativeRequest(HttpServletRequest.class); 
          Object parameter = request.getAttribute(BackingBeanUrlHandlerMapper.BACKING_BEAN_ATTRIBUTE); 
          if (parameter == null) 
          { 
           return UNRESOLVED; 
          } 
          if (methodParameter.getParameterType().isAssignableFrom(parameter.getClass())) 
          { 
           return parameter; 
          } 
         } 
    
    
         return UNRESOLVED; 
        } 
    
    } 
    
  4. También he creado una anotación BackingBean y una interfaz para pasar a mis addapters manejador mientras sentía que eran más fáciles.

  5. Crea tu controlador. Si usa mi código, querrá insertar el argumento con la anotación @BackingBean. El mapeo de solicitud en el propio controlador no debe coincidir con ningún buenas direcciones URL (Esto se debe a que omitir este paso con nuestro adaptador de guía y no queremos que el manejador de anotación por defecto para recogerlo.

  6. alambre a todo en primavera . Aquí está un ejemplo de archivo de mi proyecto de ejemplo de trabajo.

    <?xml version="1.0" encoding="UTF-8" standalone="no"?> 
    <beans xmlns="http://www.springframework.org/schema/beans" 
        xmlns:context="http://www.springframework.org/schema/context" 
        xmlns:mvc="http://www.springframework.org/schema/mvc" xmlns:p="http://www.springframework.org/schema/p" 
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:util="http://www.springframework.org/schema/util" 
        xsi:schemaLocation="http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-3.1.xsd 
         http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.1.xsd 
         http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util-3.1.xsd 
         http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.1.xsd"> 
    
        <!-- The controllers are autodetected POJOs labeled with the @Controller 
         annotation. --> 
        <context:component-scan base-package="com.wesley_acheson" 
         use-default-filters="false"> 
         <context:include-filter expression="org.springframework.stereotype.Controller" 
          type="annotation" /> 
        </context:component-scan> 
    
        <bean class="com.wesley_acheson.spring.BackingBeanUrlHandlerMapper" 
         p:order="-1"> 
         <property name="controller"> 
          <!-- A simple example controller. --> 
          <bean class="com.wesley_acheson.example.PageController" /> 
         </property> 
         <!-- A simple example mapper. --> 
         <property name="urlMapper"> 
          <bean class="com.wesley_acheson.example.PageBeanUrlMapper" /> 
         </property> 
        </bean> 
    
        <util:map id="pages"> 
         <entry key="/testPage1"> 
          <bean class="com.wesley_acheson.example.Page"> 
           <property name="title" value="Test Page 1 title" /> 
           <property name="contents" 
            value="This is the first test page.&lt;br /&gt; It's only purpose is to check 
            if &lt;b&gt;BackingBeans&lt;/b&gt; work." /> 
          </bean> 
         </entry> 
    
         <entry key="/test/nested"> 
          <bean class="com.wesley_acheson.example.Page"> 
           <property name="title" value="Nested Path" /> 
           <property name="contents" 
            value="This is another test page its purpose is to ensure nested pages work." /> 
          </bean> 
         </entry> 
        </util:map> 
    
    
        <bean 
         class="org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter"> 
         <property name="customArgumentResolver"> 
          <bean class="com.wesley_acheson.spring.BackingBeanArgumentResolver" /> 
         </property> 
        </bean> 
    
        <!-- Turns on support for mapping requests to Spring MVC @Controller methods 
         Also registers default Formatters and Validators for use across all @Controllers --> 
        <mvc:annotation-driven /> 
    
    
        <!-- Handles HTTP GET requests for /resources/** by efficiently serving 
         up static resources --> 
        <mvc:resources location="/, classpath:/META-INF/web-resources/" 
         mapping="/resources/**" /> 
    
        <!-- Allows for mapping the DispatcherServlet to "/" by forwarding static 
         resource requests to the container's default Servlet --> 
        <mvc:default-servlet-handler /> 
    
    </beans> 
    
+0

@Flexo gracias por el formateo. – Wes

+1

el ejemplo usando esto se puede encontrar en github https://github.com/Athas1980/BackingBeanWebExample note que esta solución necesita mvc 3.1 + – Wes

Cuestiones relacionadas