I have the following definition of an aspect and other classes that are co-working.
package concert;
public aspect CriticAspect {
public CriticAspect() {}
pointcut performance(): execution(* perform(..));
afterReturning() : performance() {
System.out.println(criticismEngine.getCriticism());
}
private CriticismEngine criticismEngine;
public void setCriticismEngine(CriticismEngine criticismEngine) {
this.criticismEngine = criticismEngine;
}
}
CriticismEngine
package concert;
public interface CriticismEngine {
String getCriticism();
}
CriticismEngineImpl
package concert;
public class CriticismEngineImpl implements CriticismEngine {
public CriticismEngineImpl() {}
public String getCriticism() {
int i = (int) (Math.random() * criticismPool.length);
return criticismPool[i];
}
// injected
private String[] criticismPool;
public void setCriticismPool(String[] criticismPool) {
this.criticismPool = criticismPool;
}
}
Performance
package concert;
public interface Performance {
void perform();
}
PerformanceImple
package concert;
public class Concert implements Performance {
#Override
public void perform() {
System.out.println("Playing a concert!");
}
}
Configuration
package concert.config;
import concert.Concert;
import concert.CriticAspect;
import concert.CriticismEngine;
import concert.CriticismEngineImpl;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
#Configuration
#EnableAspectJAutoProxy()
public class ApplicationConfiguration {
#Bean
public CriticAspect criticAspect() {
return CriticAspect.aspectOf();
}
#Bean
public CriticismEngine criticismEngine() {
CriticismEngineImpl criticismEngine = new CriticismEngineImpl();
String[] criticisms = { "Worst performance ever!",
"I laughed, I cried, then I realized I was at the wrong show.",
"A must see show!" };
criticismEngine.setCriticismPool(criticisms);
return criticismEngine;
}
#Bean
public Concert concert() {
return new Concert();
}
}
Main
package concert;
import concert.config.ApplicationConfiguration;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
public class Main {
public static void main(String[] args) {
ApplicationContext context = new AnnotationConfigApplicationContext(ApplicationConfiguration.class);
Performance concert = context.getBean("concert", Performance.class);
concert.perform();
}
}
Dependencies
compile "org.springframework:spring-context:${springVersion}"
compile "org.springframework:spring-aop:${springAopVersion}"
compile "org.aspectj:aspectjrt:${aspectJRuntimeVersion}"
compile "org.aspectj:aspectjweaver:${aspectJWeaverVersion}"
But intellij says it cannot find CriticAspect. How I can run this example ? Or I'm doing something wrong ?
IMO #EnableAspectJAutoProxy does not make sense for a native AspectJ aspect. It only works for Spring AOP aspects. So you have two options:
Either you compile your native AspectJ aspect right into your Spring application via Ajc (AspectJ compiler). In this case you do not need any annotations to get the aspects running, only aspectjrt.jar on the classpath during runtime.
Or you go the canonical way described in the Spring manual (chapter 9.8) and use LTW (load-time weaving) for native AspectJ aspects. This can be enabled in your configuration via #EnableLoadTimeWeaving or <context:load-time-weaver/>, respectively. See chapter 9.8.4, Load-time weaving with AspectJ in the Spring Framework.
Related
I am trying to recast my SpringBoot application to functional bean registration form for faster application start up times as mentioned in the Spring documentation for Spring Cloud Functions. Below is the code referenced in the documentation:
#SpringBootConfiguration
public class DemoApplication implements ApplicationContextInitializer<GenericApplicationContext> {
public static void main(String[] args) {
FunctionalSpringApplication.run(DemoApplication.class, args);
}
public Function<String, String> uppercase() {
return value -> value.toUpperCase();
}
#Override
public void initialize(GenericApplicationContext context) {
context.registerBean("demo", FunctionRegistration.class,
() -> new FunctionRegistration<>(uppercase())
.type(FunctionType.from(String.class).to(String.class)));
}
}
Here is my code attempting to follow the above exaple. The main difference is that my function is a separate class that implements the Java 8 function interface.
import com.example.functions.RockPaperScissors;
import com.example.model.Game;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.SpringBootConfiguration;
import org.springframework.cloud.function.context.FunctionRegistration;
import org.springframework.cloud.function.context.FunctionType;
import org.springframework.cloud.function.context.FunctionalSpringApplication;
import org.springframework.context.ApplicationContextInitializer;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.support.GenericApplicationContext;
#Slf4j
#ComponentScan
#SpringBootConfiguration
public class Application implements ApplicationContextInitializer<GenericApplicationContext> {
private RockPaperScissors rockPaperScissors;
public static void main(String[] args) {
FunctionalSpringApplication.run(Application.class, args);
}
#Override
public void initialize(GenericApplicationContext applicationContext) {
applicationContext.registerBean(
"rpsFunction",
FunctionRegistration.class,
() ->
new FunctionRegistration<>(this.rockPaperScissors)
.type(FunctionType.from(Game.class).to(String.class)));
}
#Autowired
public void setRpsFunction(RockPaperScissors rockPaperScissors) {
this.rockPaperScissors = rockPaperScissors;
}
}
The issue I am having is java.lang.IllegalArgumentException: 'target' must not be null. I know this is due to no application context being found but not sure why FunctionalSpringApplication.run(DemoApplication.class, args); isn't creating that context.
I have a random class in a random package that is loaded through reflection after the app launches, is there a way for it to be registered as a component under springboot and have annotations such as #Autowired and #Value etc work for that class.
It works when it is in the same package at launch time, but if introduce it thorough another jar at runtime (same package or not) it doesn't work.
Below are samples that don't work even if it is in the same jar. I can't change the app's configuration - it would defeat the "random package/random class" objective.
Code in Spring boot application package
package sample.app
#SpringBootApplication
public class Application {
public static void main(String[] args) {
// Code that starts app
//
//
try {
Thread.sleep(7000);
Class test = Class.forName("test.Test", true, Application.class.getClassLoader());
System.out.println(test.getMethod("getName").invoke(null)); //NPE
System.out.println(test.getMethod("getProfiles").invoke(null)); //NPE
} catch (Throwable t) {
t.printStackTrace();
}
}
}
Test.java
package test;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.DependsOn;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.Map;
#DependsOn("blaaaaaaaah")
#ComponentScan
public class Test {
#DependsOn("blaaaaaaaah")
public static String getName() {
return SpringGetter.instance.getApplicationName();
}
#DependsOn("blaaaaaaaah")
public static String[] getProfiles() {
String[] profiles = SpringGetter.instance.getEnv().getActiveProfiles();
if (profiles == null || profiles.length == 0) {
profiles = SpringGetter.instance.getEnv().getDefaultProfiles();
}
return profiles;
}
}
SpringGetter.java
package test;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.env.Environment;
import org.springframework.stereotype.Component;
import javax.annotation.PostConstruct;
#Component("blaaaaaaaah")
public class SpringGetter implements InitializingBean {
public static SpringGetter instance;
#Value("${spring.application.name}")
private String applicationName;
#Autowired
private Environment env;
public SpringGetter() {
System.out.println("consASFJEFWEFJWDNFWJVNJSBVJWNCJWBVJNVJNVJSNJSNCSDJVNSVJtruct");
}
public String getApplicationName() {
return applicationName;
}
public void setApplicationName(String applicationName) {
this.applicationName = applicationName;
}
public Environment getEnv() {
return env;
}
public void setEnv(Environment env) {
this.env = env;
}
#PostConstruct
public void setInstance() {
instance = this;
}
#Override
public void afterPropertiesSet() throws Exception {
instance = this;
}
}
EDIT:
I managed to dynamically create the SpringGetter class as part of the same package as the Application class(the one with the #SpringBootApplication). I got Test.java to point to that dynamic class and yet no luck.
To simply inject fields into a POJO as if it were a Spring-managed bean, you can use something like the following:
#Component
public class BeanInitializer implements ApplicationContextAware {
private AutowireCapableBeanFactory beanFactory;
#Override
public void setApplicationContext(final ApplicationContext applicationContext) {
beanFactory = applicationContext.getAutowireCapableBeanFactory();
}
public void initializeObject(Object pojo) {
beanFactory.autowireBean(pojo);
}
}
Note, however, that this only injects fields marked as #Autowired or #Injected. It does not create proxies that honor method interception strategies based on e.g. #Transactional, #Async, etc.
If you're using Spring 5, have a look at the registerBean() method from GenericApplicationContext. You can find an example here: https://www.baeldung.com/spring-5-functional-beans
The issue in your Test class may also be that you're not loading the Spring Boot context from the main class. You can use the SpringBootTest annotation for this.
I would like to use polymorphic configuration properties on Spring, using Spring's #ConfigurationProperties annotation.
Suppose we have the following POJO classes.
public class Base {
private String sharedProperty;
public String getSharedProperty() {
return sharedProperty;
}
public String setSharedProperty(String sharedProperty) {
this.sharedProperty = sharedProperty;
}
}
public class Foo extends Base {
private String fooProperty;
public String getFooProperty() {
return fooProperty;
}
public String setFooProperty(String sharedProperty) {
this. fooProperty = fooProperty;
}
}
public class Bar extends Base {
private String barProperty;
public String getSharedProperty() {
return sharedProperty;
}
public String setBarProperty(String barProperty) {
this.barProperty = barProperty;
}
}
And the configuration properties class,
#Component
#ConfigurationProperties(prefix = "playground")
public class SomeConfigurationProperties {
private List<Base> mixed;
public List<Base> getMixed() {
return mixed;
}
public void setMixed(List<Base> mixed) {
this.mixed = mixed;
}
}
And the application.yml file,
playground:
mixed:
- shared-property: "shared prop"
foo-property: "foo prop"
- shared-property: "shared prop"
bar-property: "bar prop"
However, with this configuration, Spring initializes the #ConfigurationProperties-annotated class with the list of Base objects, instead of their subclasses. That is, actually, an expected behavior (due to security concerns).
Is there a way to enforce the behavior of SnakeYAML to use subclasses, or implement any kind of custom deserialization provider?
Although it is possible to implement custom PropertySources and/or ConversionService, a custom deserialization provider is not necessary.
Spring has no issues binding the same properties to multiple beans. The reason your implementation is not working is because you are only registering one bean with the ApplicationContext with the #Component annotation on the base class. This is telling the component scanner that there is only one singleton of type Base. Because Foo and Bar are not registered as beans, they won't be bound to.
If the only reason you are looking at making these polymorphic is to share property name prefixes in SnakeYAML based config, then you actually do not need to introduce the polymorphic relationship, and can bind to shared properties by a common field name in different classes.
There are many ways to implement what you are asking for however in a polymorphic way, here are a few of the most straight forward simple ones:
Self declaring Polymorphic ConfigurationProperties singleton beans
Instead of applying the #ConfigurationProperties and #Component annotations on the base class, apply them on the concrete classes, with the same property name prefix. This wouldn't be my preferred approach, as each bean would not be conditional on their properties being set, however it may suit your needs. Depending on if your Spring Configuration allows properties to be reloaded, Spring will maintain the bindings on all of the beans.
Note: As of IntelliJ Idea 2018.3, an inspection profile was added to identify duplicate prefix keys as an error. You may want to ignore this, or suppress the warnings.
I tested the following successfully:
Base.java
package sample;
public class Base {
private String sharedProperty;
public String getSharedProperty() {
return sharedProperty;
}
public void setSharedProperty(String sharedProperty) {
this.sharedProperty = sharedProperty;
}
}
Foo.java
package sample;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
#Component
#ConfigurationProperties("playground")
public class Foo extends Base {
private String fooProperty;
public String getFooProperty() {
return fooProperty;
}
public void setFooProperty(String fooProperty) {
this.fooProperty = fooProperty;
}
}
Bar.java
package sample;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
#Component
#ConfigurationProperties("playground")
public class Bar extends Base {
private String barProperty;
public String getBarProperty() {
return barProperty;
}
public void setBarProperty(String barProperty) {
this.barProperty = barProperty;
}
}
application.yml
playground:
shared-property: "shared prop"
foo-property: "foo prop"
bar-property: "bar prop"
SampleAppTest.java
package sample;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.core.env.Environment;
import java.util.List;
import static org.junit.jupiter.api.Assertions.assertEquals;
#SpringBootTest
public class SampleAppTest {
#Autowired
public Environment environment;
#Test
public void test(#Autowired Bar bar, #Autowired Foo foo) {
assertEquals("shared prop", bar.getSharedProperty());
assertEquals("shared prop", foo.getSharedProperty());
assertEquals("bar prop", bar.getBarProperty());
assertEquals("foo prop", foo.getFooProperty());
}
#Test
public void testSuper(#Autowired List<Base> props) {
assertEquals(2, props.size());
}
}
Polymorphic ConfigurationProperties beans conditional on properties
You may not want certain concrete implementations to be instantiated if their specific properties are missing. Furthermore, you may not want to couple the #ConfigurationProperties and #Component annotations to each concrete class. This implementation constructs the ConfigurationProperties beans via a Spring #Configuration bean. The configuration bean ensures they are only constructed conditionally via a property existence check. This implementation also creates a bean of concrete type Base if none of the other Base beans meet conditions and the shared properties exist. The same unit test from the previous example is used here and passes:
Base.java
package sample;
public class Base {
private String sharedProperty;
public String getSharedProperty() {
return sharedProperty;
}
public void setSharedProperty(String sharedProperty) {
this.sharedProperty = sharedProperty;
}
}
Foo.java
package sample;
public class Foo extends Base {
private String fooProperty;
public String getFooProperty() {
return fooProperty;
}
public void setFooProperty(String fooProperty) {
this.fooProperty = fooProperty;
}
}
Bar.java
package sample;
public class Bar extends Base {
private String barProperty;
public String getBarProperty() {
return barProperty;
}
public void setBarProperty(String barProperty) {
this.barProperty = barProperty;
}
}
SampleConfiguration.java
package sample;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
#Configuration
public class SampleConfiguration {
#Bean
#ConfigurationProperties("playground")
#ConditionalOnProperty("playground.foo-property")
public Foo foo() {
return new Foo();
}
#Bean
#ConfigurationProperties("playground")
#ConditionalOnProperty("playground.bar-property")
public Bar bar() {
return new Bar();
}
#Bean
#ConfigurationProperties("playground")
#ConditionalOnProperty("playground.shared-property")
#ConditionalOnMissingBean(Base.class)
public Base base() {
return new Base();
}
}
application.yml
playground:
shared-property: "shared prop"
foo-property: "foo prop"
bar-property: "bar prop"
SampleAppTest.java
package sample;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.core.env.Environment;
import java.util.List;
import static org.junit.jupiter.api.Assertions.assertEquals;
#SpringBootTest
public class SampleAppTest {
#Autowired
public Environment environment;
#Test
public void test(#Autowired Bar bar, #Autowired Foo foo) {
assertEquals("shared prop", bar.getSharedProperty());
assertEquals("shared prop", foo.getSharedProperty());
assertEquals("bar prop", bar.getBarProperty());
assertEquals("foo prop", foo.getFooProperty());
}
#Test
public void testSuper(#Autowired List<Base> props) {
assertEquals(2, props.size());
}
}
My intention is run the aspect before get message method in service. I don't want to use xml configuration, so I add (hopefully) necessary annotation. But, when I run my application, aspect doesen't work, and nothing happen. Can You explain me why?
Service
public interface Service {
void getMessage();
}
Service implementation
import org.springframework.stereotype.Component;
#Service
public class ServiceImpl implements Service {
public void getMessage() {
System.out.println("Hello world");
}
}
Aspect
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
#Aspect
public class LoggingAspect {
#Before("execution(* com.example.aop.Service.getMessage())")
public void logBefore(JoinPoint joinPoint) {
System.out.println("AOP is working!!!");
}
}
Run
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
#SpringBootApplication
#EnableAspectJAutoProxy(proxyTargetClass=true)
#ComponentScan("com.example")
public class AopApplication {
public static void main(String[] args) {
final ConfigurableApplicationContext run = SpringApplication.run(AopApplication.class, args);
final Service bean = run.getBean(ServiceImpl.class);
bean.getMessage();
}
}
Output
Only Hello world
Probably, you have to add #Component annotation to LoggingAspect class.
I believe the pointcut expression syntax should be like this:
#Before("execution(void com.aop.service.Service+.getMessage(..))")
The + is used to apply the pointcut to subtypes (you can replace void with * too.
I want to wrap all methods annotated with #Annotation1 but not with #Annotation2.
So far I have tried 3 approaches, but all have failed. The first is with a pointcut expression. Example:
#Before("#annotation(Annotation1) && !#annotation(Annotation2)")
public void doTheWrapping() {
System.out.println("Wrapped!");
}
This approach wraps everything annotated with Annotation1 regardless of Annotation2.
The second approach is to detect Annotation2 manually, but this does not seem to work either.
#Before("#annotation(Annotation1)")
public void doTheWrapping(final JoinPoint joinPoint) {
Method method = MethodSignature.class.cast(joinPoint.getSignature()).getMethod();
if (AnnotationUtils.getAnnotation(method, Annotation2.class) == null) {
System.out.println("Wrapped!");
}
}
This fails because AnnotationUtils.getAnnotations(method) always return null. It doesn't seem to be aware of the annotations on the method at all.
Finally, I tried using #Pointcuts.
#Pointcut("execution(#Annotation1 * *(..))")
public void annotatedWithAnnotation1() {}
#Pointcut("execution(#Annotation2 * *(..))")
public void annotatedWithAnnotation2() {}
#Before("annotatedWithAnnotation1() && !annotatedWithAnnotation2()")
public void doTheWrapping() {
System.out.println("Wrapped!");
}
This again, just wraps everything regardless of Annotation2.
Can anyone assist?
SOLUTION
Turns out the answer was really very simple. Moving the #Annotation2 to the interface rather than the implementation solved this.
Your first pointcut should work, as long as the aspect is in the same package as both annotations. Otherwise you need to specify fully qualified class names. Here is a stand-alone AspectJ example. It should be the same in Spring AOP:
package de.scrum_master.app;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
#Retention(RetentionPolicy.RUNTIME)
public #interface Annotation1 {}
package de.scrum_master.app;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
#Retention(RetentionPolicy.RUNTIME)
public #interface Annotation2 {}
package de.scrum_master.app;
public class Application {
#Annotation1
public void foo() {}
#Annotation2
public void bar() {}
#Annotation1
#Annotation2
public void zot() {}
public void baz() {}
public static void main(String[] args) {
Application application = new Application();
application.foo();
application.bar();
application.zot();
application.bar();
}
}
package de.scrum_master.aspect;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
#Aspect
public class MyAspect {
#Before("#annotation(de.scrum_master.app.Annotation1) && !#annotation(de.scrum_master.app.Annotation2)")
public void doTheWrapping(JoinPoint thisJoinPoint) {
System.out.println(thisJoinPoint);
}
}
Console output for AspectJ:
call(void de.scrum_master.app.Application.foo())
execution(void de.scrum_master.app.Application.foo())
Console output for Spring AOP:
Because Spring AOP does not support call() pointcuts, only the execution() joinpoint will be intercepted as long as class Application is a Spring #Component:
execution(void de.scrum_master.app.Application.foo())