Spring annotation with xml mixing problem
I'm trying to find an optimal way to use the latest Spring 3.0. I really like the @RequestMapping annotation with all features applied to it. However, what I don't like, is that the URL bound to the action should be full开发者_JS百科y specified in the java file.
It would be the best to somehow send the whole url-binding configuration to the context xml file. However, it would also do if that url-binding could be moved to xml at least partially.
This is what I mean:
Current code:
@Controller
@RequestMapping("myController")
class MyController {
@RequestMapping("**/someMethod")
String someMethod(...) {
}
}
This code binds the myController/someMethod to MyController::someMethod. What I don't like here is that "myController" part binding is also in this java file. I want to make it as modular as possible, and this part plays very bad for me.
What I'd like to see is something like this, to achieve the same result:
context.xml
<mapping>
<url>myController</url>
<controller>MyController</controller>
</mapping>
java
@Controller
//-- No request mapping here --// @RequestMapping("myController")
class MyController {
@RequestMapping("**/someMethod")
String someMethod(...) {
}
}
Is something like this possible on annotated controllers in Spring 3?
As requested. You want to create your own URL pattern without Spring controllers annotations.
First of all, create a CustomController annotation To avoid to be detected by @Controller HandlerMapping
package br.com.ar.web.stereotype;
@Target(value=TYPE)
@Retention(value=RUNTIME)
@Component
public @interface CustomController {}
Here goes our AccountController
@CustomController
public class AccountController {
public void form(Long id) {
// do something
}
}
Our HandlerAdapter - It takes care of calling our controller - Someting similar to Spring Validator interface approach
package br.com.ar.web.support;
public class CustomHandlerAdapter implements HandlerAdapter {
public boolean supports(Object handler) {
Annotation [] annotationArray = handler.getClass().getAnnotations();
for(Annotation annotation: annotationArray) {
/**
* Make sure your annotation contains @SomeController
*/
}
}
/**
* Third parameter is our CustomController
*/
public ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
Method[] methods = handler.getClass().getMethods();
/**
* Logic To verify whether Target method fullfil request goes here
*/
/**
* It can be useful To see MultiActionController.invokeNamedMethod and MultiActionController.isHandlerMethod
*/
method.invoke(// parameters goes here);
}
public long getLastModified(HttpServletRequest request, Object handler) {
return -1;
}
}
And finally, our HandlerMapping. Make sure your HandlerMapping extends WebApplicationObjectSupport. It allows you To retrieve any Spring managed bean by calling
getApplicationContext().getBean(beanName);
package br.com.ar.web.servlet.handler;
public class CustomeHandlerMapping extends WebApplicationObjectSupport implements HandlerMapping, Ordered {
private static final String CUSTOM_HANDLER_ADAPTER_NAME = "CUSTOM_HANDLER_ADAPTER_NAME";
/**
* Bind each URL path-CustomController bean name
*/
private final Map handlerMap = new LinkedHashMap();
/**
* Ordered interface will make sure your HandlerMapping should be intercepted BEFORE or AFTER DefaultAnnotationHandlerMapping
*/
public final void setOrder(int order) {
this.order = order;
}
public final int getOrder() {
return this.order;
}
/**
* HandlerMapping interface method
*/
public final HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
String url = extractUrl(request);
if(handlerMap.get(url) == null) {
/**
* Because Spring 3.0 controller is stateful
* Let's just store CustomController class (Not an instance) in ApplicationContext
*
* Or use a FactoryBean to retrieve your CustomController
*/
handlerMap.put(url, getApplicationContext().getBean(beanName));
}
/**
* instantiateClass needs no-arg constructor
*/
Object handler = BeanUtils.instantiateClass(handlerMap.get(url));
return new HandlerExecutionChain(handler);
}
private String extractUrl(HttpServletRequest request) {
/**
* Here goes code needed To retrieve URL path from request
*
* Take a look at AntPathMatcher, UrlPathHelper and PathMatcher
*
* It can be useful To see AbstractUrlHandlerMapping.getHandlerInternal method
*/
}
}
Do not forget register both HandlerAdapter and HandlerMapping
<bean id="br.com.ar.web.servlet.handler.CustomHandlerMapping"/>
<bean id="br.com.ar.web.support.CustomHandlerAdapter"/>
<!--To allow Spring 3.0 controller-->
<bean class="org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter"/>
<bean class="org.springframework.web.servlet.mvc.annotation.DefaultAnnotationHandlerMapping"/>
I hope it can gives you a good kick off
The sequence (Behind The scenes) Spring DispatcherServlet will call our objects are
/**
* Our HandlerMapping goes here
*/
HandlerMapping handlerMapping = getHandler(request);
HandlerExecutionChain handlerExecutionChain = handlerMapping.getHandler(request);
for(HandlerInterceptor interceptor: handlerExecutionChain.getInterceptors) {
interceptor.preHandle(request, response, handlerExecutionChain.getHandler());
}
/**
* Our CustomController goes here
*/
Object handler = handlerExecutionChain.getHandler();
/**
* Our CustomHandlerAdapter goes here
*/
HandlerAdapter handlerAdapter = getHandlerAdapter(handler);
ModelAndView mav = handlerAdapter.handle(request, response, handler);
for(HandlerInterceptor interceptor: handlerExecutionChain.getInterceptors) {
interceptor.postHandle(request, response, handlerExecutionChain.getHandler());
}
You can combine XML and annotation-style mappings with a considerable amount of flexibility. Both can use Ant-style wildcard matching, so you can do things like this (not tested, but gives you the general idea):
<bean class="SimpleUrlHandlerMapping">
<property name="mappings">
<map>
<entry key="myController/**" value-ref="myController"/>
</map>
</property>
</bean>
<bean id="myController" class="MyController"/>
And then
@Controller
class MyController {
@RequestMapping("**/someMethod")
String someMethod(...) {
}
}
The URL /myController/someMethod
should then match that method.
You might need to play with a bit to get it to work, but that's the gist of it.
However, what I do not like, is that the URL bound to the action should be fully specified in the java file
So rely on ControllerClassNameHandlerMapping
<bean class="org.springframework.web.servlet.mvc.support.ControllerClassNameHandlerMapping"/>
Keep in mind ControllerNameHandlerMapping remove Controller suffix if it exists and return the remaining text, lower-cased If you just want The first letter lower-cased, set caseSensitive property as true
Suppose here goes your controller
package br.com.ar.view.resources;
@Controller
public class UserController {
/**
* mapped To /user/form
*/
@RequestMapping(method=RequesMethod.GET)
public void form(Model model) {
model.add(categoryRepository().getCategoryList());
}
/**
* mapped To user/form
*/
@RequestMapping(method=RequesMethod.POST)
public void form(User user) {
userRepository.add(user);
}
}
There is more: if you use a modularized app, you can rely on basePackage property. Suppose you have financial and human resources module like
br.com.ar.view.financial.AccountController;
br.com.ar.view.resources.ManagementController;
You define your base package
<bean class="org.springframework.web.servlet.mvc.support.ControllerClassNameHandlerMapping">
<property name="basePackage" value="br.com.ar.view"/>
</bean>
You can call your AccountController form method as
/financial/account/form
And you can call your ManagementController form method as
/resources/management/form
As i am pretty sure you use default TranslateToViewName convention over configuration, your directory structure should looks like
/WEB-INF
/view
/financial
/user
form.jsp
/resources
/management
form.jsp
Do not forget define your InternalResourceViewResolver
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="prefix" value="/view/"/>
<property name="suffix" value=".jsp"/>
</bean>
And finally, If your request does not need a controller. No problem, define your defaultHandler property
<bean class="org.springframework.web.servlet.mvc.support.ControllerClassNameHandlerMapping">
<property name="basePackage" value="br.com.ar.view"/>
<property name="caseSensitive" value="true"/>
<property name="defaultHandler">
<bean class="org.springframework.web.servlet.mvc.UrlFilenameViewController"/>
</property>
</bean>
Now if you call, for instance, /index.htm (I suppose your DispatcherServlet intercepts htm extension) and you do not have any IndexController, Spring will look for
/WEB-INF/view/index.jsp
Good, do not ???
精彩评论