12

这是一个交叉帖子。我也在春季论坛上发布了同样的问题。http://forum.springsource.org/showthread.php?128579-Database-driven-Controller-Mapping

您好我正在尝试进行数据库驱动的控制器映射,以便它们可以在运行时更改。

到目前为止,我所拥有的如下。

自定义处理程序适配器,以后总是可以优化。

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

}

我的 webmvc-config 如下所示(相关部分)

代码:

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

这似乎选择了正确的控制器。但是,当转到数据库定义的路径(例如“/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)
        ....

我需要定义一个自定义注释处理程序吗?

老实说,这整个过程似乎比它应该的更困难。我想要 1 个控制器来处理对外部定义的 url 路径的所有请求,这是正确的解决方法。

如果可能的话,我还想将匹配到控制器的对象传递给控制器​​,而不是在控制器中进行新的查找。这基本上将形成我的视图模型。

关于如何使它工作的任何建议?

编辑 为了记录,NPE在这里

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

另一个 从 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>

我已经在下面自己回答了这个问题,但我仍然对人们权衡正确的方法很感兴趣。

4

2 回答 2

3

显然,从这里和spring 论坛上缺乏相反的答案来看,似乎在 spring 框架内没有更简单的方法可以做到这一点。

然而,我设法让它工作,我在 github 上共享了一个项目,该项目可以使用 maven 构建,添加 4 个类以简化动态添加类的过程。这个项目可以在https://github.com/Athas1980/MvcBackingBean找到。我还将分享另一个项目以证明它有效。

感谢 Marten Deinum 和 Rossen Stoyanchev


对于那些对如何自己实现这一目标感兴趣的人,您需要执行以下操作

  1. 实现 HandlerMapper 的实例 这为您提供了控制器类和您要映射到的 url 之间的映射。

    //   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. 实现 HandlerMethodArgumentResolver 以从会话中获取值。(假设您对会话中的设置感兴趣)

    //   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. 实现自定义 WebArgumentResolver 以获取传递的 Bean 的实例。将此设置为 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. 我还创建了一个 BackingBean 注释和一个接口来传递给我的处理程序添加器,因为我觉得它们更容易。

  5. 创建你的控制器。如果您使用我的代码,您将希望使用 @BackingBean 注释注入参数。控制器本身的请求映射不能匹配任何好的 url(这是因为我们使用处理程序适配器绕过了这一步,并且我们不希望默认的注解处理程序获取它。

  6. 在春天把所有东西都连接起来。这是我的工作示例项目中的示例文件。

    <?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>
    
于 2012-07-20T22:15:27.557 回答
2

只是为了解决这个特定问题,让我现在推荐一种解决方法 -

在内部创建自己的 handlerAdapter 来组成 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>

这应该可以解决当前问题,但您可能会遇到其他问题

于 2012-07-20T18:17:31.790 回答