The problem is simple
#Around("execution(* package.*Repository.save(..))")
public Object saveInterupt(ProceedingJoinPoint joinPoint) throws Throwable {
// This gets called whenever repository save is called
}
#Around("execution(* package.*Repository.findAll(..))")
public Object findInterupt(ProceedingJoinPoint joinPoint) throws Throwable {
// This IS NOT GETTING called whenever repository findAll is called
}
Breaking head here!
Edit: A small break through. I printed the target , it returns SimpleJpaRepository instead of the Actual repository.
Assuming the repository is of the following construct
public interface JpaEmployeeRepository extends CrudRepository<JpaEmployee, Long> {..}
following pointcut works on both the cases
#Around("execution(* org..*Repository.save(..))")
and
#Around("execution(* org..*Repository.findAll(..))")
If I understand the question correctly the requirement is to intercept on the execution of a particular method within a specific package.If yes, More details on the same can be read here.
#AspectJ pointcut for all methods inside package
Are you using Spring-BOOT ? Aspecting in Spring-BOOT only calls the advice on a single method in the class the first time it is called. If you want Spring-BOOT to respect your #Around advice in all cases for multiple methods in any given class -- you need to access the class as a bean (SB's #Bean)...
Spring-BOOT's AOP is not 100% the same as aspecting using AspectJ -- the main difference is AspectJ modifies the byte-code where Spring uses a dynamic proxy.
Related
I understand that Spring AOP is very limited in its abilities (it can only cut into public methods of classes that are Spring beans, and only when those methods are called from outside the class). But now I've discovered another baffling limitation when interfaces are involved.
Normally, if a class is subclassed, Spring AOP has no problem cutting into all of their methods (even overridden ones):
public class A {
public void methodA() { } //OK, can cut in
}
public class B extends A {
#Override
public void methodA() { } //OK, can cut in
public void methodB() { } //OK, can cut in
}
But when we add an interface into the mix, things get pretty bad for Spring AOP:
public interface I {
public void methodA();
}
public class A implements I {
#Override
public void methodA() { } //OK, can cut in
public void methodB() { } //Fail, cannot see or cut into this method
}
public class B extends A {
#Override
public void methodA() { } //Fail, cannot see or cut into this method
public void methodC() { } //Fail, cannot see or cut into this method
}
First of all, Spring AOP can only cut into methods that are in the interface, anything else - it cannot see. Second, it can only cut into the method that directly implements the interface's method - A.methodA(). It cannot cut into the same method overridden by B.
I am using a generic pointcut expression "execution(* method*(..))" to cut into all possible methods, so it's not an expression issue.
Is there any way around this limitation? Or should I just forget about Spring AOP and use a different approach?
UPDATE:
Ok, I have found the real cause of the problem. I was actually relying on Intellij IDEA's AOP plugin to test this. It's supposed to link the pointcut to all affected methods. But it was using the 'old', dynamic JDK proxy strategy instead of the new, CGLIB strategy. So it wasn't linking it to all methods, but when I actually ran my program, it would cut into all methods correctly.
I'm using Spring Boot 2, which uses the 'new' CGLIB strategy. But on SB1 it might still use the 'old' dynamic JDK proxy strategy, so it might still not work there.
Spring will use either dynamic proxy or cglib to implement AOP.
Cglib is picked if there is no interface, then it will effectively create a subclass of the target class, and override all methods in the target class. With this way all methods could be cut in, except final and static ones.
In case the target class is with interface, then Spring might use a dynamic proxy using one of the interface, and apprantly this will only affect the methods declared in the interface.
Before spring-boot 2.0, dynamic proxy is the default strategy. Now Cglib is the default strategy after spring-boot 2.0.
It seems to me spring probably take the dynamic proxy approach in your case. You could add spring.aop.proxy-target-class: true in your application.yaml to force use Cglib.
In case you still have issue, it's better to post more complete code snippet showing how the mothods are invoked.
I'm currently using IAnnotationTransformer to exclude all tests listed in an XML which contain a particular string in their name, for example:
#Override
#SuppressWarnings("rawtypes")
public void transform(ITestAnnotation annotation, Class testClass, Constructor testConstructor, Method testMethod) {
if(testMethod.getName().contains("StringGoesHere")){
annotation.setEnabled(false);
}
This works perfectly well, however due to the naming convention of my tests, and the vast amount of tests which I need to exclude, the ideal solution would be to find a way to exclude the test if it calls upon a particular method. For example, I want to exclude all tests which call upon the method .clickUserSettings() shown below:
#Test
public void editUserDetailsOnStackOverflow() {
final StackOverflow so = stackOverflow.loginAsMe()
.clickUserSettings()
.changeUserEmail()
.clickSave()
.assertChangeSuccessful();
}
Does anyone know how I might be able to achieve this? I feel like I've explored all other avenues (classes, packages, testMethods, groups, xml, etc) And the reason it has to be done in this way is because I currently have 40,000 tests (15,000 of which will likely be excluded depending on the browserMode variable set by the tester) This is the general idea I was hoping to achieve:
#Override
#SuppressWarnings("rawtypes")
public void transform(ITestAnnotation annotation, Class testClass, Constructor testConstructor, Method testMethod) {
if(testMethod.getAllCalledMethods.contains(".clickUserSettings()")){
annotation.setEnabled(false);
}
If this can be done it will solve my issue,
Cheers
This is not possible with TestNG. TestNG has visibility only to the methods that are annotated with its annotations and its not aware of the code path that a TestNG method traverses during its course of execution. [ Here I am assuming that the method clickUserSettings() is a utility method that your #Test methods perhaps call into, as part of its execution ]
So you have a couple of options
Resort to using groups wherein all methods that call into clickUserSettings() as part of a particular group and then exclude that group from execution.
Create your own custom annotation, annotate all methods that invoke clickUserSettings() using this annotation, and then modify your current AnnotationTransformer to look for this annotation at either the method level or at the class level.
I'm currently migrating one of my projects form "self configured spring" to spring boot. while most of the stuff is already working I have a problem with a #Transactional method where when it is called the context is not present as set before due to a call to the "target" instance instead of the "proxy" instance (I'll try to elaborate below).
First a stripped down view of my class hierarchy:
#Entity
public class Config {
// fields and stuff
}
public interface Exporter {
int startExport() throws ExporterException;
void setConfig(Config config);
}
public abstract class ExporterImpl implements Exporter {
protected Config config;
#Override
public final void setConfig(Config config) {
this.config = config;
// this.config is a valid config instance here
}
#Override
#Transactional(readOnly = true)
public int startExport() throws ExporterException {
// this.config is NULL here
}
// other methods including abstract one for subclass
}
#Scope("prototype")
#Service("cars2Exporter")
public class Cars2ExporterImpl extends ExporterImpl {
// override abstract methods and some other
// not touching startExport()
}
// there are other implementations of ExporterImpl too
// in all implementations the problem occurs
the calling code is like this:
#Inject
private Provider<Exporter> cars2Exporter;
public void scheduleExport(Config config) {
Exporter exporter = cars2Exporter.get();
exporter.setConfig(config);
exporter.startExport();
// actually I'm wrapping it here in a class implementing runnable
// and put it in the queue of a `TaskExecutor` but the issue happens
// on direct call too. :(
}
What exactly is the issue?
In the call to startExport() the field config of ExporterImpl is null although it has been set right before.
What I found so far:
With a breakpoint at exporter.startExport(); I checked the id of the exporter instance shown by eclipse debugger. In the debbug round while writing this post it is 16585. Continuing execution into the call/first line of startExport() where i checked the id again (of this this time) expecting it to be the same but realizing that it is not. It is 16606 here... so the call to startExport() is done on another instance of the class... in a previous debug round i checked to wich instance/id the call to setConfig() is going... to the first on (16585 in this case). This explains why the config field is null in the 16606 instance.
To understand what happens between the line where i call exporter.startExport(); and the actuall first line of startExport() i clicked into the steps between those both lines in eclipse debugger.
There i came to line 655 in CglibAopProxy that looks like this:
retVal = new CglibMethodInvocation(proxy, target, method, args, targetClass, chain, methodProxy).proceed();
checking the arguments here i found that proxy is the instance with id 16585 and target the one with 16606.
unfortunately I'm not that deep into springs aop stuff to know if that is how it should be...
I just wonder why there are two instances that get called on diffrent methods. the call to setConfig() goes to the proxy instance and the call do startExport() reaches the target instance and thus does not have access to the config previously set...
As mentioned the project has been migrated to spring boot but we where before already using the Athens-RELEASE version of spring platform bom. From what i can tell there where no special AOP configurations before the migration and no explicitly set values after the migration.
To get this problem fixed (or at least somehow working) i already tried multiple things:
remove #Scope from the sub class
move #Transactional from method level to class
override startExport() in subclass and put #Transactional here
add #EnableAspectJAutoProxy to application class (i wasn't even able to log in - no error message)
set spring.aop.proxy-target-class to true
the above in diffrent combinations...
Currently I'm out of clues on how to get this back working...
Thanks in advance
*hopes someone can help*
Spring Boot tries to create a cglib proxy, which is a class based proxy, before you probably had an interface based (JDK Dynamic Proxy).
Due to this a subclass of your Cars2ExporterImpl is created and all methods are overridden and the advices will be applied. However as your setConfig method is final that cannot be overridden and as a result that method will be actually called on the proxy instead on the proxied instance.
So either remove the final keyword so that CgLib proxy can be created or explicitly disable class based proxies for transactions. Add #EnableTransationManagement(proxy-target-class=false) should also do the trick. Unless there is something else triggering class based proxies that is.
I am trying to write a code for custom annotation. when I use this annotation on any method, then before execution and after execution of method some simple print msg should execute. I tried like this :
import java.lang.annotation.*;
#Retention(RetentionPolicy.RUNTIME)
#interface DemoAnnotation {
String value();
String value1();
}
// Applying annotation
class CustomAnnotationExample {
#DemoAnnotation(value = "code is started!!!", value1= "code is completed!!!")
public void sayHello() {
System.out.println("hello Annotation Example");
}
}
and in another main method I called it like :
CustomAnnotationExample h=new CustomAnnotationExample();
Method m=h.getClass().getMethod("sayHello");
DemoAnnotation anno=m.getAnnotation(DemoAnnotation.class);
System.out.println(anno.value());
h.sayHello();
System.out.println(anno.value1());
I want to print values from annotation without using System.out.println() in main method . when I just call sayHello() method . annotation values should get printed before and after execution of sayHello() method.
Please help me on this.
There are two ways, both of them very complex, runtime and compile time solution:
The runtime solution relies on specific framework which is used to instantiate the application. The common way is to create wrapping proxies for the final object and do the stuff from the proxy before (or after) calling the original object method.
For spring for example the solution is to register BeanPostProcessor object which would intercept the instantiation of the bean and check whether some of the method contains the DemoAnnotation annotation. In case it does, it would create a proxy to that object and return the proxy as the real bean.
Second solution is compile time solution and is based on annotation processors which can modify the java compiler behavior. You need to create and register annotation processor and after parsing the source file checking the annotations on the method and add the relevant code during the compilation time. There are many helpers, you can for example register on TreeScanner.visitMethod() method and invoke the TreeScanner from your annotation processor.
Generally the good example can be found in lombok which does similar things in terms of modifying the code during the compile time.
This is the code I have:
#Cacheable(value = "configurationCache", key = "#myFile.lastModified()")
private Object foo(File myFile) throws IOException {
System.out.println(myFile.lastModified());
try {
Thread.sleep(6000);
} catch (InterruptedException ignored) {
}
final Object foo = new SomeObjectFromFile(myFile);
return foo;
}
I call this method twice passing file objects that have the same lastmodified value but caching does not work, the method will wait for 6 seconds.
Here is the output I am getting:
1456298573000
1456298573000
What am I doing wrong?
key = "#myFile.lastModified"
did not work either..
I am sure my configuration with ehcache is fine.
Juliens answer is probably the right one assuming you do not use aspectj. its not alone invoking a public method, but invoking a public method of an object where spring had the chance to wrap it's proxies around. So make sure you are injecting the service that you want to have enhanced with cacheable support.
For example
#Service
public SomeService {
#Autowired
private CacheEnhancedService css;
public void doSomething() {
css.getConfig(new File("./file"));
}
}
#Service
public CacheEnhancedService {
#Cacheable(value = "configurationCache", key = "#myFile.lastModified()")
public Object getConfig(File myFile) {
...
}
}
}
The issue lies with the fact that your method is private.
As mentioned in the documentation of the Spring Framework:
Method visibility and cache annotations
When using proxies, you should apply the cache annotations only to
methods with public visibility. If you do annotate protected, private
or package-visible methods with these annotations, no error is raised,
but the annotated method does not exhibit the configured caching
settings. Consider the use of AspectJ (see below) if you need to
annotate non-public methods as it changes the bytecode itself.
[...]
In proxy mode (which is the default), 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
caching at runtime even if the invoked method is marked with
#Cacheable - considering using the aspectj mode in this case. Also,
the proxy must be fully initialized to provide the expected behaviour
so you should not rely on this feature in your initialization code,
i.e. #PostConstruct.
You should either switch to a public method and make and external call or user AspectJ.