I am trying to add a RepositoryEventHandler as described on Spring Data REST documentation to the REST repository shown below:
#RepositoryRestResource(collectionResourceRel = "agents", path = "/agents")
public interface AgentRepository extends CrudRepository<Agent, Long> {
// no implementation required; Spring Data will create a concrete Repository
}
I created an AgentEventHandler:
#Component
#RepositoryEventHandler(Agent.class)
public class AgentEventHandler {
/**
* Called before {#link Agent} is persisted
*
* #param agent
*/
#HandleBeforeSave
public void handleBeforeSave(Agent agent) {
System.out.println("Saving Agent " + agent.toString());
}
}
and declared it in a #Configuration component:
#Configuration
public class RepositoryConfiguration {
/**
* Declare an instance of the {#link AgentEventHandler}
*
* #return
*/
#Bean
AgentEventHandler agentEvenHandler() {
return new AgentEventHandler();
}
}
When I am POSTing to the REST resource, the Entity gets persisted but the method handleBeforeSave never gets invoked. What am I missing?
I'm using: Spring Boot 1.1.5.RELEASE
Sometimes obvious mistakes go unnoticed.
POST-ing a Spring Data REST resource, emits a BeforeCreateEvent. To catch this event, the method handleBeforeSave must be annotated with #HandleBeforeCreate instead of #HandleBeforeSave (the latter gets invoked on PUT and PATCH HTTP calls).
Tests pass successfully on my (cleaned up) demo app now.
How does your main Application class look like? Does it import the RepositoryRestMvcConfiguration as described in https://spring.io/guides/gs/accessing-data-rest/?
Related
In a Spring Boot Web MVC REST service I want to use the operation ID and path values from SpringDoc generated OpenAPI from within the service where its generated. How can I get the OpenAPI JSON doc without going through the web endpoint?
If I understand you correctly:
You want to get OpenAPI documentation in JSON format inside code your Spring application.
I do it this way:
1.) Create a component that extends from the OpenApiResource class.
And create a getOpenApiJson method that calls getOpenApi() (creating or receiving an OpenApi model) and writeJsonValue() (serialization of OpenAPI).
#Component
public class CustomOpenApiResource extends OpenApiResource {
public CustomOpenApiResource(ObjectFactory<OpenAPIService> openAPIBuilderObjectFactory,
AbstractRequestService requestBuilder,
GenericResponseService responseBuilder,
OperationService operationParser,
Optional<List<OperationCustomizer>> operationCustomizers,
Optional<List<OpenApiCustomiser>> openApiCustomisers,
Optional<List<OpenApiMethodFilter>> methodFilters,
SpringDocConfigProperties springDocConfigProperties,
SpringDocProviders springDocProviders) {
super(openAPIBuilderObjectFactory,
requestBuilder,
responseBuilder,
operationParser,
operationCustomizers,
openApiCustomisers,
methodFilters,
springDocConfigProperties,
springDocProviders);
}
#Override
protected String getServerUrl(HttpServletRequest request, String apiDocsUrl) {
/**
* How to implement this method you can find out for example from OpenApiWebMvcResource
*/
return "";
}
public String getOpenApiJson() throws JsonProcessingException {
return writeJsonValue(getOpenApi(Locale.getDefault()));
}
}
2.) Inject CustomOpenApiResource component
#Autowired
private CustomOpenApiResource resource;
And use getOpenApiJson()
String openApiJson = resource.getOpenApiJson();
Spring cache is not working when calling cached method from another method of the same bean.
Here is an example to explain my problem in clear way.
Configuration:
<cache:annotation-driven cache-manager="myCacheManager" />
<bean id="myCacheManager" class="org.springframework.cache.ehcache.EhCacheCacheManager">
<property name="cacheManager" ref="myCache" />
</bean>
<!-- Ehcache library setup -->
<bean id="myCache"
class="org.springframework.cache.ehcache.EhCacheManagerFactoryBean" p:shared="true">
<property name="configLocation" value="classpath:ehcache.xml"></property>
</bean>
<cache name="employeeData" maxElementsInMemory="100"/>
Cached service :
#Named("aService")
public class AService {
#Cacheable("employeeData")
public List<EmployeeData> getEmployeeData(Date date){
..println("Cache is not being used");
...
}
public List<EmployeeEnrichedData> getEmployeeEnrichedData(Date date){
List<EmployeeData> employeeData = getEmployeeData(date);
...
}
}
Result :
aService.getEmployeeData(someDate);
output: Cache is not being used
aService.getEmployeeData(someDate);
output:
aService.getEmployeeEnrichedData(someDate);
output: Cache is not being used
The getEmployeeData method call uses cache employeeData in the second call as expected. But when the getEmployeeData method is called within the AService class (in getEmployeeEnrichedData), Cache is not being used.
Is this how spring cache works or am i missing something ?
I believe this is how it works. From what I remember reading, there is a proxy class generated that intercepts all requests and responds with the cached value, but 'internal' calls within the same class will not get the cached value.
From https://code.google.com/p/ehcache-spring-annotations/wiki/UsingCacheable
Only external method calls coming in through the proxy are
intercepted. This means that self-invocation, in effect, a method
within the target object calling another method of the target object,
will not lead to an actual cache interception at runtime even if the
invoked method is marked with #Cacheable.
Since Spring 4.3 the problem could be solved using self-autowiring over #Resource annotation:
#Component
#CacheConfig(cacheNames = "SphereClientFactoryCache")
public class CacheableSphereClientFactoryImpl implements SphereClientFactory {
/**
* 1. Self-autowired reference to proxified bean of this class.
*/
#Resource
private SphereClientFactory self;
#Override
#Cacheable(sync = true)
public SphereClient createSphereClient(#Nonnull TenantConfig tenantConfig) {
// 2. call cached method using self-bean
return self.createSphereClient(tenantConfig.getSphereClientConfig());
}
#Override
#Cacheable(sync = true)
public SphereClient createSphereClient(#Nonnull SphereClientConfig clientConfig) {
return CtpClientConfigurationUtils.createSphereClient(clientConfig);
}
}
The example below is what I use to hit the proxy from within the same bean, it is similar to #mario-eis' solution, but I find it a bit more readable (maybe it's not:-). Anyway, I like to keep the #Cacheable annotations at the service level:
#Service
#Transactional(readOnly=true)
public class SettingServiceImpl implements SettingService {
#Inject
private SettingRepository settingRepository;
#Inject
private ApplicationContext applicationContext;
#Override
#Cacheable("settingsCache")
public String findValue(String name) {
Setting setting = settingRepository.findOne(name);
if(setting == null){
return null;
}
return setting.getValue();
}
#Override
public Boolean findBoolean(String name) {
String value = getSpringProxy().findValue(name);
if (value == null) {
return null;
}
return Boolean.valueOf(value);
}
/**
* Use proxy to hit cache
*/
private SettingService getSpringProxy() {
return applicationContext.getBean(SettingService.class);
}
...
See also Starting new transaction in Spring bean
Here is what I do for small projects with only marginal usage of method calls within the same class. In-code documentation is strongly advidsed, as it may look strage to colleagues. But its easy to test, simple, quick to achieve and spares me the full blown AspectJ instrumentation. However, for more heavy usage I'd advice the AspectJ solution.
#Service
#Scope(proxyMode = ScopedProxyMode.TARGET_CLASS)
class AService {
private final AService _aService;
#Autowired
public AService(AService aService) {
_aService = aService;
}
#Cacheable("employeeData")
public List<EmployeeData> getEmployeeData(Date date){
..println("Cache is not being used");
...
}
public List<EmployeeEnrichedData> getEmployeeEnrichedData(Date date){
List<EmployeeData> employeeData = _aService.getEmployeeData(date);
...
}
}
If you call a cached method from same bean it will be treated as a private method and annotations will be ignored
Yes, the caching will not happen because of the reasons that were already mentioned in the other posts. However I would solve the problem by putting that method to its own class (service in this case). With that your code will be easier to maintain/test and understand.
#Service // or #Named("aService")
public class AService {
#Autowired //or how you inject your dependencies
private EmployeeService employeeService;
public List<EmployeeData> getEmployeeData(Date date){
employeeService.getEmployeeData(date);
}
public List<EmployeeEnrichedData> getEmployeeEnrichedData(Date date){
List<EmployeeData> employeeData = getEmployeeData(date);
...
}
}
#Service // or #Named("employeeService")
public class EmployeeService {
#Cacheable("employeeData")
public List<EmployeeData> getEmployeeData(Date date){
println("This will be called only once for same date");
...
}
}
In my Case I add variable :
#Autowired
private AService aService;
So I call the getEmployeeData method by using the aService
#Named("aService")
public class AService {
#Cacheable("employeeData")
public List<EmployeeData> getEmployeeData(Date date){
..println("Cache is not being used");
...
}
public List<EmployeeEnrichedData> getEmployeeEnrichedData(Date date){
List<EmployeeData> employeeData = aService.getEmployeeData(date);
...
}
}
It will use the cache in this case.
Better approach should be creating another service like ACachingService and call ACachingService.cachingMethod() instead of self Autowiring ( or any other approach trying to self inject). This way you do not fall into Circular dependency, which may be resulted in warning/error when upgrade to newer Spring ( Spring 2.6.6 in my case ) :
ERROR o.s.boot.SpringApplication - Application run failed
org.springframework.beans.factory.BeanCurrentlyInCreationException:
Error creating bean with name 'webSecurityConfig':
Requested bean is currently in creation: Is there an unresolvable circular reference?
We looked at all the solutions here and decided to use a separate class for the cached methods because Spring 5 doesn't like circular dependencies.
Use static weaving to create proxy around your bean. In this case even 'internal' methods would work correctly
I use internal inner bean (FactoryInternalCache) with real cache for this purpose:
#Component
public class CacheableClientFactoryImpl implements ClientFactory {
private final FactoryInternalCache factoryInternalCache;
#Autowired
public CacheableClientFactoryImpl(#Nonnull FactoryInternalCache factoryInternalCache) {
this.factoryInternalCache = factoryInternalCache;
}
/**
* Returns cached client instance from cache.
*/
#Override
public Client createClient(#Nonnull AggregatedConfig aggregateConfig) {
return factoryInternalCache.createClient(aggregateConfig.getClientConfig());
}
/**
* Returns cached client instance from cache.
*/
#Override
public Client createClient(#Nonnull ClientConfig clientConfig) {
return factoryInternalCache.createClient(clientConfig);
}
/**
* Spring caching feature works over AOP proxies, thus internal calls to cached methods don't work. That's why
* this internal bean is created: it "proxifies" overloaded {#code #createClient(...)} methods
* to real AOP proxified cacheable bean method {#link #createClient}.
*
* #see Spring Cache #Cacheable - not working while calling from another method of the same bean
* #see Spring cache #Cacheable method ignored when called from within the same class
*/
#EnableCaching
#CacheConfig(cacheNames = "ClientFactoryCache")
static class FactoryInternalCache {
#Cacheable(sync = true)
public Client createClient(#Nonnull ClientConfig clientConfig) {
return ClientCreationUtils.createClient(clientConfig);
}
}
}
I would like to share what I think is the easiest approach:
Autowire the controller and use to call the method it instead of using the class context this.
The updated code would look like:
#Controller
public class TestController {
#Autowired TestController self;
#RequestMapping("/test")
public String testView(){
self.expensiveMethod();
return "test";
}
#Cacheable("ones")
public void expensiveMethod(){
System.out.println("Cache is not being used");
}
}
The default advice mode for processing caching annotation is “proxy”. At the startup of an application, all the caching annotations like #Caching, #Cacheable, #CacheEvict etc. are scanned and a target proxy class is generated for all of these classes. The proxy allows for intercepting the calls to these cacheable methods, which adds the caching advice/behavior.
So when we invoke the cacheable methods from the same class, as shown below, calls from the clients don’t get intercepted in a way that allows for caching advice to be added to them. Hence, every single time there is an unexpected cache miss.
Solution: Invoke the Cacheable methods from a different bean to use proxy class with caching advice.
TL;DR : How to switch elastic search hosts at runtime using spring-data-elasticsearch?
We have a spring boot application (v : 2.5.2) using spring data elasticsearch (v: 4.2.2).
I am using java configurations to initialize ES connection as mentioned in High Level Rest Client section of spring's documentation.
We have a requirement where we need to change the elasticsearch hosts at runtime and not manually restart our servers.
I tried restarting spring IOC on hostname config change using something similar to spring actuator's RestartEndpoint. But this seems to work only in spring's embedded tomcat and not with the external tomcat that we use.
Is there an alternate way to do this?
[This is required in case there is a disaster and we need to switch to backup]
Nothing out of the box.
One idea (I did not try it out): You'd need to create your own implementation of the ElasticsearchOperations interface, lets call it MyOperations and return that from the AbstractElasticsearchConfiguration#elasticsearchOperations() method which your configuration inherits. In this MyOperations implementation (which is a singleton Spring bean), you'd create a delegate ElasticsearchOperations with the same code like in the configuration class that is connected to normal cluster. Every method in your implementation then delegates to the corresponding method of the delegate.
In the failure case, your implementation would create a new delegate that now connects to the secondary cluster and replace the first one with it. Or create both and implement a toggle feature to switch between the two.
Expanding on the answer provided by P.J.Meisch,
Create a custom implementation for ElasticsearchOperations
public class CustomElasticsearchOperations implements ElasticsearchOperations {
private ElasticsearchOperations delegateElasticSearchOp;
/**
* Main Constructor to create the instance using existing elasticsearchConverter bean
* #param elasticsearchConverter : ElasticsearchConverter
*/
public CustomElasticsearchOperations(ElasticsearchConverter elasticsearchConverter) {
updateElasticsearchOperationDelegate(elasticsearchConverter);
}
private ElasticsearchOperations createNewElasticsearchOperationDelegate(ElasticsearchConverter elasticsearchConverter){
return new ElasticsearchRestTemplate(ElasticsearchRestHighLevelClientProvider.INSTANCE.elasticsearchClient(),elasticsearchConverter);
}
/**
* Method to update the delegate ElasticsearchRestTemplate to register elasticsearch configurations
* #param elasticsearchConverter : Default spring lib provided ElasticsearchConverter instance
*/
public void updateElasticsearchOperationDelegate(ElasticsearchConverter elasticsearchConverter){
this.delegateElasticSearchOp = createNewElasticsearchOperationDelegate(elasticsearchConverter);
}
/*
* ElasticsearchOperations method implementation using Delegate ElasticsearchRestTemplatea
*/
#Override
public IndexOperations indexOps(Class<?> aClass) {
return delegateElasticSearchOp.indexOps(aClass);
}
#Override
public IndexOperations indexOps(IndexCoordinates indexCoordinates) {
return delegateElasticSearchOp.indexOps(indexCoordinates);
}
#Override
public ClusterOperations cluster() {
return delegateElasticSearchOp.cluster();
}
#Override
public ElasticsearchConverter getElasticsearchConverter() {
return delegateElasticSearchOp.getElasticsearchConverter();
}
// And so on... delegate the impl of overriden method to existing ElasticsearchRestTemplate
}
Where ElasticsearchRestHighLevelClientProvider is a class that holds the logic to create object of RestHighLevelClient that holds the values for current cluster configurations
// This is a sample from spring documentation. Create your own rest client with your own configs
public enum ElasticsearchRestHighLevelClientProvider{
INSTANCE;
// below is a sample, change it according to your requirements.
public RestHighLevelClient elasticsearchClient() {
String hosts [] = getLatestConfigs().getHosts();
final ClientConfiguration clientConfiguration =
ClientConfiguration.builder()
.connectedTo(hosts)
.build();
return RestClients.create(clientConfiguration).rest();
}
// your code for fetching cluster configs..
}
And finally, register the bean of CustomElasticsearchOperations
#Configuration
public class ElasticSearchAutoConfigurations extends ElasticsearchConfigurationSupport {
#Bean(
name = {"elasticsearchOperations", "elasticsearchTemplate"}
)
public ElasticsearchOperations elasticsearchOperations(ElasticsearchConverter elasticsearchConverter) {
return new CustomElasticsearchOperations(elasticsearchConverter);
}
}
Now when your configurations change you can Autowire the instances of ElasticsearchOperations and ElasticsearchConverter beans, and invoke ElasticsearchOperations#updateElasticsearchOperationDelegate(elasticsearchConverter)
This will replace the instance of ElasticsearchRestTemplate within CustomElasticsearchOperations singleton bean with the new configs, and new requests will go to the cluster you want to switch to.
Note that
If you have autowired ElasticsearchRestTemplate to make non spring repository based calls to elasticsearch, change the autowired candidate to parent interface ElasticsearchOperations as ElasticsearchRestTemplate's instance will not be present in the application context
ElasticsearchConverter is already created within ElasticsearchConfigurationSupport class. So no need to create this bean again
I am creating a route controller structure for commands.
Every controller has a #ControlController annotation:
#Target(ElementType.TYPE)
#Retention(RetentionPolicy.RUNTIME)
#Component // Because #Component all controllers will be spring managed.
public #interface ControlController {
}
The controller should contain methods with the #CommandMapping annotation:
#Target(ElementType.METHOD)
#Retention(RetentionPolicy.RUNTIME)
public #interface CommandMapping {
String value();
}
The value of the #CommandMapping annotation is the command. So the method should be called when the value is the same as the command that is called.
At the start of the application the following code is called to fetch all the #CommandMappings:
/**
* Load all controller mappings.
*/
private void fetchControllers() {
// Get all beans with the ControlController annotation.
Map<String, Object> controllers = this.applicationContext.getBeansWithAnnotation(ControlController.class);
for (Map.Entry<String, Object> entry : controllers.entrySet()) {
Class controller = entry.getValue().getClass();
for (Method method: controller.getMethods()) {
// Check every method in a controller for the CommandMapping annotation.
// When the annotation is present the method is a command mapping.
if (method.isAnnotationPresent(CommandMapping.class)) {
CommandMapping commandMapping = method.getAnnotation(CommandMapping.class);
// Add the command mapping to the controller list.
this.controllers.put(commandMapping.value(), method);
}
}
}
}
This code will find all the beans with the #ControlController annotation and will loop trough all the methods to find the #CommandMapping annotation. All the methods will be put in a Map<String, Method>.
Until this far everything works perfect.
The following method is used to execute the right method that belongs to a command:
/**
* Execute a command for a client.
*
* #param client The client.
* #param command The command.
*/
public void executeCommand(Client client, String command) {
// Get the method that belongs to the command.
Method method = this.controllers.get(command);
Class<?> controllerClass = method.getDeclaringClass();
// The the controller that belongs to the method.
Object controller = this.applicationContext.getBean(controllerClass); // Here the code just stops.
System.out.println("Yeah"); // This isn't executed.
try {
List<Object> arguments = new ArrayList<>();
for (Parameter parameter: method.getParameters()) {
// Add arguments based on the parameter type.
}
method.invoke(controller, arguments.toArray(new Object[arguments.size()]));
} catch (Exception exception) {
exception.printStackTrace();
}
}
The code just stops without any exception at the this.applicationContext.getBean(controllerClass);
I found out that when I AutoWire the controllerClass it for some reason works. It doesn't matter in what class I autowire the controllers. But of course AutoWiring every controller is an ugly fix.
Why does the ApplicationContext.getBean get stuck and how can I fix this?
UPDATE:
I just found out that using the bean name in getBean also works.
Example:
this.applicationContext.getBean(MainController.class); //Doesn't work
this.applicationContext.getBean("mainController"); // Works
UPDATE:
I forgot to mention something very important(I think): The executeCommand method is called from a thread, but the thread is spring managed. When I run it without a thread it works, but I really need threads. How can I make beans work in a thread?
You can try by searching the Controller using the 'name' ; this solution implie to find the name of the Controller by getting the annotation.
i.e.:
#Service
#Component(value = "statService")
public class Controller {...}
public class AnnotationFinder {
public static String findComponentName(Class cls) {
for (Annotation annotation : cls.getDeclaredAnnotations()) {
if (annotation.annotationType().equals(Component.class)) {
return annotation.value();
}
}
return null;
}
}
When you get your #Component you get the value member and =>
Object controller = this.applicationContext.getBean(AnnotationFinder.findComponentName(controllerClass));
I found out the the web application wasn't working either.
The problem was that the loop that was accepting connections was not running in a separate thread, but just in a component's #PostConstruct, so the application was never fully started, but the server(My SocketServer) was running.
Because the application was not fully started the beans didn't work like expected. So it had nothing to do with the code I posted...
I hope someone else can still learn of my answer.
I have this in src/main/groovy/...
package com.mycompany.web;
// imports....
#Controller
class GroovyController {
#RequestMapping("/status_groovy")
public #ResponseBody String getStatus() {
return "Hello World from groovy!";
}
}
Using maven 3 and spring 3.1 (Milestone). Spring MVC works perfectly well for java controllers and everything is set up fine. The groovy class compiles fine and can be found in the classes directory along with the java controller classes.
I have similar controller written in java (JavaController) in same package but under src/main/java and its getting picked up properly by spring and mapped and I can see the response on screen when I hit the url.
package com.mycompany.web;
// imports....
#Controller
class JavaController {
#RequestMapping("/status")
public #ResponseBody String getStatus() {
return "Hello World!";
}
}
Jetty starts normally with no error in log but in I dont see groovy url getting mapped whereas i can see the java one.
2011-09-23 16:05:50,412 [main] INFO org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping - Mapped "{[/status],methods=[],params=[],headers=[],consumes=[],produces=[]}" onto public java.lang.String com.mycompany.web.JavaController.getStatus()
All the setting are fine as other parts of app are working just fine with annotations (component-scan etc.), Just that I can not get the url mapped in GroovyController
Can anyone explain what needs to be done in order to get Controllers written in groovy working?
PS: I am avoiding GroovyServlet to run the scripts because it has major downside when it comes to bean injection and url path mappings.
With all due respect to Ben (whom I work with), the problem here isn't that Spring is creating a cglib proxy. Rather, it's creating a dynamic JDK (or interface-based) proxy. This method of creating proxies can only implement methods declared in the target's implemented interfaces. You actually want Spring to create a cglib proxy, which creates a proxy that is a subclass of the target object and can therefore recreate all of its public methods. Unless you specify otherwise, Spring will create a cglib proxy if the target object doesn't implement any interfaces, and an interface-based proxy otherwise. Since all Groovy objects implement GroovyObject, you're getting an interface-based proxy, even though you didn't explicitly implement any interfaces in your Groovy controller. Ben's solution is correct in that if you create an interface with all your controller methods, you'll get the expected behavior. An alternative is to create a BeanFactoryPostProcessor which instructs Spring to create cglib proxies for classes that implement GroovyObject and only GroovyObject. Here's the code:
/**
* Finds all objects in the bean factory that implement GroovyObject and only GroovyObject, and sets the
* AutoProxyUtils.PRESERVE_TARGET_CLASS_ATTRIBUTE value to true. This will, in the case when a proxy
* is necessary, force the creation of a CGLIB subclass proxy, rather than a dynamic JDK proxy, which
* would create a useless proxy that only implements the methods of GroovyObject.
*
* #author caleb
*/
public class GroovyObjectTargetClassPreservingBeanFactoryPostProcessor implements BeanFactoryPostProcessor {
private static final Logger logger = LoggerFactory.getLogger(GroovyObjectTargetClassPreservingBeanFactoryPostProcessor.class);
#Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
for (String beanDefName : beanFactory.getBeanDefinitionNames()) {
BeanDefinition bd = beanFactory.getBeanDefinition(beanDefName);
//ignore abstract definitions (parent beans)
if (bd.isAbstract())
continue;
String className = bd.getBeanClassName();
//ignore definitions with null class names
if (className == null)
continue;
Class<?> beanClass;
try {
beanClass = ClassUtils.forName(className, beanFactory.getBeanClassLoader());
}
catch (ClassNotFoundException e) {
throw new CannotLoadBeanClassException(bd.getResourceDescription(), beanDefName, bd.getBeanClassName(), e);
}
catch (LinkageError e) {
throw new CannotLoadBeanClassException(bd.getResourceDescription(), beanDefName, bd.getBeanClassName(), e);
}
Class<?>[] interfaces = beanClass.getInterfaces();
if (interfaces.length == 1 && interfaces[0] == GroovyObject.class) {
logger.debug("Setting attribute {} to true for bean {}", AutoProxyUtils.PRESERVE_TARGET_CLASS_ATTRIBUTE, beanDefName);
bd.setAttribute(AutoProxyUtils.PRESERVE_TARGET_CLASS_ATTRIBUTE, true);
}
}
}
}
Just include a bean of this type in your context, and voila! You can have Groovy controllers without needing to define interfaces.
I beg to differ. There is no need to implement an interface. The problem here is that the default AnnotationMethodHandlerAdapter does not read annotations from proxies. Hence we would have to create this proxy aware AnnotationMethodHandlerAdapter which extends the default AnnotationMethodHandlerAdapter of spring. We also need to instantiate a bean for this ProxyAwareAnnotationMethodHandlerAdapter in the Spring Configuration xml file.
Note: This feature is not available in Spring 3.x but since spring 4.0 would support groovy beans, this feature should be covered.
//ProxyAwareAnnotationMethodHandlerAdapter.java
package name.assafberg.spring;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.aop.TargetSource;
import org.springframework.aop.framework.Advised;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter;
/**
* Add proxy awareness to <code>AnnotationMethodHandlerAdapter</code>.
*
* #author assaf
*/
public class ProxyAwareAnnotationMethodHandlerAdapter extends AnnotationMethodHandlerAdapter {
/**
* #param request
* #param response
* #param handler
* #return
* #throws Exception
* #see org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter#handle(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse, java.lang.Object)
*/
#Override
public ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
handler = unwrapHandler(handler);
return super.handle(request, response, handler);
}
/**
* #param handler
* #return
* #see org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter#supports(java.lang.Object)
*/
#Override
public boolean supports(Object handler) {
handler = unwrapHandler(handler);
return super.supports(handler);
}
/**
* Attempt to unwrap the given handler in case it is an AOP proxy
*
* #param handler
* #return Object
*/
private Object unwrapHandler(Object handler) {
if (handler instanceof Advised) {
try {
TargetSource targetSource = ((Advised) handler).getTargetSource();
return targetSource.getTarget();
} catch (Exception x) {
throw new RuntimeException(x);
}
} else {
return handler;
}
}
}
The spring configuration XML file must have the following. Instead of creating a bean of AnnotationMethodHandlerAdapter we must create a ProxyAwareAnnotationMethodHandlerAdapter bean.
<beans .........
...
...
<bean class="full.qualified.name.of.ProxyAwareAnnotationMethodHandlerAdapter" />
...
...
<lang:groovy script-source="classpath:com/example/mysample.groovy refresh-check-delay="1000" />
</beans>
Also Spring parses the configuration XML file using a SAX parser (based on event occurence). So, in order for spring to understand the annotations within the groovy scripts, the groovy beans (using tag) must be created after the ProxyAwareAnnotationMethodHandlerAdapter.
Hope than helps
Reference: http://forum.springsource.org/showthread.php?47271-Groovy-Controller
Unfortunately, if you want to get this running in Groovy you'll have to create an interface for your Controller class and annotate the method definitions as well. Spring creates a proxy for your class using Cglib. However, without creating a custom interface for your controller Spring is proxying on groovy.lang.GroovyObject because all Groovy objects implement that interface by default.
interface GroovyControllerInterface {
#RequestMapping("/status_groovy")
#ResponseBody String getStatus()
}
#Controller
class GroovyController implements GroovyControllerInterface {
#RequestMapping("/status_groovy")
public #ResponseBody String getStatus() {
return "Hello World from groovy!";
}
}