寻找一种解决方案,用另一个Component / Annotation扩展Spring MVC

时间:2013-08-18 03:56:49

标签: spring spring-mvc architecture

假设我有一个在普通模式(浏览器)和其他模式下使用的网站,例如MobileView模式(在移动应用程序内)。对于我创建的每个Controller,可能有MobileView的相应控制器,处理相同的URL。

最简单的解决方案是在所有具有MobileView逻辑的控制器中创建 ifs 。另一种解决方案是使用MobileView的对应URL(类似于普通URL)和两个独立的控制器(可能从另一个扩展;或使用其他方式回收公共代码)

但是,更优雅的解决方案是添加一些额外的注释,比如@SupportsMobileView(标记控制器,并告诉应用程序这将有一个对应的MobileView控制器)和@MobileViewController(标记第二个控制器,以及告诉应用程序该控制器需要在标有@SupportsMobileView的初始控制器后立即运行。普通控制器和MobileView控制器之间的链接将通过它们处理的URL(使用@RequestMapping定义)。

是否可以扩展Spring MVC(A)?在哪里注入新的注释扫描器(B)和注释处理程序/组件处理程序(C)?应该如何执行MobileView控制器(D)(现在我认为它可以通过AOP执行,我的新控制器类型的新处理程序以编程方式在相应的普通控制器上创建一个连接点)

注意我没有提到如何触发和检测此MobileView模式。我们只是说有一个Session布尔变量(标志)。

欢迎任何要点(A),(B),(C)或(D)的批评者,以及任何一点或整个解决方案的技术提示和替代解决方案。

2 个答案:

答案 0 :(得分:5)

HandlerInterceptor可用于拦截RequestMapping处理。 This是一个如何配置和实现一个的简单示例。

您可以检查您的会话变量,并且有一堆方法可以让您进行自定义处理,或者只是通过移动视图从普通控制器处理交换视图。

答案 1 :(得分:0)

好的,警告:

这只是一个概念验证 我理解必须这样做:

+ @ MobileViewEnable和@MobileView注释(及相关)方法需要保留在同一个控制器中

+没有检查所使用的httpAction

+这两种方法必须具有相同的签名

+ mobileView注释值和requestMapping注释值必须为equals和uniques

+ callYourLogic(..)中的逻辑定义了要调用的方法,此时有一个非常简单的逻辑,检查请求中是否存在参数(“mobile”),只是为了测试

+此代码不打算按原样使用

+不知道它是否适用于我的电脑以外(笑话:D,嗯......)

SO:

注解:

@Retention(RetentionPolicy.RUNTIME)
public @interface MobileView {
    String value() default "";
}

@Retention(RetentionPolicy.RUNTIME)
public @interface MobileViewEnable {

}

ExampleController:

@Controller
public class MainController extends BaseController {

    private final static Logger logger = LoggerFactory.getLogger(MainController.class);
    private final static String PROVA_ROUTE = "prova";

    @MobileViewEnable
    @RequestMapping(PROVA_ROUTE)
    public String prova() {
        logger.debug("inside prova!!!");
        return "provaview";
    }

    @MobileView(PROVA_ROUTE)
    public String prova2() {
        logger.debug("inside prova2!!!");
        return "prova2view";
    }
}

方面定义:

<bean id="viewAspect" class="xxx.yyy.ViewAspect" />

<aop:config>
    <aop:pointcut expression="@annotation(xxx.yyy.MobileViewEnable)" id="viewAspectPointcut" />
    <aop:aspect ref="viewAspect" order="1">
        <aop:around method="around" pointcut-ref="viewAspectPointcut" arg-names="viewAspectPointcut"/>
    </aop:aspect>
</aop:config>

方面实施:

public class ViewAspect implements BeforeAdvice, ApplicationContextAware {

        private final static Logger logger = LoggerFactory.getLogger(ViewAspect.class);

        private ApplicationContext applicationContext;

        public Object around(ProceedingJoinPoint joinPoint) {
            Method mobileViewAnnotatedMethod = null;
            HttpServletRequest request = getCurrentHttpRequest();
            String controllerName = getSimpleClassNameWithFirstLetterLowercase(joinPoint);
            Object[] interceptedMethodArgs = getInterceptedMethodArgs(joinPoint);
            String methodName = getCurrentMethodName(joinPoint);
            Method[] methods = getAllControllerMethods(joinPoint);
            Method interceptedMethod = getInterceptedMethod(methods, methodName);
            String interceptedMethodRoute = getRouteFromInterceptedMethod(interceptedMethod);

            if (callYourLogic(request)) {
                mobileViewAnnotatedMethod = getMobileViewAnnotatedMethodWithRouteName(methods, interceptedMethodRoute);
                if (mobileViewAnnotatedMethod != null)
                    return invokeMethod(mobileViewAnnotatedMethod, interceptedMethodArgs, controllerName);
            }

            return continueInterceptedMethodExecution(joinPoint, interceptedMethodArgs);
        }

        private Object continueInterceptedMethodExecution(ProceedingJoinPoint joinPoint, Object[] interceptedMethodArgs) {

            try {
                return joinPoint.proceed(interceptedMethodArgs);
            } catch (Throwable e) {
                logger.error("unable to proceed with intercepted method call: " + e);
            }

            return null;
        }

        private Object[] getInterceptedMethodArgs(JoinPoint joinPoint) {
            return joinPoint.getArgs();
        }

        private boolean callYourLogic(HttpServletRequest request) {
            // INSERT HERE YOUR CUSTOM LOGIC (e.g.: is the server accessed from a mobile device?)
            // THIS IS A STUPID LOGIC USED ONLY FOR EXAMPLE
            return request.getParameter("mobile")!= null;   
        }

        private HttpServletRequest getCurrentHttpRequest() {
            return ((ServletRequestAttributes) RequestContextHolder.currentRequestAttributes()).getRequest();
        }

        private String invokeMethod(Method method, Object[] methodArgs, String className) {
            if (method != null) {
                try {
                    Object classInstance = getInstanceOfClass(method, className);
                    return (String) method.invoke(classInstance, methodArgs);
                } catch (Exception e) {
                    logger.error("unable to invoke method" + method + " - " + e);
                }
            }
            return null;
        }

        private Object getInstanceOfClass(Method method, String className) {
            return applicationContext.getBean(className);
        }

        private Method getMobileViewAnnotatedMethodWithRouteName(Method[] methods, String routeName) {
            for (Method m : methods) {
                MobileView mobileViewAnnotation = m.getAnnotation(MobileView.class);
                if (mobileViewAnnotation != null && mobileViewAnnotation.value().equals(routeName))
                    return m;
            }
            return null;
        }

        private String getRouteFromInterceptedMethod(Method method) {
            RequestMapping requestMappingAnnotation = method.getAnnotation(RequestMapping.class);
            if (requestMappingAnnotation != null)
                return requestMappingAnnotation.value()[0];

            return null;
        }

        private String getCurrentMethodName(JoinPoint joinPoint) {
            return joinPoint.getSignature().getName();
        }

        private Method[] getAllControllerMethods(JoinPoint joinPoint) {
            return joinPoint.getThis().getClass().getSuperclass().getMethods();
        }

        private String getSimpleClassNameWithFirstLetterLowercase(JoinPoint joinPoint) {
            String simpleClassName = joinPoint.getThis().getClass().getSuperclass().getSimpleName();
            return setFirstLetterLowercase(simpleClassName);
        }

        private String setFirstLetterLowercase(String simpleClassName) {
            String firstLetterOfTheString = simpleClassName.substring(0, 1).toLowerCase();
            String restOfTheString = simpleClassName.substring(1);
            return firstLetterOfTheString + restOfTheString;

        }

        private Method getInterceptedMethod(Method[] methods, String lookingForMethodName) {
            for (Method m : methods)
                if (m.getName().equals(lookingForMethodName))
                    return m;

            return null;
        }

        @Override
        public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
            this.applicationContext = applicationContext;
        }

    }