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.
Related
I have a scenario in which i need to intercept some subclass methods, but i couldn't find a proper pointcut expression to do so.
I have a client facing interface InfoService which has a method getClientDetails.
package sample;
public interface InfoService {
InfoVO getClientDetails(int id);
}
The implementation class has some nested methods like get*Info().
package sample;
public class InfoServiceImpl implements InfoService{
public InfoVO getClientDetails(int id) {
InfoVO clientInfo = new InfoVO();
clientInfo.setA(getAInfo(id));
clientInfo.setB(getBInfo(id));
clientInfo.setC(getCInfo(id));
return clientInfo;
}
public Object getAInfo(int id) {
return null;
}
public Object getBInfo(int id) {
return null;
}
public Object getCInfo(int id) {
return null;
}
}
When the user invoke the getClientDetails method i would like to intercept the get*Info() methods. I can easily intercept the getClientDetails but nothing seems to intercept the subclass method. I even tried annotating those info methods using custom annotation but with no luck. So far I came up with the following aspect
<aop:aspect ref="infoAspect">
<aop:pointcut expression="execution(* sample.InfoService+.*Info(..))"
id="infoMethods" />
<aop:around method="aroundAdviceForinfoMethods" pointcut-ref="infoMethods" />
</aop:aspect>
Setting the <aop:aspectj-autoproxy proxy-target-class="false"/> to true or false didn't help either.
I know AOP cannot intercept on private subclass methods but these are public ones. Is it even possible to do this? Any help is much appreciated.
PS: The example shown is for demo purpose. The actual implementations are huge, so moving those inner methods to a different bean and invoking won't be feasible.
This is a classic one, having been asked here dozens of times already. I guess you have not read the Spring manual's info about Spring AOP being proxy-based and that for this reason Spring aspects cannot intercept self-invocation. If you really need AOP to work for self-invoked methods (even private ones if necessary), say goodbye to Spring AOP and hello to full AspectJ and LTW (load-time weaving).
In some cases, we need to write to database in a Spring -application within an ApplicationListener, so we need transactions within the listener using #Transactional-annotation. These listeners are extended from an abstract baseclass, so normal ScopedProxyMode.INTERFACES won't do, as Spring container complains about expecting a bean of the abstract class-type, not "[$Proxy123]". However, using Scope(proxyMode=ScopedProxyMode.TARGET_CLASS), the listener receives the same event twice. We are using Spring version 3.1.3.RELEASE. (Edit: Still occurring with version 3.2.4.RELEASE)
Digging into Spring source with debugger, I found out that org.springframework.context.event.AbstractApplicationEventMulticaster.getApplicationListeners returns a LinkedList that contains the same listener twice (same instance: [com.example.TestEventListenerImpl#3aa6d0a4, com.example.TestEventListenerImpl#3aa6d0a4]), if the listener is a ScopedProxyMode.TARGET_CLASS.
Now, I can work around this by placing the code handling database write into a separate class and putting the #Transactional there, but my question is, is this a bug in Spring or expected behavior? Are there any other workarounds so we wouldn't need to create separate service-classes (ie. handle the transaction in the listener, but don't get the same event twice) for even the simplest cases?
Below is a smallish example showing the problem.
With #Scope(proxyMode=ScopedProxyMode.TARGET_CLASS) in TestEventListenerImpl, the output is as follows:
Event com.example.TestEvent[source=Main] created by Main
Got event com.example.TestEvent[source=Main]
Got event com.example.TestEvent[source=Main]
With #Scope(proxyMode=ScopedProxyMode.TARGET_CLASS) removed from TestEventListenerImpl, the output is:
Event com.example.TestEvent[source=Main] created by Main
Got event com.example.TestEvent[source=Main]
So it seems that TARGET_CLASS -scoped beans get inserted twice into the listener list.
Example:
applicationContext.xml
<?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.0.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd">
<context:component-scan base-package="com.example/**"/>
</beans>
com.example.TestEvent
public class TestEvent extends ApplicationEvent
{
public TestEvent(Object source)
{
super(source);
System.out.println("Event " + this + " created by " + source);
}
}
com.example.TestEventListener
public interface TestEventListener extends ApplicationListener<TestEvent>
{
#Override
public void onApplicationEvent(TestEvent event);
}
com.example.TestEventListenerImpl
#Component
#Scope(proxyMode=ScopedProxyMode.TARGET_CLASS) //If commented out, the event won't be received twice
public class TestEventListenerImpl implements TestEventListener
{
#Override
public void onApplicationEvent(TestEvent event)
{
System.out.println("Got event " + event);
}
}
com.example.ListenerTest
public class ListenerTest
{
public static void main(String[] args)
{
ClassPathXmlApplicationContext appContext = new ClassPathXmlApplicationContext("classpath:applicationContext.xml");
SimpleApplicationEventMulticaster eventMulticaster = appContext.getBean(SimpleApplicationEventMulticaster.class);
//This is also needed for the bug to reproduce
TestEventListener listener = appContext.getBean(TestEventListener.class);
eventMulticaster.multicastEvent(new TestEvent("Main"));
}
}
I can't speak to if this is a bug or expected behavior, but here's the dirty:
Declaring a bean like
#Component
#Scope(proxyMode=ScopedProxyMode.TARGET_CLASS) //If commented out, the event won't be received twice
public class TestEventListenerImpl implements TestEventListener
{
Creates two BeanDefinition instances:
A RootBeanDefinition describing the Scoped bean.
A ScannedGenericBeanDefinition describing the actual object.
The ApplicationContext will use these bean definitions to create two beans:
A ScopedProxyFactoryBean bean. This is a FactoryBean that wraps the TestEventListenerImpl object in a proxy.
A TestEventListenerImpl bean. The actual TestEventListenerImpl object.
Part of the initialization process is to register beans that implement the ApplicationListener interface. The TestEventListenerImpl bean is created eagerly (right away) and registered as an ApplicationListener.
The ScopedProxyFactoryBean is lazy, the bean (proxy) it's supposed to create is only generated when requested. When that happens, it also gets registered as an ApplicationListener. You only see this when you explicitly request it
TestEventListener listener = appContext.getBean(TestEventListener.class);
Or implicitly by using #Autowired to inject it into another bean. Note that the actual target object is added, not the proxy.
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 gone through number of post regarding tx with spring and AspectJ. Below is the summary,
Say, I have a service class and its interface
interface TestService {
void methodA();
void methodB();
}
class TestServiceImpl implements TesService {
#Transactional
void methodA() {
methodB();
}
#Transactional(propagation=Propagation.NEVER)
void methodB(){}
}
And my configuration
<tx:annotation-driven transaction-manager="jpaTxManager"/>
<bean id="jpaTxManager" class="org.springframework.orm.jpa.JpaTransactionManager">
<property name="entityManagerFactory"><ref bean="entityManagerFactory"/></property>
<property name="dataSource"><ref bean="dataSource"/></property>
</bean>
<bean id="testService" class="com.motherframework.plugin.test.service.TestServiceImpl">
<property name="testDAO" ref="testDAO"/>
</bean>
I am calling testService.methodA() from some client class. As per spring's JDK dynamic proxy usage, it will only care about #Transactional on methodA(), but not #Transactional(propagation=Propagation.NEVER) on methodB(). So the code executes with proper transaction and commits. If we use AspectJ mode then it will also check for #Transactional(propagation=Propagation.NEVER) on methodB() and will throw an exception.
Now my question is, why this limitation is imposed by Spring? Now there are two possibilities for Spring design,
It is a technical limitaion with spring that they can not check annotation in methodB(), though it is public? But if AspectJ can check it, then why not Spring?
Intentionally they have limited this AOP checking for internal method calls. Is this kind of method call (where target method is annotated with different transactionPropagation) is against proper design methodology?
Yes, it's a technical limitation. When you don't use AspectJ, the transactional aspect is implemented by returning a proxy around the actual bean class instance and returning/injecting this proxy into other beans. So, when you call testService.methodA(), the following (basically) happens:
caller ---> transactionalProxy.methodA() ---> testServiceImpl.methodA()
The proxy applies the transactional aspect around the call to testServiceImpl.methodA(): it starts the transaction before, and commits/rollbacks it after.
If you call this.methodB() from methodA(), the following happens:
caller ---> transactionalProxy.methodA() ---> testServiceImpl.methodA() ---> testServiceImpl.methodB()
And since you bypass the proxy, no transactional aspect can be applied.
AspectJ is different because it transforms the byte-code of TestServiceImpl in order to apply aspects around various method invocations.
I wouldn't say that applying aspects around internal method calls is not proper design. You just need to be aware that it works only with byte-code instrumentation.
It is a technical limitation (as others have answered). If you want Spring to check this, you could modify your service like this:
class TestServiceImpl implements TesService {
TesService thiz; // setter left outside, assumed to be injected by Spring
#Transactional
void methodA() {
thiz.methodB();
}
#Transactional(propagation=Propagation.NEVER)
void methodB(){}
}
The idea here is that the outermost method knows what is best for the whole transaction. But, as you noticed, there are corner cases.
Workaround: Move the implementations of the methods to a second bean and inject that bean into your TestServiceImpl. Since you'll get a proxy injected, all method calls will heed the annotations.
You will need to split some methods. If you have this situation:
methodX() {
...code before...
methodB();
...code after...
}
you can use a callback:
methodX() {
Callable<Void> callback = new Callable<Void>() {
Void call() {
realImpl.methodB();
}
}
realImpl.methodX(callback);
}
and in your inner bean:
void methodX(Callable<Void> callback) {
...code before...
callback();
...code after...
}
I have a duplicated configuration file under WEB-INF directory, called configDEV.properties and configPRO.properties (one for development environment, and the other for production environment).
I load the proper file thanks to these Spring declaration and this Tomcat launch parameter:
<context:property-placeholder
location="WEB-INF/config${project.environment}.properties" />
-Dproject.environment=PRO
(or –Dproject.environment=DEV)
Then, in a servlet listener (called StartListener) I do the following, in order to allow JSF to access these properties, in the managed beans, and in the jsp views. (In concrete, we are going to play with a property called cfg.skin.richSelector).
public class StartListener implements ServletContextListener {
public void contextInitialized(ServletContextEvent sce) {
//Environment properties
Map<String, String> vblesEntorno = System.getenv();
//Project properties
String entorno = vblesEntorno.get("project.environment");
String ficheroPropiedades = "/WEB-INF/config" + entorno + ".properties";
try {
Properties props = new Properties();
props.load(sc.getResourceAsStream(ficheroPropiedades));
setSkinRichSelector(sc, props.getProperty("cfg.skin.richSelector"));
} catch (Exception e) {
//...
}
}
private void setSkinRichSelector(ServletContext sc, String skinRichSelector) {
sc.setInitParameter("cfg.skin.richSelector", skinRichSelector);
}
public void contextDestroyed(ServletContextEvent sce) {}
}
In a JSF managed bean:
public class ThemeSwitcher implements Serializable {
private boolean richSelector;
public ThemeSwitcher() {
richSelector = Boolean.parseBoolean(
FacesContext.getCurrentInstance().getExternalContext().getInitParameter("cfg.skin.richSelector"));
if (richSelector) {
//do A
} else {
//do B
}
}
//getters & setters
}
In a xhtml page:
<c:choose>
<c:when test="#{themeSwitcher.richSelector}">
<ui:include src="/app/comun/includes/themeSwitcherRich.xhtml"/>
</c:when>
<c:otherwise>
<ui:include src="/app/comun/includes/themeSwitcher.xhtml"/>
</c:otherwise>
</c:choose>
All of this WORKS OK, but I want to ask the experts if it is the most suitable way to do that, or if this could be simplified in some way???
Thanks in advance for your hints and advices
It depends on which version of Spring you are using. If it happens to be newest Spring 3.1, you can take advantage of #Profile:
Springsource Blog
Springsource reference
Leave the property-placeholder in your applicationContext.xml if you are using it in your spring beans.
Configure a context init param in your web.xml like this:
<context-param>
<param-name>envProp</param-name>
<param-value>${project.environment}</param-value>
</context-param>
Also move the properties file under some package in your classpath (Note: Due to this change you need to locate the resource in the application context using classpath*: prefix)
and then you can load the bundle in your JSF page like this:
<f:loadBundle basename="com.examples.config#{initParam['envProp']}" var="msgs"/>
and use something like this:
<h:outputText value="#{msgs.cfg.skin.richSelector}" />
But instead of setting the system property like this configure ProjectStage via JNDI as mentioned by Ryan Lubke in his blog so that you can use the same property even for javax.faces.PROJECT_STAGE context parameter.