I'm building a package that is trying to intercept a function's return value based on a flag. My design involves some AOP. The idea is that a class FirstIntercept intercepts a call firstCall and stores parameters in a Parameters object. Then later, a second class SecondIntercept intercepts another call secondCall and does some logic based on what is populated in Parameters:
// pseudoish code
public class FirstIntercept {
private Parameters param;
#AfterReturning(pointcut = "execution(* ...firstCall(..))", returning = "payload")
public void loadParam(Joinpoint joinPoint, Object payload) {
// logic handling payload returned from firstCall()
// logic provides a Boolean flag
this.param = new Parameters(flag);
}
}
public class Parameters {
#Getter
private Boolean flag;
public Parameters(Boolean flag) {
this.flag = flag;
}
}
public class SecondIntercept {
private static Parameters params;
#Around("execution(* ...secondCall(..))")
public void handleSecondCallIntercept(ProceedingJoinPoint joinPoint) {
// want to do logic here based on what params contains
}
}
What I want to achieve is that the Parameters object is loaded once and for all when FirstIntercept.loadParam is invoked through AOP. I'm not too sure how I can go about with this persistence. I looked online and Google guice seems to be promising. I believe a first step would to use dependency injection on the Parameters, but I'm really not sure. Can someone help point me in the right direction?
edit:
So I tried this setup:
public class FirstIntercept implements MethodInterceptor {
public Object invoke(MethodInvocation invocation) throws Throwable {
System.out.println("invoked!");
return invocation.proceed();
}
#AfterReturning(pointcut = "execution(* ...firstCall(..))", returning = "payload")
public void loadParam(Joinpoint joinPoint, Object payload) {
// do stuff
}
public String firstCall() {
return "hello";
}
}
public class InterceptionModule extends AbstractModule {
protected void configure() {
FirstIntercept first = new FirstIntercept();
bindInterceptor(Matchers.any(), Matchers.annotatedWith(AfterReturning.class), first);
}
}
public class FirstIterceptTest {
#Test
public void dummy() {
Injector injector = Guice.createInjector(new InterceptionModule());
FirstIntercept intercept = injector.getInstance(FirstIntercept.class);
intercept.firstCall();
}
}
When I do .firstCall(), I can see the #AfterReturning running but the invoke is not being called.
If you expand upon the documentation for AOP https://github.com/google/guice/wiki/AOP you should get something close to:
public class FirstInterceptor implements MethodInterceptor {
#Inject Parameters parameters; // Injected with singleton Parameter
public Object invoke(MethodInvocation invocation) throws Throwable {
Object result = invocation.proceed();
// your logic based on result to set parameters.setFlag()
return result;
}
}
Then the second:
public class SecondInterceptor implements MethodInterceptor {
#Inject Parameters parameters; // Injected with singleton Parameter
public Object invoke(MethodInvocation invocation) throws Throwable {
boolean flag = parameters.getFlag();
// your logic here
return invocation.proceed(); // maybe maybe not?
}
}
Your parameters is the key, you'll need to ensure it's thread safe, which is another topic. But to inject these you need:
public class InterceptionModule extends AbstractModule {
protected void configure() {
// Ensure there is only ever one Parameter injected
bind(Parameter.class).in(Scopes.SINGLETON);
// Now inject and bind the first interceptor
FirstInterceptor firstInterceptor = new FirstInterceptor();
requestInjection(firstInterceptor );
bindInterceptor(Matchers.any(), Matchers.annotatedWith(AfterReturning.class),
firstInterceptor);
// Now inject and bind the second interceptor
SecondInterceptor SecondInterceptor = new SecondInterceptor ();
requestInjection(firstInterceptor);
bindInterceptor(Matchers.any(), Matchers.annotatedWith(AfterReturning.class),
SecondInterceptor);
}
}
Edit
Look at what you're doing.
You're telling Guice to wrap a method with #AfterReturn with the FirstInterceptor
Then you're calling interceptor.firstCall()
First call does not have #AfterReturn annotation, so why would it be matched against that configuration?
I'm guessing if you called:
intercept.loadParam();
you would see the invoke method. Also, this is great for a test, but in real life you want to have a Service level class have the #AfterReturn which is then Injected into another Api/Job/Etc that will call LoadParam.
edit
Oh no. Take a look at this line
bindInterceptor(Matchers.any(), // a class with this matcher
Matchers.annotatedWith(AfterReturning.class), // a method with this
firstInterceptor);
This means that the injector only fires on the loadParams. You need to annotate the method of the class youw ish to cause the interception with #AfterReturning. And you want the loadParams to be the invoke method.
Related
My Spring Boot application contains several #KafkaListeners, and each listener performs the same steps before and after actually processing the payload: Validate the payload, check whether the event has been processed already, check whether it's a tombstone (null) message, decide whether processing should be retried in case of failure, emit metrics, etc.
These steps are currently implemented in a base class, but because the topics passed to #KafkaListener must be constant at runtime, the method annotated with #KafkaListener is defined in the subclass, and does nothing but pass its parameters to a method in the base class.
This works just fine, but I wonder if there's a more elegant solution. I assume my base class would have to create a listener container programmatically, but after a quick look at KafkaListenerAnnotationBeanPostProcessor, it seems to be quite involved.
Does anyone have any recommendadtions?
Having stumbled upon this question while looking to implement something similar, I first started with Artem Bilan's answer. However this did not work because annotations by default are not inherited in child classes unless they are themselves annotated with #Inherited. Despite this there may yet be a way to make an annotation approach work and I will update this answer if and when I get it to work. Thankfully though I have achieved the desired behavour using programtic registration of the Kafka listeners.
My code is something like the following:
Interface:
public interface GenericKafkaListener {
String METHOD = "handleMessage";
void handleMessage(ConsumerRecord<String, String> record);
}
Abstract Class:
public abstract class AbstractGenericKafkaListener implements GenericKafkaListener {
private final String kafkaTopic;
public AbstractGenericKafkaListener(final String kafkaTopic) {
this.kafakTopic = kafkaTopic;
}
#Override
public void handleMessage(final ConsumerRecord<String, String> record) {
//do common logic here
specificLogic(record);
}
protected abstract specificLogic(ConsumerRecord<String, String> record);
public String getKafkaTopic() {
return kafkaTopic;
}
}
We can then programtically register all beans of type AbstractGenericKafkaListener in a KafkaListenerConfigurer:
#Configuration
public class KafkaListenerConfigurataion implements KafkaListenerConfigurer {
#Autowired
private final List<AbstractGenericKafkaListener> listeners;
#Autowired
private final BeanFactory beanFactory;
#Autowired
private final MessageHandlerMethodFactory messageHandlerMethodFactory;
#Autowired
private final KafkaListenerContainerFactory kafkaListenerContainerFactory;
#Value("${your.kafka.consumer.group-id}")
private String consumerGroup;
#Value("${your.application.name}")
private String service;
#Override
public void configureKafkaListeners(
final KafkaListenerEndpointRegistrar registrar) {
final Method listenerMethod = lookUpMethod();
listeners.forEach(listener -> {
registerListenerEndpoint(listener, listenerMethod, registrar);
});
}
private void registerListenerEndpoint(final AbstractGenericKafkaListener listener,
final Method listenerMethod,
final KafkaListenerEndpointRegistrar registrar) {
log.info("Registering {} endpoint on topic {}", listener.getClass(),
listener.getKafkaTopic());
final MethodKafkaListenerEndpoint<String, String> endpoint =
createListenerEndpoint(listener, listenerMethod);
registrar.registerEndpoint(endpoint);
}
private MethodKafkaListenerEndpoint<String, String> createListenerEndpoint(
final AbstractGenericKafkaListener listener, final Method listenerMethod) {
final MethodKafkaListenerEndpoint<String, String> endpoint = new MethodKafkaListenerEndpoint<>();
endpoint.setBeanFactory(beanFactory);
endpoint.setBean(listener);
endpoint.setMethod(listenerMethod);
endpoint.setId(service + "-" + listener.getKafkaTopic());
endpoint.setGroup(consumerGroup);
endpoint.setTopics(listener.getKafkaTopic());
endpoint.setMessageHandlerMethodFactory(messageHandlerMethodFactory);
return endpoint;
}
private Method lookUpMethod() {
return Arrays.stream(GenericKafkaListener.class.getMethods())
.filter(m -> m.getName().equals(GenericKafkaListener.METHOD))
.findAny()
.orElseThrow(() ->
new IllegalStateException("Could not find method " + GenericKafkaListener.METHOD));
}
}
How about this:
public abstract class BaseKafkaProcessingLogic {
#KafkaHandler
public void handle(Object payload) {
}
}
#KafkaListener(topics = "topic1")
public class Topic1Handler extends BaseKafkaProcessingLogic {
}
#KafkaListener(topics = "topic2")
public class Topic2Handler extends BaseKafkaProcessingLogic {
}
?
I needed the same functionality and came up with solution close to Artem Bilan answer. Yes, #KafkaHandler annotation is not inherited by the child classes but defined in interface it is. Here is the solution:
interface AbstractKafkaListener<T> {
default Class<T> getCommandType() {
TypeToken<T> type = new TypeToken<>(getClass()) {};
return (Class<T>) type.getRawType();
}
#KafkaHandler
default void handle(String message) throws JsonProcessingException {
ObjectMapper objectMapper = new ObjectMapper();
T value = objectMapper.readValue(message, getCommandType());
handle(value);
}
void handle(T message);
}
The class should implement the handle method only:
#Component
#KafkaListener(topics = "my_topic")
public class KafkaListenerForMyCustomMessage implements AbstractKafkaListener<MyCustomMessage> {
#Override
public void handle(MyCustomMessage message) {
System.out.println(message);
}
}
The 2 implemented methods in the interface should be private/protected but because they are in interface this cannot be done. default methods are always public. Actually, all methods defined in interface are always public.
I use this solution to dynamically parse the message from kafka (received in String) to the custom class.
getCommandType method returns the class of the T generic param. TypeToken is from Google Guava package.
I have annotation:
#Target({ElementType.METHOD})
#Retention(RetentionPolicy.RUNTIME)
#Documented
#Inherited
public #interface Loggable { }
and aspect:
#Aspect
public class AspectLogger {
#Around("#annotation(aspects.Loggable)")
public void aroundLogging(ProceedingJoinPoint joinPoint) {
System.out.println("aroundLogging()");
throw new AuthentificationFailException();
}
}
Also I have interface and class:
public interface IAuthInteractor {
public User authorization(String login, String password);
}
public class AuthInteractor implements IAuthInteractor {
private EntityDAO<User> userDAO;
private ITokenGenerator tokenGenerator;
public AuthInteractor(EntityDAO<User> userDAO,
ITokenGenerator tokenGenerator) {
this.userDAO = userDAO;
this.tokenGenerator = tokenGenerator;
}
#Loggable
public User authorization1(String login, String password) {
return null;
}
#Loggable
public User authorization(String login, String password) {
return null;
}
}
For first method (authorization1) annotation doesn't work. For method authorization (that was described in interafce) annotation works.
Why does it work this way? and how to work without interface?
First of all, the aspect's advice has a void return type, i.e. it will never kick in for methods returning other types such as User in your example. The aspect should not even compile. It does not for me in any case. The AspectJ compiler says:
applying to join point that doesn't return void: method-execution(de.scrum_master.app.User de.scrum_master.app.AuthInteractor.authorization(java.lang.String, java.lang.String))
So, assuming you change your advice to
#Around("#annotation(aspects.Loggable)")
public Object aroundLogging(ProceedingJoinPoint joinPoint) {
System.out.println("aroundLogging()");
throw new AuthentificationFailException();
}
it will compile and also kick in. I tested it locally.
Now let me just quickly change the advice to actually proceed to the original method instead of always throwing an exception so we can test a bit more without catching exceptions all the time. I also want to print the actual joinpoint signature, so we can see what is going on:
#Around("#annotation(aspects.Loggable)")
public Object aroundLogging(ProceedingJoinPoint joinPoint) throws Throwable {
System.out.println(joinPoint);
//throw new AuthentificationFailException();
return joinPoint.proceed();
}
If then you add this main method to your interface implementation class:
public static void main(String[] args) {
System.out.println("Interface object");
IAuthInteractor iAuthInteractor = new AuthInteractor(null, null);
iAuthInteractor.authorization("user", "pw");
System.out.println("\nImplementation object");
AuthInteractor authInteractor = new AuthInteractor(null, null);
authInteractor.authorization("user", "pw");
authInteractor.authorization1("user", "pw");
}
The console log should print something like this, assuming you use AspectJ and not just "AOP lite" via Spring AOP which does not support call() joinpoints:
Interface object
execution(User de.scrum_master.app.AuthInteractor.authorization(String, String))
Implementation object
call(User de.scrum_master.app.AuthInteractor.authorization(String, String))
execution(User de.scrum_master.app.AuthInteractor.authorization(String, String))
call(User de.scrum_master.app.AuthInteractor.authorization1(String, String))
execution(User de.scrum_master.app.AuthInteractor.authorization1(String, String))
As you can see, executions are always caught, but calls are not for interface type instances because the interface method is not annotated, only the implementation.
BTW, method annotations are not inherited anyway, so your #Inherited meta annotation for an annotation type with #Target({ElementType.METHOD}) is kinda useless.
1.How to write junits for this class using mockito?
public class Jerseybinding implements DynamicFeature{
#Override
public void configure(ResourceInfo resourceInfo, FeatureContext context) {
if (SomeImpl.class.equals(resourceInfo.getResourceClass()) || SomeResource.class.equals(resourceInfo.getClass())) {
context.register(new ExampleFilter(new ExampleMatcher()));
}
}
}
I have written the junit but it is throwing error when i'm trying to return to return SomeResource.class.
public class JerseybindingTest {
public void before(){
resourceInfo = Mockito.mock(ResourceInfo.class);
info = Mockito.mock(UriInfo.class);
featureContext = Mockito.mock(FeatureContext.class);
}
#Test
public void testBind() {
Mockito.when(resourceInfo.getClass()).thenReturn(SomeResource.class); // this line also shows error when I return anything.class
Mockito.when(featureContext.register(Mockito.class)).thenReturn(featureContext);// same here
Jerseybinding.configure(resourceInfo,featureContext);
}
}
You'll need to mock out the FeatureContext and assert that register is called as expected.
Something along the lines of:
#Test
public void testConfigure() {
SomeImpl resourceInfo = ... ; // create a new instance of SomeImpl, or mock it if needed
// prepare your mock
FeatureContext context = Mockito.mock(FeatureContext.class);
Mockito.doNothing().when(context).register(Mockito.any(ExampleFilter.class));
// invoke the method under test
JerseyBinding binding = new JerseyBinding();
binding.configure(resourceInfo, context);
// verify that we called register
Mockito.verify(context).register(Mockito.any(ExampleFilter.class));
// verify nothing else was called on the context
Mockito.verifyNoMoreInteractions(context);
}
Alternatively you can also use an ArgumentCaptor if you want to validate the particulars of what is passed into the register method.
If register is a void method, you can use Mockito.doNothing().register(...) as in the example.
If register is not a void method, use Mockito.doReturn(null).register(...) instead.
In Spring 4.2+, we can use #EventListener annotation with a "condition" expression.
In my scenario, I need to match the id of the event object with a regular expression that is configured in a .properties file.
However, it seems impossible to reference any bean's property or method from the condition's regular expression, as the root context seems to be the event object itself.
So far, I have an abstract class, that sets the event id pattern property based on the class name. The goal is to make the implementation of each Event Listener as clean and simple as possible.
#Service
#PropertySource(value = "classpath:subscriberEventMapping.properties")
public abstract class AbstractEventHandler implements IEventHandler {
private String eventIdPattern;
#Autowired
Environment env;
#Autowired(required = true)
public void configureEventIdPattern() {
String simpleClassName = this.getClass().getSimpleName();
String resolvedEventIdPattern = env.getProperty(
simpleClassName.substring(0,1).toLowerCase() +
simpleClassName.substring(1, simpleClassName.length()));
this.eventIdPattern = resolvedEventIdPattern == null ? ".*" : resolvedEventIdPattern;
}
public String getEventIdPattern() {
return eventIdPattern;
}
}
The properties file looks like this:
regExpEventHandler=^(901|909|998|1000)$
dummyEventHandler=^([1-9][0-9]{0,2}|1000)$
And then, I have a sample Event Listener that extends the above Abstract class:
#Service
public class RegExpEventHandler extends AbstractEventHandler {
#Log
private ILog logger;
#Override
#EventListener(condition = "#event.eventid matches #regExpEventHandler.getEventIdPattern()")
public void onEvent(Event event) {
logger.debug("RegExpEventHandler processing : {} with event pattern : {}", event, getEventIdPattern());
}
}
The problem is that the expression
"#event.eventid matches #regExpEventHandler.getEventIdPattern()"
does not work, because the bean "#regExpEventHandler" cannot be found in the context used by the #EventListener.
Is there a way to access methods or properties of an existing Spring Bean here? Any other better approach for this scenario ?
I know I can easily access STATIC constants or methods by using something like:
#event.eventid matches T(my.package.RegExpEventHandler.MY_CONSTANT)
But a String constant (static final) cannot be initialized from a properties file using a #Value expression.
Using NON-FINAL static constants can work, but then EACH Event Listener needs to add boiler-plate to initialize the static constant from a non-static variable using a #Value expression, which we want to avoid.
Thanks a lot in advance !
It works for me - I looked at the EventExpressionEvaluator and saw that it added a bean resolver to the evaluation context...
public EvaluationContext createEvaluationContext(ApplicationEvent event, Class<?> targetClass,
Method method, Object[] args, BeanFactory beanFactory) {
Method targetMethod = getTargetMethod(targetClass, method);
EventExpressionRootObject root = new EventExpressionRootObject(event, args);
MethodBasedEvaluationContext evaluationContext = new MethodBasedEvaluationContext(
root, targetMethod, args, getParameterNameDiscoverer());
if (beanFactory != null) {
evaluationContext.setBeanResolver(new BeanFactoryResolver(beanFactory));
}
return evaluationContext;
}
So I wrote a quick test...
#SpringBootApplication
public class So43225913Application {
public static void main(String[] args) {
ConfigurableApplicationContext context = SpringApplication.run(So43225913Application.class, args);
context.publishEvent("foo");
}
#EventListener(condition = "#bar.accept(event)")
public void listen(Object event) {
System.out.println("handler:" + event);
}
#Bean
public Bar bar() {
return new Bar();
}
public static class Bar {
public boolean accept(Object o) {
System.out.println("bar:" + o);
return true;
}
}
}
and it works just fine...
bar:org.springframework.context.PayloadApplicationEvent[...
handler:foo
(This was with 4.3.7; boot 1.5.2).
Here is my custom annotation AnnoLogExecTime and class AOP:
#Target(ElementType.METHOD)
#Retention(RetentionPolicy.RUNTIME)
#Documented
public #interface AnnoLogExecTime {
}
#Aspect
#Service
public class AOP {
Logger logger = LoggerFactory.getLogger(AOP.class);
#Around("execution(#com.judking.general.aop.AnnoLogExecTime * *(..))")
public Object calExecTime(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
MethodSignature signature = (MethodSignature) proceedingJoinPoint.getSignature();
Method method = signature.getMethod();
long t1 = System.currentTimeMillis();
Object obj = proceedingJoinPoint.proceed();
long t2 = System.currentTimeMillis();
logger.info("method `"+method.getName()+"` takes "+(t2-t1)+"ms");
return obj;
}
}
And the test case is as below:
#Service
class A {
public void go() {
B b = new B() { //Anonymous class B
#Override
public void exec() {
aopMethod();
}
};
b.exec();
}
#AnnoLogExecTime
public void aopMethod() {
System.out.println("aopMethod");
}
}
#Service
class B {
public void exec() {
System.out.println("exec");
}
}
When I call a.aopMethod(), the AOP.calExecTime is hooked up to a.aopMethod().
But if I call a.go(), which is using anonymous class B instance to call a.aopMethod(), then the AOP.calExecTime is NOT hooked up to a.aopMethod().
Could anyone give me an explanation to this phenomenon? And please give me a way to resolve this problem in the case of anonymous class. Thanks a lot!
This is not exactly because it is an anonymous inner class. What you are experiencing is a limitation of AOP proxies.
When you have
A a = ...; // get proxy
The proxy itself wraps the actual instance in a wrapper instance. When you interact with this wrapper instance by calling
a.aopMethod();
the proxy interceptor intercepts the call and can execute the advice.
This would apply to you calling
a.go()
if there was a joinpoint. Instead nothing intercepts that call, and the call to go() goes through the interceptor and the method is called on the actual instance
actualA.go();
When you create the anonymous inner class and have
#Override
public void exec() {
aopMethod();
}
it's implicitly doing
#Override
public void exec() {
A.this.aopMethod();
}
which goes around the proxy because you are calling it on the actual instance, not the wrapper.
You might not be using Spring to generate your proxies, but their documentation explains this pretty well.