2012-07-20 11 views
12

Questo è un cross post. Ho anche postato la stessa domanda ai forum di primavera. http://forum.springsource.org/showthread.php?128579-Database-driven-Controller-MappingCome si fa a mappare dinamicamente i percorsi spring-webmvc?

Ciao Sto cercando di fare database di mappature del controller guidati in modo che possano cambiare in fase di esecuzione .

Finora quello che ho è il seguente.

Adattatore per gestore personalizzato che può essere sempre ottimizzato in seguito.

@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); 
    } 

} 

mia webmvc-config appare come segue (la parte rilevante)

Codice:

<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"/> 

Questo sembra essere raccogliendo il controller corretto. Tuttavia ricevo un errore quando si va in un percorso definito database (come "/ 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) 
     .... 

Ho bisogno di definire un gestore di annotazione personalizzata?

Per essere onesti, questo intero processo sembra più difficile di quanto dovrebbe. Voglio che 1 controller gestisca tutte le richieste su un percorso url definito esternamente, è questo il modo corretto di spostarlo.

Vorrei anche passare l'oggetto che corrisponde al controller se questo è possibile piuttosto che fare una nuova ricerca nel controller. Questo fondamentalmente formerà il mio modello per la vista.

Qualche consiglio su come farlo funzionare?

EDIT Per la cronaca la NPE è qui

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

Un altro Modifica numeri di versione dal 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> 

Ho risposto alla domanda io stesso qui sotto ma sono ancora interessato a persone che stanno valutando il modo corretto di farlo.

+0

@fmucar cosa esattamente di database di guida dei modelli URL? il suo 90% non la sua conclusione non solo il metodo corretto sul controller del controller corretto. Se stai parlando di passare la variabile al controller, credo che il caso peggiore mi farebbe male ct come attributo di richiesta e preleva il controller da lì, ma sembra goffo. – Wes

+0

Mi dispiace, devo aver frainteso il tuo q. Cancellerò il commento sopra – fmucar

+0

Puoi confermare la tua versione di Spring MVC –

risposta

2

Proprio per superare questo problema specifico, mi permetta di raccomandare una via d'uscita per il momento -

Creare la tua handlerAdapter comporre internamente l'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> 

Questo dovrebbe risolvere il problema attuale, ma probabilmente incontrerai altri problemi

+0

Non ho altre risposte. Hai la taglia. – Wes

3

Apparentemente dalla mancanza di risposte contrarie qui e sullo spring forums sembra che non ci sia un modo più semplice per farlo all'interno di la struttura della molla.

Sono comunque riuscito a farlo funzionare e ho condiviso un progetto su github che può essere creato con maven che aggiunge 4 classi per facilitare il processo di aggiunta dinamica della classe. Questo progetto può essere trovato a https://github.com/Athas1980/MvcBackingBean. Condividerò anche un altro progetto per dimostrare che funziona.

Grazie a Marten Deinum, e Rossen Stoyanchev


Per chi è interessato a come raggiungere questo voi stessi è necessario eseguire le seguenti operazioni

  1. Implementare un'istanza di HandlerMapper Questo dà la mappatura tra una classe controller e l'url a cui stai mappando.

    // 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. implementare un HandlerMethodArgumentResolver per recuperare il valore dalla sessione. (Supponendo che si sono interessati di impostazione nella sessione)

    // 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. implementare un WebArgumentResolver personalizzato per andare a prendere l'istanza del Bean passato. Impostalo come una proprietà su un'istanza di 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. Ho anche creato un'annotazione BackingBean e un'interfaccia di trasmettere ai miei addapters del gestore, come ho sentito che erano più facili.

  5. Crea il controller. Se usi il mio codice, vorrai inserire l'argomento usando l'annotazione @BackingBean. La mappatura richiesta sul controller stesso non deve corrispondere eventuali buoni URL (Questo perché abbiamo bypassare questo passaggio con il nostro adattatore conduttore e non vogliamo che il gestore predefinito di annotazione per raccoglierlo.

  6. Wire ogni cosa in primavera . Ecco un esempio di file dal mio progetto di esempio di lavoro.

    <?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 grazie per la formattazione. – Wes

+1

l'esempio che utilizza questo può essere trovato su github https://github.com/Athas1980/BackingBeanWebExample nota questa soluzione ha bisogno di mvc 3.1 + – Wes

Problemi correlati