diff --git a/spring-messaging/src/main/java/org/springframework/messaging/handler/invocation/InvocableHandlerMethod.java b/spring-messaging/src/main/java/org/springframework/messaging/handler/invocation/InvocableHandlerMethod.java index b0005f8ce1..78c4c3a7db 100644 --- a/spring-messaging/src/main/java/org/springframework/messaging/handler/invocation/InvocableHandlerMethod.java +++ b/spring-messaging/src/main/java/org/springframework/messaging/handler/invocation/InvocableHandlerMethod.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2013 the original author or authors. + * Copyright 2002-2014 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -183,11 +183,11 @@ public class InvocableHandlerMethod extends HandlerMethod { private Object invoke(Object... args) throws Exception { ReflectionUtils.makeAccessible(this.getBridgedMethod()); try { + assertTargetBean(getBridgedMethod(), getBean(), args); return getBridgedMethod().invoke(getBean(), args); } catch (IllegalArgumentException e) { - String msg = getInvocationErrorMessage(e.getMessage(), args); - throw new IllegalArgumentException(msg, e); + throw new IllegalArgumentException(getInvocationErrorMessage(e.getMessage(), args), e); } catch (InvocationTargetException e) { // Unwrap for HandlerExceptionResolvers ... @@ -208,6 +208,25 @@ public class InvocableHandlerMethod extends HandlerMethod { } } + /** + * Assert that the target bean class is an instance of the class where the given + * method is declared. In some cases the actual controller instance at request- + * processing time may be a JDK dynamic proxy (lazy initialization, prototype + * beans, and others). {@code @Controller}'s that require proxying should prefer + * class-based proxy mechanisms. + */ + private void assertTargetBean(Method method, Object targetBean, Object[] args) { + Class methodDeclaringClass = method.getDeclaringClass(); + Class targetBeanClass = targetBean.getClass(); + if (!methodDeclaringClass.isAssignableFrom(targetBeanClass)) { + String message = "The mapped controller method class '" + methodDeclaringClass.getName() + + "' is not an instance of the actual controller bean instance '" + + targetBeanClass.getName() + "'. If the controller requires proxying " + + "(e.g. due to @Transactional), please use class-based proxying."; + throw new IllegalArgumentException(getInvocationErrorMessage(message, args)); + } + } + private String getInvocationErrorMessage(String message, Object[] resolvedArgs) { StringBuilder sb = new StringBuilder(getDetailedErrorMessage(message)); sb.append("Resolved arguments: \n"); diff --git a/spring-web/src/main/java/org/springframework/web/method/support/InvocableHandlerMethod.java b/spring-web/src/main/java/org/springframework/web/method/support/InvocableHandlerMethod.java index 4942bf0922..a0b0cd6276 100644 --- a/spring-web/src/main/java/org/springframework/web/method/support/InvocableHandlerMethod.java +++ b/spring-web/src/main/java/org/springframework/web/method/support/InvocableHandlerMethod.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2013 the original author or authors. + * Copyright 2002-2014 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -211,11 +211,11 @@ public class InvocableHandlerMethod extends HandlerMethod { private Object invoke(Object... args) throws Exception { ReflectionUtils.makeAccessible(this.getBridgedMethod()); try { + assertTargetBean(getBridgedMethod(), getBean(), args); return getBridgedMethod().invoke(getBean(), args); } catch (IllegalArgumentException e) { - String msg = getInvocationErrorMessage(e.getMessage(), args); - throw new IllegalArgumentException(msg, e); + throw new IllegalArgumentException(getInvocationErrorMessage(e.getMessage(), args), e); } catch (InvocationTargetException e) { // Unwrap for HandlerExceptionResolvers ... @@ -236,6 +236,25 @@ public class InvocableHandlerMethod extends HandlerMethod { } } + /** + * Assert that the target bean class is an instance of the class where the given + * method is declared. In some cases the actual controller instance at request- + * processing time may be a JDK dynamic proxy (lazy initialization, prototype + * beans, and others). {@code @Controller}'s that require proxying should prefer + * class-based proxy mechanisms. + */ + private void assertTargetBean(Method method, Object targetBean, Object[] args) { + Class methodDeclaringClass = method.getDeclaringClass(); + Class targetBeanClass = targetBean.getClass(); + if (!methodDeclaringClass.isAssignableFrom(targetBeanClass)) { + String message = "The mapped controller method class '" + methodDeclaringClass.getName() + + "' is not an instance of the actual controller bean instance '" + + targetBeanClass.getName() + "'. If the controller requires proxying " + + "(e.g. due to @Transactional), please use class-based proxying."; + throw new IllegalArgumentException(getInvocationErrorMessage(message, args)); + } + } + private String getInvocationErrorMessage(String message, Object[] resolvedArgs) { StringBuilder sb = new StringBuilder(getDetailedErrorMessage(message)); sb.append("Resolved arguments: \n"); diff --git a/src/asciidoc/index.adoc b/src/asciidoc/index.adoc index 8fee5fb45d..6fcb274ead 100644 --- a/src/asciidoc/index.adoc +++ b/src/asciidoc/index.adoc @@ -28381,7 +28381,7 @@ snippet: [[mvc-ann-requestmapping]] -==== Mapping Requests With Using @RequestMapping +==== Mapping Requests With @RequestMapping You use the `@RequestMapping` annotation to map URLs such as `/appointments` onto an entire class or a particular handler method. Typically the class-level annotation maps a @@ -28472,26 +28472,17 @@ application shows a multi-action controller using `@RequestMapping`: } ---- -.@RequestMapping On Interface Methods -[TIP] -==== - -A common pitfall when working with annotated controller classes happens when applying -functionality that requires creating a proxy for the controller object (e.g. -`@Transactional` methods). Usually you will introduce an interface for the controller in -order to use JDK dynamic proxies. To make this work you must move the `@RequestMapping` -annotations, as well as any other type and method-level annotations (e.g. -`@ModelAttribute`, `@InitBinder`) to the interface as well as the mapping mechanism can -only "see" the interface exposed by the proxy. Alternatively, you could activate -`proxy-target-class="true"` in the configuration for the functionality applied to the -controller (in our transaction scenario in ``). Doing so -indicates that CGLIB-based subclass proxies should be used instead of interface-based -JDK proxies. For more information on various proxying mechanisms see <>. - -Note however that method argument annotations, e.g. `@RequestParam`, must be present in -the method signatures of the controller class. -==== +[[mvc-ann-requestmapping-proxying]] +===== `@Controller`'s and AOP Proxying +In some cases a controller may need to be decorated with an AOP proxy at runtime. +One example is if you choose to have `@Transactional` annotations directly on the +controller. When this is the case, for controllers specifically, we recommend +using class-based proxying. This is typically the default choice with controllers. +However if a controller must implement an interface that is not a Spring Context +callback (e.g. `InitializingBean`, `*Aware`, etc), you may need to explicitly +configure class-based proxying. For example with ``, +change to ``. [[mvc-ann-requestmapping-31-vs-30]] ===== New Support Classes for @RequestMapping methods in Spring MVC 3.1 @@ -29465,14 +29456,6 @@ attribute name: } ---- -[NOTE] -==== -When using controller interfaces (e.g., for AOP proxying), make sure to consistently put -__all__ your mapping annotations - such as `@RequestMapping` and `@SessionAttributes` - -on the controller __interface__ rather than on the implementation class. -==== - - [[mvc-ann-redirect-attributes]] ===== Specifying redirect and flash attributes By default all model attributes are considered to be exposed as URI template variables