Improve info on use of @Controller's with aop proxying
Before this change, issues surrounding the use of @Controller's in combination with AOP proxying, resulted in an IllegalArgumentException when trying to invoke the controller method. This change detects such cases proactively and reports them with a clear recommendation to use class-based proxying when it comes to @Controller's. This is the most optimcal approach for controllers in many respects, also allows @MVC annotations to remain on the class. The documentation has also been updated to have a specific section on @Controller's and AOP proxying providing the same advice. Issue:SPR-11281
This commit is contained in:
parent
12598f8581
commit
7301b58ec9
|
|
@ -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");
|
||||
|
|
|
|||
|
|
@ -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");
|
||||
|
|
|
|||
|
|
@ -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 `<tx:annotation-driven />`). 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 <<aop-proxying>>.
|
||||
|
||||
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 `<tx:annotation-driven />`,
|
||||
change to `<tx:annotation-driven proxy-target-class="true" />`.
|
||||
|
||||
[[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
|
||||
|
|
|
|||
Loading…
Reference in New Issue