Cercando di creare URL REST-pieni con più punti nella parte “filename” – MVC Spring 3.0

Sto usando Spring MVC (3.0) con controller basati su annotazioni. Vorrei creare URL REST-ful per le risorse ed essere in grado di non richiedere (ma ancora facoltativamente consentire) l’estensione del file alla fine dell’URL (ma assumere il tipo di contenuto HTML se non ci sono estensioni). Funziona immediatamente con Spring MVC finché non ci sono punti (punto / punto) nella parte del nome file.

Tuttavia alcuni dei miei URL richiedono un identificatore con punti nel nome. Ad esempio in questo modo:

http://company.com/widgets/123.456.789.500 

In questo caso, Spring cerca un tipo di contenuto per l’estensione .500 e non trova nessuno, quindi errori. Posso usare work-arounds come aggiungere .html alla fine, codificare l’identificatore o aggiungere una barra finale. Non sono contento di nessuno se questi, ma potrebbe probabilmente vivere con l’aggiunta di .html .

Ho cercato invano un modo per ignorare il rilevamento dell’estensione file predefinito in spring.

È ansible personalizzare o disabilitare il rilevamento delle estensioni di file per un determinato metodo di controller o pattern URL, ecc.?

La corrispondenza del pattern @PathVariable è un po ‘nervosa quando si tratta di punti nell’URL (vedere SPR-5778 ). Puoi renderlo meno nervoso (ma più schizzinoso), e ottenere un controllo migliore sugli URL più pesanti, impostando la proprietà useDefaultSuffixPattern su DefaultAnnotationHandlerMapping su false .

Se non hai già dichiarato esplicitamente un DefaultAnnotationHandlerMapping nel tuo contesto (e la maggior parte delle persone non lo fa poiché è dichiarato implicitamente per te), puoi aggiungerlo in modo esplicito e impostare tale proprietà.

Probabilmente, è un brutto scherzo, volevo solo esplorare l’estensibilità di Spring @MVC. Ecco un PathMatcher personalizzato. Usa $ nel pattern come marker di fine – se il pattern termina con esso, il marker viene rimosso e il pattern è abbinato dal matcher di default, ma se pattern ha $ nel mezzo (es. ...$.* ), Tale pattern non è abbinato

 public class CustomPathMatcher implements PathMatcher { private PathMatcher target; public CustomPathMatcher() { target = new AntPathMatcher(); } public String combine(String pattern1, String pattern2) { return target.combine(pattern1, pattern2); } public String extractPathWithinPattern(String pattern, String path) { if (isEncoded(pattern)) { pattern = resolvePattern(pattern); if (pattern == null) return ""; } return target.extractPathWithinPattern(pattern, path); } public Map extractUriTemplateVariables(String pattern, String path) { if (isEncoded(pattern)) { pattern = resolvePattern(pattern); if (pattern == null) return Collections.emptyMap(); } return target.extractUriTemplateVariables(pattern, path); } public Comparator getPatternComparator(String pattern) { final Comparator targetComparator = target.getPatternComparator(pattern); return new Comparator() { public int compare(String o1, String o2) { if (isEncoded(o1)) { if (isEncoded(o2)) { return 0; } else { return -1; } } else if (isEncoded(o2)) { return 1; } return targetComparator.compare(o1, o2); } }; } public boolean isPattern(String pattern) { if (isEncoded(pattern)) { pattern = resolvePattern(pattern); if (pattern == null) return true; } return target.isPattern(pattern); } public boolean match(String pattern, String path) { if (isEncoded(pattern)) { pattern = resolvePattern(pattern); if (pattern == null) return false; } return target.match(pattern, path); } public boolean matchStart(String pattern, String path) { if (isEncoded(pattern)) { pattern = resolvePattern(pattern); if (pattern == null) return false; } return target.match(pattern, path); } private boolean isEncoded(String pattern) { return pattern != null && pattern.contains("$"); } private String resolvePattern(String pattern) { int i = pattern.indexOf('$'); if (i < 0) return pattern; else if (i == pattern.length() - 1) { return pattern.substring(0, i); } else { String tail = pattern.substring(i + 1); if (tail.startsWith(".")) return null; else return pattern.substring(0, i) + tail; } } } 

config:

        

E l'uso (dato "/hello/1.2.3", il value è "1.2.3"):

 @RequestMapping(value = "/hello/{value}$", method = RequestMethod.GET) public String hello(@PathVariable("value") String value, ModelMap model) 

EDIT:: Ora non infrange la regola "barra finale non importa"

  

@Controller public class MyController { @RequestMapping(value="/widgets/{preDot}.{postDot}") public void getResource(@PathVariable String preDot, @PathVariable String postDot) { String fullPath = preDot + "." + postDot; //... } }

// Il codice sopra deve corrispondere a /widgets/111.222.333.444

Spring 3.2 è cambiato e suggerisce di impostare le proprietà sul bean RequestMappingHandlerMapping , esplicitamente (se non si utilizza lo spazio dei nomi mvc) o utilizzando un BeanPostProcessor come il seguente (è necessario eseguirne la scansione o un’istanza):

 @Component public class IncludeExtensionsInRequestParamPostProcessor implements BeanPostProcessor { @Override public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException { if (bean instanceof RequestMappingHandlerMapping) { RequestMappingHandlerMapping mapping = (RequestMappingHandlerMapping)bean; mapping.setUseRegisteredSuffixPatternMatch(false); mapping.setUseSuffixPatternMatch(false); } return bean; } @Override public Object postProcessAfterInitialization(Object bean, String beanName) { return bean; } } 

Puoi anche aggiungere :.* tuo @RequestMapping, ad esempio "/{documentPath:.*}" (vedi commento JIRA)

Ho avuto lo stesso problema e l’ho risolto anche con PathMatcher personalizzato. La mia soluzione è in qualche modo più semplice rispetto a quanto proposto da axtavt. My PathMatcher ha anche un bersaglio finale AntPathMatcher privato e delega tutte le chiamate invariate, ad eccezione del metodo match ():

 @Override public boolean match(String pattern, String path) { return pattern.endsWith(".*") ? false : target.match(pattern, path); } 

Funziona perché Spring cerca di abbinare i controller aggiungendo “. Alla fine. Ad esempio, con mapping del percorso “/ widgets / {id}” e URL “/widgets/1.2.3.4”, Spring tenta la prima corrispondenza “/ widget / {id}. ” E dopo “/ widget / {id}” . Il primo corrisponderà, ma lascerà solo “1.2.3” per id.

Il mio PatchMatcher rifiuta specificamente i pattern che terminano “. *”, Quindi il primo tentativo fallisce e il secondo corrisponde.

Se si utilizza ContentNegotiatingViewResolver, è ansible specificare il tipo di contenuto nell’URL utilizzando il parametro di richiesta “format” (se il parametro favorParameter è impostato su true).

-jarppe

JFY: nella spring 4 questo problema è stato risolto tramite: WebMvcConfigurerAdapter.

 @Configuration class MvcConfiguration extends WebMvcConfigurerAdapter { @Override public void configurePathMatch(PathMatchConfigurer configurer) { configurer.setUseSuffixPatternMatch(false); } } 

Oppure tramite WebMvcConfigurationSupport come qui .

Per aggiungere alla risposta di skaffman, se stai usando e vuoi sovrascrivere il valore useDefaultSuffixPattern, puoi sostituire il con quanto segue: