I have an application running with Spring, and I'm using AOP in some places. Since I want to use the #Transactional annotation at interface level, I have to allow Spring to create JDK proxies. So, I don't set the proxy-target-class property to true. On the other hand, I don't want to create an interface for every single class I want advised: if the interface just doesn't make sense, I want to have just the implementation, and Spring should create a CGLIB proxy.
Everything was working perfectly, just as I described. But I wanted to have some other annotations (created by me) going in interfaces and being "inherited" by the implementation classes (just like the #Transactional one). Turns out that I can't do that with the built-in support for AOP in Spring (at least I could not figure it out how to do it after some research. The annotation in the interface is not visible in the implementation class, and hence that class does not get advised).
So I decided to implement my own pointcut and interceptor, allowing other method annotations to go on interfaces. Basically, my pointcut look for the annotation on the method and, until not found, in the same method (same name and parameter types) of the interfaces that the class or its superclasses implements.
The problem is: when I declare a DefaultAdvisorAutoProxyCreator bean, that will properly apply this pointcut/interceptor, the behavior of advising classes with no interfaces is broken. Apparently something goes wrong and Spring tries to proxy my classes twice, once with CGLIB and then with JDK.
This is my configuration file:
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:aop="http://www.springframework.org/schema/aop" xmlns:task="http://www.springframework.org/schema/task"
xsi:schemaLocation="
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.0.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.0.xsd
http://www.springframework.org/schema/task http://www.springframework.org/schema/task/spring-task-3.0.xsd">
<!-- Activates various annotations to be detected in bean classes: Spring's
#Required and #Autowired, as well as JSR 250's #Resource. -->
<context:annotation-config />
<context:component-scan base-package="mypackage" />
<!-- Instruct Spring to perform declarative transaction management automatically
on annotated classes. -->
<tx:annotation-driven transaction-manager="transactionManager" />
<bean class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator" />
<bean id="logger.advisor" class="org.springframework.aop.support.DefaultPointcutAdvisor">
<constructor-arg>
<bean class="mypackage.MethodAnnotationPointcut">
<constructor-arg value="mypackage.Trace" />
</bean>
</constructor-arg>
<constructor-arg>
<bean class="mypackage.TraceInterceptor" />
</constructor-arg>
</bean>
</beans>
This is the class I want to be proxied, with no interfaces:
#Component
public class ServiceExecutorImpl
{
#Transactional
public Object execute(...)
{
...
}
}
When I try to autowire it in some other bean, like:
public class BaseService {
#Autowired
private ServiceExecutorImpl serviceExecutorImpl;
...
}
I get the following exception:
java.lang.IllegalArgumentException: Can not set mypackage.ServiceExecutorImpl field mypackage.BaseService.serviceExecutor to $Proxy26
This are some lines of the Spring output:
13:51:12,672 [main] DEBUG [org.springframework.aop.framework.Cglib2AopProxy] - Creating CGLIB2 proxy: target source is SingletonTargetSource for target object [mypackage.ServiceExecutorImpl#1eb515]
...
13:51:12,782 [main] DEBUG [org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator] - Creating implicit proxy for bean 'serviceExecutorImpl' with 0 common interceptors and 1 specific interceptors
13:51:12,783 [main] DEBUG [org.springframework.aop.framework.JdkDynamicAopProxy] - Creating JDK dynamic proxy: target source is SingletonTargetSource for target object [mypackage.ServiceExecutorImpl$$EnhancerByCGLIB$$2eb5f51#5f31b0]
I could supply the full output if someone thinks it will help. I have no idea why Spring is trying to "double-proxy" my class, and why this just happens when I declare the DefaultAdvisorAutoProxyCreator bean.
I have been struggling with this for some time now, and any help or ideas would be very much appreciated.
EDIT:
This is my interceptor source code, as requested. It basically log the method execution (only methods annotated with #Trace get intercepted). If the method is annotated with #Trace(false), the logging is suspended until the method returns.
public class TraceInterceptor
implements
MethodInterceptor
{
#Override
public Object invoke(
MethodInvocation invocation )
throws Throwable
{
if( ThreadExecutionContext.getCurrentContext().isLogSuspended() ) {
return invocation.proceed();
}
Method method = AopUtils.getMostSpecificMethod( invocation.getMethod(),
invocation.getThis().getClass() );
Trace traceAnnotation = method.getAnnotation( Trace.class );
if( traceAnnotation != null && traceAnnotation.value() == false ) {
ThreadExecutionContext.getCurrentContext().suspendLogging();
Object result = invocation.proceed();
ThreadExecutionContext.getCurrentContext().resumeLogging();
return result;
}
ThreadExecutionContext.startNestedLevel();
SimpleDateFormat dateFormat = new SimpleDateFormat( "dd/MM/yyyy - HH:mm:ss.SSS" );
Logger.log( "Timestamp: " + dateFormat.format( new Date() ) );
String toString = invocation.getThis().toString();
Logger.log( "Class: " + toString.substring( 0, toString.lastIndexOf( '#' ) ) );
Logger.log( "Method: " + getMethodName( method ) );
Logger.log( "Parameters: " );
for( Object arg : invocation.getArguments() ) {
Logger.log( arg );
}
long before = System.currentTimeMillis();
try {
Object result = invocation.proceed();
Logger.log( "Return: " );
Logger.log( result );
return result;
} finally {
long after = System.currentTimeMillis();
Logger.log( "Total execution time (ms): " + ( after - before ) );
ThreadExecutionContext.endNestedLevel();
}
}
// Just formats a method name, with parameter and return types
private String getMethodName(
Method method )
{
StringBuffer methodName = new StringBuffer( method.getReturnType().getSimpleName() + " "
+ method.getName() + "(" );
Class<?>[] parameterTypes = method.getParameterTypes();
if( parameterTypes.length == 0 ) {
methodName.append( ")" );
} else {
int index;
for( index = 0; index < ( parameterTypes.length - 1 ); index++ ) {
methodName.append( parameterTypes[ index ].getSimpleName() + ", " );
}
methodName.append( parameterTypes[ index ].getSimpleName() + ")" );
}
return methodName.toString();
}
}
Thanks!
I found a solution using the 'scoped-proxy' suggested by Bozho.
Since I'm using almost only annotations, my ServiceExecutor class now looks like this:
#Component
#Scope( proxyMode = ScopedProxyMode.TARGET_CLASS )
public class ServiceExecutor
{
#Transactional
public Object execute(...)
{
...
}
}
Until now everything seens to be working fine. I don't know why I have to explicitly tell Spring this class should be proxied using CGLIB, since it does not implement any interface. Maybe it's a bug, I don't know.
Thanks a lot, Bozho.
Something doesn't match here - if ther is a $ProxyXX, it means there is an interface. Make sure there is no interface. Some other notes:
in your pointcut you can check if the target object is already a proxy using (x instanceof Advised), then you can cast to Advised
you can use <aop:scoped-proxy /> to define the proxy strategy per-bean
Related
I know you can bind get request parameters to a pojo like:
#RequestMapping(value = "/reservation",
method = RequestMethod.GET,
produces = MediaType.APPLICATION_JSON_VALUE)
public List<Reservation> loadReservations(ReservationCriteria criteria)
return service.loadReservations(criteria);
}
Using a pojo like:
public class ReservationCriteria {
String hotelName;
DateRange reservationDateRange;
//getters-setters omitted
}
With a request: /reservation?hotelName=myHotel
myHotel will be bound to hotelName in ReservationCriteria object.
But how can I bind parameters to the nested object DateRange? Which defined like:
public class DateRange {
Date from;
Date to;
//getters-setters omitted
}
Is there a URL pattern which allows that kind of binding something like:
/reservation?hotelName=myHotel&reservationDateRange={fromDate=14.04.2016,toDate=15.04.2016}
Or do I have to declare seperate request parameters and bind them manually?
#RequestMapping(value = "/reservation",
method = RequestMethod.GET,
produces = MediaType.APPLICATION_JSON_VALUE)
public List<Reservation> loadReservations(
ReservationCriteria criteria,
#RequestParam Date from,
#RequestParam Date to)
DateRange range = new DateRange();
range.setFrom(from);
range.setTo(to);
criteria.setDateRange(range);
return service.loadReservations(criteria);
}
I would prefer not to modify ReservationCriteria class because it is used in many other projects, which would cause alot of refactoring to be made.
Since at least Spring 4 you can pass in nested objects separated with "." in the url.
In the OP case it would be for query parameters:
?reservationDateRange.from=2019-04-01&reservationDateRange.to=2019-04-03
This assumes that Date can be parsed from the given string. This may not work to an arbitrary level of nesting but I've tested it works with one additional nested object.
When you pass a POJO as container of data, Spring use the name of the properties for build the query string and with the data that you pass build the pojo through an appropriated converter. This works for planar pojo or in other words without nesting, for this purpose you have provide the your converter. for this reason you cold have a think like below:
public class ReservationCriteria {
String hotelName;
Date from;
Date to;
//getters-setters omitted
}
#RequestMapping(value = "/reservation",
method = RequestMethod.GET,
produces = MediaType.APPLICATION_JSON_VALUE)
public List<Reservation> loadReservations(ReservationCriteria criteria)
return service.loadReservations(criteria);
}
/reservation?hotelName=value&from=val&to=val
in this way you can benefit of standard converter of SpringMVC.
the your attempt to use a sort of json for codificate the inner object didn't work because Spring by default in query string don't understand this presentation you have provide a converter for this purpose.
Update for answer to Ben's comment:
If you want implement a custom Converter you had implements the org.springframework.core.convert.converter.Converter<S, T> and then register the your new Converter on the Spring Conversion Service.
On xml configuration you can use FormattingConversionServiceFactoryBean and register it on mvc namespace like below:
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:mvc="http://www.springframework.org/schema/mvc" xmlns:util="http://www.springframework.org/schema/util"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util.xsd">
<mvc:annotation-driven conversion-service="conversionService"/>
<context:component-scan base-package="com.springapp.mvc"/>
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="prefix" value="/WEB-INF/pages/"/>
<property name="suffix" value=".jsp"/>
</bean>
<bean id="conversionService" class="org.springframework.format.support.FormattingConversionServiceFactoryBean">
<property name="converters">
<util:list>
<bean class="com.springapp.mvc.DateRangeToStringConverter"/>
<bean class="com.springapp.mvc.StringToDateRangeConverter"/>
</util:list>
</property>
</bean>
</beans>
on java config you can extends WebMvcConfigurerAdapter and add you bena like below:
#Configuration
#EnableWebMvc
public class YourWebConfigurationClass extends WebMvcConfigurerAdapter{
#Override
public void addFormatters(FormatterRegistry formatterRegistry) {
formatterRegistry.addConverter(yourConverter());
}
...
}
the your converter can be like below:
public class DateRangeToStringConverter implements Converter<DateRange,String> {
#Override
public String convert(DateRange dateRange) {
return Json.createObjectBuilder().add("fromDate",DateFormatData.DATE_FORMAT.format(dateRange.getFrom()))
.add("toDate", DateFormatData.DATE_FORMAT.format(dateRange.getTo()))
.build()
.toString();
}
}
public class StringToDateRangeConverter implements Converter<String,DateRange> {
#Override
public DateRange convert(String dateRange) {
DateRange range = new DateRange();
JsonObject jsonObject = Json.createReader(new StringReader(dateRange)).readObject();
try {
range.setFrom(DateFormatData.DATE_FORMAT.parse(jsonObject.getString("fromDate")));
} catch (ParseException e) {
e.printStackTrace();
}
try {
range.setTo(DateFormatData.DATE_FORMAT.parse(jsonObject.getString("toDate")));
} catch (ParseException e) {
e.printStackTrace();
}
System.out.println(range);
return range;
}
}
in this way you can listgening on the url: http://localhost:8080/reservation?hotelName=myHotel&reservationDateRange={"fromDate":"14.04.2016","toDate":"15.04.2016"}
pleas pay attenction on reservation DateRange field because I encoded it like a json.
I hope that it can help you
I'm trying to grasp Spring's FactoryBean and I've had and issue. Could you please see my sources below and answer. It's my app context:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.1.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-3.1.xsd">
<context:annotation-config/>
<bean id="SHADigest" class="com.dtoryanik.spring.factorybean.MessageDigestFactoryBean">
<property name="algorithmName">
<value>SHA1</value>
</property>
</bean>
<bean id="defaultDigest" class="com.dtoryanik.spring.factorybean.MessageDigestFactoryBean"/>
<bean id="digester" class="com.dtoryanik.spring.factorybean.MessageDigester">
<property name="messageDigest1">
<ref local="SHADigest"/>
</property>
<property name="messageDigest2">
<ref local="defaultDigest"/>
</property>
</bean>
</beans>
It's a factory bean actually:
public class MessageDigestFactoryBean implements FactoryBean<MessageDigest>{
private String algorithmName = "MD5";
private MessageDigest messageDigest = null;
#Override
public MessageDigest getObject() throws Exception {
System.out.println("<> MessageDigestFactoryBean.getObject()");
return messageDigest;
}
#Override
public Class<?> getObjectType() {
System.out.println("<> MessageDigestFactoryBean.getObjectType()");
return MessageDigest.class;
}
#Override
public boolean isSingleton() {
System.out.println("<> MessageDigestFactoryBean.isSingleton()");
return true;
}
#PostConstruct
public void postConstructHandler() throws NoSuchAlgorithmException {
System.out.println("<> MessageDigestFactoryBean.postConstructHandler()");
messageDigest = MessageDigest.getInstance(algorithmName);
}
public void setAlgorithmName(String algorithmName) {
this.algorithmName = algorithmName;
}
}
There is another class - MessageDigester but it does not do anything helpful for topic. And I have a main-method class:
public class MessageDigestDemo {
public static void main(String[] args) {
GenericXmlApplicationContext ctx = new GenericXmlApplicationContext();
ctx.load("classpath:app-ctx.xml");
ctx.refresh();
MessageDigester messageDigester = (MessageDigester) ctx.getBean("digester");
messageDigester.digest("Hello World!");
}
}
The issue is in my output. It seems I have a double instantiating. Methods isSingleton(), getObject() are called two times for each bean (although I retrieve only 2 instances from factory). Why does it occur? Maybe I do something wrong?
<> MessageDigestFactoryBean.postConstructHandler()
<> MessageDigestFactoryBean.postConstructHandler()
<> MessageDigestFactoryBean.isSingleton()
<> MessageDigestFactoryBean.getObject()
<> MessageDigestFactoryBean.isSingleton()
<> MessageDigestFactoryBean.getObject()
<> MessageDigestFactoryBean.isSingleton()
<> MessageDigestFactoryBean.getObjectType()
<> MessageDigestFactoryBean.isSingleton()
<> MessageDigestFactoryBean.getObjectType()
I've modified your MessageDigestFactoryBean class so that it outputs algorithmName as well, this will help to clear out the case. After that change, the output is:
<> MessageDigestFactoryBean.postConstructHandler() SHA1
<> MessageDigestFactoryBean.postConstructHandler() MD5
<> MessageDigestFactoryBean.isSingleton() SHA1
<> MessageDigestFactoryBean.getObject() SHA1
<> MessageDigestFactoryBean.isSingleton() MD5
<> MessageDigestFactoryBean.getObject() MD5
<> MessageDigestFactoryBean.isSingleton() SHA1
<> MessageDigestFactoryBean.getObjectType() SHA1
<> MessageDigestFactoryBean.isSingleton() MD5
<> MessageDigestFactoryBean.getObjectType() MD5
Let's try to analyze this output.
You have declared two MessageDigestFactoryBean instances, so when Spring discovers them in context, it initializes them, and in process calls the method annotated with #PostConstruct - MessageDigestFactoryBean.postConstructHandler().
Then as Spring discovers digester bean, it tries to obtain it's dependencies. Spring sees that dependency is FactoryBean, so it eventually calls FactoryBeanRegistrySupport.getObjectFromFactoryBean. This method first checks if the bean is singleton, calling MessageDigestFactoryBean.isSingleton(). If the bean is singleton, it first tries to obtain reference to it from factory bean objects cache. Since this is the first time this bean is referenced, it's not yet cached, so reference is obtained via MessageDigestFactoryBean.getObject(), cached and then returned. Since you have two factory beans referenced in digester, obviously this process is repeated for each one.
After initialization is complete, Spring tries to publish ContextRefreshedEvent to the lifecycle processor, so it searches all bean definitions for an instance that implements Lifecycle interface. Basically Spring loops through all beans defined in context, checking for singleton bean of matching type (actually there are more checks, but we're interested only in these). In process of this, for factory bean MessageDigestFactoryBean.isSingleton() is called to determine whether object is singleton, and MessageDigestFactoryBean.getObjectType() is called to check whether object's type is assignable from Lifecycle interface. Again, since you have two instances of MessageDigestFactoryBean, each of these methods is called twice.
That's what happens when you call ctx.refresh(). This is just over the top look and obviously a lot more is done by Spring under the hood, but that's all I can see related to your output. Hope this answers your first question.
Now for the second question - no, you did not do anything wrong, the output you see just reflects how Spring functions internally.
I've to manage the association between bean instance and some type of resource. More specifically, when I receive some data associated to Resource A, I have to dispatch the data to the create an instance of some bean type every time I receive data associated with a specified resource. For example, if I receive some data associated with Resource "a", then i have to dispatch this data to a Bean instance associated with "a". If there isn't a Bean instance associated to Resource "a", then it has to be created. The number of Resource isn't statically known, and it will be known only during execution.
I understand I need some type of dictionary to store the association between Resource id and bean instance, building something like a Factory to manage bean creation.
How can I implement this in Spring? What's the proper way?
the Spring ApplicationContext can be referred to in this 'dictionary' fashion. that is, you specify to the application context to 'setup' the 'resource beans'. you then map the 'received data' via way of a Map object, that, in turn, holds a reference to the bean. once the data maps to the bean, you can then retrieve the bean and process the received data. here's a brief example
here's a resource service interface
package de.incompleteco.spring.service;
public interface ResourceService {
public void processData(Object data);
}
here's an implementation to process 'TypeA' data
package de.incompleteco.spring.service;
public class TypeAResourceService implements ResourceService {
public void processData(Object data) {
//do something for 'type A' data
}
}
here's a 'delegate' service
package de.incompleteco.spring.service;
import java.util.Map;
import de.incompleteco.spring.domain.TypeA;
import de.incompleteco.spring.domain.TypeB;
public class DelegateResourceService implements ResourceService {
private Map<String,ResourceService> services;
public void processData(Object data) {
if (data instanceof TypeA) {
services.get(TypeA.class.getSimpleName()).processData(data);
} else if (data instanceof TypeB) {
services.get(TypeA.class.getSimpleName()).processData(data);
} else {
throw new IllegalArgumentException("...");
}
}
public void setServices(Map<String, ResourceService> services) {
this.services = services;
}
}
the 'delegate' service is responsible for retrieving the right service from a map to process the right data (keyed by Class Name)
here's the spring configuration to support
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:util="http://www.springframework.org/schema/util"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util.xsd">
<!-- type 'a' bean -->
<bean id="typeA" class="de.incompleteco.spring.service.TypeAResourceService"/>
<!-- type 'b' bean -->
<bean id="typeB" class="de.incompleteco.spring.service.TypeBResourceService"/>
<util:map id="dictionary">
<entry key="TypeA" value-ref="typeA"/>
<entry key="TypeB" value-ref="typeB"/>
</util:map>
<bean class="de.incompleteco.spring.service.DelegateResourceService">
<property name="services" ref="dictionary"/>
</bean>
</beans>
this configuration does the following;
sets up services for type A and B data
maps those services to keys
sets up the delegate service, setting the map to be used
this pattern is not the only way of doing it, but gives you the following;
ability to configure the service beans independantly
the delegate only needs a map to process
the constraint is that the service objects have to be of the same interface
you can think of the Spring ApplicationContext as a giant factory, but (in many ways) a whole lot simpler to setup.
In my application that uses Spring container I created my own annotation, and I wanted at runtime get Class objects of classes that are annotated with my annotation. For this I wanted to utilize Spring container.
In my .xml configuration file I put
<context:component-scan base-package="some.package" >
<context:include-filter type="annotation" expression="some.package.Question" />
</context:component-scan>
so the classes that are annotated with my Question annotation are detected by Spring. Problem is that those classes don't have no parameter constructor, so now I have 2 options:
Define no-parameter constructor in those classes
Define the beans in .xml and use constructor-arg
but is it possible to annotate constructor arguments with some annotation, so Spring will know that it needs to pass null value during creation of a bean?
Also those beans will have prototype scope, and from the point of view of an application the contents of an constructor arguments are not known during the creation of a bean.
EDIT:
I had to use #Value("#{null}") for annotation constructor arguments
I think your first suggestion of using a no-arg constructor sounds cleaner - the reason is that the object created is, from your perspective, being considered properly initialized even though the instance variables have null values - this can be indicated by having a default constructor
If it cannot be changed, your approach of using #Value("#{null}") also works, I was able to test out in a test case:
#MyAnnotation
public class Component1 {
private String message;
#Autowired
public Component1(#Value("#{null}") String message){
this.message = message;
}
public String sayHello(){
return this.message;
}
}
This may not be what you're looking for, but if you want to re-use Spring's classpath scanner and wrap it in your own implementation, you can use the following;
Class annotation = [your class here ];
String offsetPath = [your path here ];
// Scan a classpath for a given annotation class
ClassPathScanningCandidateComponentProvider scanner = new ClassPathScanningCandidateComponentProvider(false);
// MZ: Supply the include filter, to filter on an annotation class
scanner.addIncludeFilter(new AnnotationTypeFilter(annotation));
for (BeanDefinition bd : scanner.findCandidateComponents(offsetPath))
{
String name = bd.getBeanClassName();
try
{
Class classWithAnnotation = Class.forName(name);
}
catch (Exception e)
{
//Logger.fatal("Unable to build sessionfactory, loading of class failed: " + e.getMessage(), e);
return null;
}
I have written a simple Spring2.5 app to demo/test AOP; specifically, I want to log the entry and exit of every method of every class in a specific package. This is what I have...
(note: I am using annotation-controllers; I am omitting details not directly-related to aop because my basic setup works fine -- I am only including aop-related details -- let me know if you need to see more)
applicationContext.xml :
(...)
<bean id="loggerInterceptor" class="aspect.LoggerInterceptor" />
(...)
dispatcher-servlet.xml :
(...)
<aop:aspectj-autoproxy proxy-target-class="true" />
(...)
HomeController.java :
public class HomeController() {
public HomeController() { }
public ModelAndView get() {
System.out.println("In HomeController#get()...");
this.somePrivateMethod();
this.somePublicMethod();
return new ModelAndView( "home" );
}
private void somePrivateMethod() {
System.out.println("In HomeController#somePrivateMethod()...");
}
public void somePublicMethod() {
System.out.println("In HomeController#somePublicMethod()...");
}
}
LoggerInterceptor.java :
public class LoggerInterceptor {
#Pointcut("execution(* controller.*.*(..))")
private void anyOperationInControllerPackage() {
/* nothing to do here;
* this just defines that we want to catch all methods
* in the controller-package
*/
}
#Around("anyOperationInControllerPackage()")
public Object logAround(ProceedingJoinPoint joinPoint) throws Throwable {
System.out.println("Entering " + joinPoint.getSignature().getDeclaringTypeName() + "#" + joinPoint.getSignature().getName() + "() using arguments: " + Arrays.toString( joinPoint.getArgs() ) );
try {
Object result = joinPoint.proceed();
System.out.println("Leaving " + joinPoint.getSignature().getDeclaringTypeName() + "#" + joinPoint.getSignature().getName() + "()." );
return result;
} catch (Throwable ex) {
ex.printStackTrace();
throw ex;
}
}
}
Here is what I'm getting when HomeController#get() is invoked:
Entering controller.HomeController#get() using arguments: []
In HomeController#get()...
In HomeController#somePrivateMethod()...
In HomeController#somePublicMethod()...
Leaving controller.HomeController#get().
As you can see, the only method that's getting intercepted is HomeController#get(). When #get() calls #somePrivateMethod() or #somePublicMethod(), the interceptor doesn't catch those. I would expect, at the very least, that #somePublicMethod() would also get caught (and since I'm using cglib, I would also expect that #somePrivateMethod() would get caught).
So I guess my question is what do I need to change/add in order to allow (at the very least) all public methods in the controller-package to get caught even when another method in that package called them and was itself caught first???
I hope that makes sense.
:D
EDIT(25APR2011 # 1:13PM)
applicationContext.xml :
(...)
<context:load-time-weaver /> <!-- added -->
<bean id="loggerInterceptor"... />
(...)
aop.xml :
<!DOCTYPE aspectj PUBLIC
"-//AspectJ//DTD//EN" "http://www.eclipse.org/aspectj/dtd/aspectj.dtd">
<aspectj>
<weaver>
<!-- only weave classes in this package -->
<include within="controller.*" />
</weaver>
<aspects>
<!-- use only this aspect for weaving -->
<aspect name="aspect.LoggerInterceptor" />
</aspects>
</aspectj>
In Netbean's "Project Properties" under the "Run" tab I added this line to the "VM Options" :
-javaagent:C:\Users\bgresham\Documents\libraries\spring-framework-2.5\dist\weaving\spring-agent.jar
As before, I'm not getting any errors -- I just don't get the "nested"-logging I'm looking for.
???
If you're using Spring AOP, you must only call a method that's had an aspect applied to it via a reference returned by Spring, not through this, and I don't think you can apply pointcuts to private methods either (might be wrong on that last part). That's because Spring AOP applies the pointcuts through the proxy object, not by class rewriting (which is what AspectJ does). The benefit of that heavy restriction is that it is much easier to make it work in containers (I know from experience that Spring AOP works just fine inside Tomcat) because there's no warring over what bits are plugged in where.
The best way of doing this is by splitting the class definition so that you never call a method via this, but if that's not possible then you can always try giving a bean a Spring-derived reference to itself:
private HomeController self;
#Required
public void setSelf(HomeController self) { this.self = self; }
public ModelAndView get() {
System.out.println("In HomeController#get()...");
self.somePrivateMethod();
self.somePublicMethod();
return new ModelAndView( "home" );
}
(This is pretty neat; self is a keyword in a number of languages but not Java so it's relatively easy to remember what you're using it for.)
You are using spring aop for aspect support. Spring aop will work only on spring beans. So, the pointcut does not work on the actual class instance i.e. when the controller calls any of its public or private method. In order to log all the methods in the controller, you need to use AspectJ for your aop support by enabling either load time or compile time weaving of all the classes that you want to intercept.
Edit:
You would need the following for load time weaving :
aop.xml
<!DOCTYPE aspectj PUBLIC "-//AspectJ//DTD//EN" "http://www.eclipse.org/aspectj/dtd/aspectj.dtd">
<aspectj>
<weaver options="-Xset:weaveJavaxPackages=true -verbose -showWeaveInfo -debug">
<include within="*"/>
</weaver>
<aspects>
<!-- weave in just this aspect -->
<aspect name="your.logger.impl.LoggingImpl"/>
</aspects>
</aspectj>
This implies weaving in all your files ('within=*', modify as you wish) with the aspect/s specified. On load time you should see verbose information on weaving of classes.
Configurations in the spring configurations :
<context:load-time-weaver aspectj-weaving="autodetect"
weaver-class="org.springframework.instrument.classloading.ReflectiveLoadTimeWeaver"/>
Notice the weaving class has to be in the server library path and NOT your application path.
The above configurations should do what you are looking out to do.