2011-03-11 25 views
27

Venendo da Struts2 Sono abituato a dichiarare @Namespace annotazioni sulle classi super (o package-info.java) ed ereditando le classi sarebbero successivamente salire sul valore nella @Namespace annotazione dei suoi antenati e anteporre al percorso di richiesta per l'azione. Ora sto cercando di fare qualcosa di simile in Spring MVC utilizzando @RequestMapping annotazione come segue (codice tagliati per brevità):Spring MVC @RequestMapping Inheritance

package au.test 

@RequestMapping(value = "/") 
public abstract class AbstractController { 
    ... 
} 

au.test.user 

@RequestMapping(value = "/user") 
public abstract class AbstractUserController extends AbstractController { 

    @RequestMapping(value = "/dashboard") 
    public String dashboard() { 
     .... 
    } 
} 

au.test.user.twitter 

@RequestMapping(value = "/twitter") 
public abstract class AbstractTwitterController extends AbstractUserController { 
    ... 
} 

public abstract class TwitterController extends AbstractTwitterController { 

    @RequestMapping(value = "/updateStatus")  
    public String updateStatus() { 
     .... 
    } 
} 
  • / opere come si aspettano
  • /user/dashboard funziona come previsto
  • Tuttavia quando lo farei mi aspettavo che /user/twitter/updateStatus funzionasse e non controlla i registri Posso vedere una voce di registro che assomiglia a qualcosa:

org.springframework.web.servlet.mvc.annotation.DefaultAnnotationHandlerMapping - Mappato sentiero URL [/ tweeter/updateStatus] sul gestore 'twitterController'

C'è un'impostazione che posso permettere che eseguirà la scansione del superclassi per le annotazioni @RequestMapping e costruire il percorso corretto?

Inoltre, suppongo che la definizione di @RequestMapping su un pacchetto in package-info.java sia illegale?

risposta

24

Il seguente diventa fondamentalmente /tweeter/updateStatus e non /user/tweeter/updateStatus

public abstract class TwitterController extends AbstractTwitterController { 

    @RequestMapping(value = "/updateStatus")  
    public String updateStatus() { 
     .... 
    } 
} 

Questo è il comportamento previsto dal momento che hai sovrascritta l'originale @RequestMapping che avete dichiarato nella AbstractController e AbstractUserController.

Infatti, quando hai dichiarato che AbstractUserController ha sostituito anche lo @RequestMapping per AbstractController. Ti dà solo l'illusione che il/dal AbstractController sia stato ereditato.

"Esiste un'impostazione che posso abilitare che eseguirà la scansione delle superclassi per le annotazioni @RequestMapping e creerà il percorso corretto?" Non che io sappia.

+6

Avete per caso qualche altro suggerimento su come raggiungere questo obiettivo. Mi sembra uno spreco dover mettere/x/y/z in un ambiente di sottoclasse. E poi se ho deciso di cambiare/x/a/k/devo andare in tutto il posto nel codice in cui è definito e cambiarlo manualmente! – garyj

+0

@chris: questo non funziona per me. C'è una fase di configurazione in questione o è solo un problema di annotazione? Il controller di base si trova infatti nello stesso pacchetto del controller che estende il controller di base. Tuttavia, l'URI/RequestMapping del controller di base viene ignorato e/o non trovato. "org.springframework.web.servlet.DispatcherServlet: 947 - Nessuna mappatura trovata per richiesta HTTP con URI". Quindi, se utilizzo semplicemente il RequestMapping "base" del controller esteso, va bene. Se provo a utilizzare RequestMapping ereditato dal controller di base, non riesco a trovare l'URI. – Rick

+0

Vale la pena dichiarare esplicitamente che i percorsi di super classe sono ancora instradabili. Ovvero, scavalcando una classe e fornendo nuovi '@ RequestMapping's non sovrascrive o sostituisce' @ RequestMapping' della super-classe, ne aggiunge solo altri. Nella domanda dell'OP sopra, ad esempio, ciò significa che tutti questi percorsi sono validi: '/ user/dashboard','/twitter/updateStatus'. – kuporific

0

Secondo la tecnica spiegata in Modifying @RequestMappings on startup, sì, è possibile costruire un modello di URL da superclassi nel modo desiderato.

In sostanza, è necessario creare una sottoclasse RequestMappingHandlerMapping (molto probabilmente, sarà il vostro HandlerMapping implementazione, ma si prega di controllare in primo luogo) e protected override getMappingForMethod metodo. Una volta reso possibile il rendering, hai il pieno controllo della generazione dei pattern URL.

Dall'esempio hai dato non è del tutto chiaro il criterio esatto fusione, per esempio, quale strada si vuole avere se una superclasse AbstractTwitterController implementa anche updateStatus() metodo con il proprio @RequestMapping, o come si vorrebbe per concatenare l'URL modelli in tutta la gerarchia, top-down o bottom-up, (ho assunto l'ex sotto), ma, si spera, il seguente frammento vi darà alcune idee:

private static class PathTweakingRequestMappingHandlerMapping extends RequestMappingHandlerMapping { 

       @Override 
       protected RequestMappingInfo getMappingForMethod(Method method, Class<?> handlerType) { 
        RequestMappingInfo methodMapping = super.getMappingForMethod(method, handlerType); 
        if (methodMapping == null) 
         return null; 
        List<String> superclassUrlPatterns = new ArrayList<String>(); 
        boolean springPath = false; 
        for (Class<?> clazz = handlerType; clazz != Object.class; clazz = clazz.getSuperclass()) 
         if (clazz.isAnnotationPresent(RequestMapping.class)) 
          if (springPath) 
           superclassUrlPatterns.add(clazz.getAnnotation(RequestMapping.class).value()[0]);// TODO handle other elements in the array if necessary 
          else 
           springPath = true; 
        if (!superclassUrlPatterns.isEmpty()) { 
         RequestMappingInfo superclassRequestMappingInfo = new RequestMappingInfo("", 
           new PatternsRequestCondition(String.join("", superclassUrlPatterns)), null, null, null, null, null, null);// TODO implement specific method, consumes, produces, etc depending on your merging policies 
         return superclassRequestMappingInfo.combine(methodMapping); 
        } else 
         return methodMapping; 
       } 
    } 

Un'altra buona domanda è come intercettare la istanziazione di RequestMappingHandlerMapping. In Internet ci sono un certo numero di vari esempi per varie strategie di configurazione. Con JavaConfig, tuttavia, ricorda che se fornisci WebMvcConfigurationSupport nel set @Configuration, il tuo @EnableWebMvc (esplicito o implicito) smetterà di funzionare. Ho finito con il seguente:

@Configuration 
public class WebConfig extends DelegatingWebMvcConfiguration{ 

    @Configuration 
    public static class UnconditionalWebMvcAutoConfiguration extends WebMvcAutoConfiguration {//forces @EnableWebMvc 
    } 

    @Override 
    protected RequestMappingHandlerMapping createRequestMappingHandlerMapping() { 
     return new PathTweakingRequestMappingHandlerMapping(); 
    } 

    @Bean 
    @Primary 
    @Override 
    public RequestMappingHandlerMapping requestMappingHandlerMapping() { 
     return super.requestMappingHandlerMapping(); 
    } 

} 

ma vorrei conoscere modi migliori.