开发者

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 ???

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜