Annotate a Spring Boot bootstrap class and implement the annotation elsewhere - java

I want to do something like the following:-
#Retention(RetentionPolicy.RUNTIME)
#Target(ElementType.TYPE)
public #interface MyCustomAnnotation {
public String field1();
public String[] list1();
}
#SpringBootApplication
#MyCustomAnnotation(field1 = "value1", list1 = { "list value 1", "list value2" })
public class Application {
public static void main(String[] args){
SpringApplication.run(Application.class, args);
}
}
public class AnnotationImplementationClass {
// Inject field1 and list1 values from #MyCustomAnnotation into this class
private String field1;
private String[] list1;
}
I want to isolate the AnnotationImplementationClass from the annotated class so that I can package and distribute the custom annotation and its implementation, thus allowing developers to annotate their own spring boot application class with #MyCustomAnnotation.
The constraints are that I will not know the class name for the spring boot class (in this case Application.java) and obviously I will not have access to this class to alter it. I must somehow gain access at runtime so that I can use reflection to obtain the values within the custom annotation.
I have researched examples that attempt to demonstrate the use of BeanPostProcessor but I have been unable to locate the #MyCustomAnnotation when it is applied to the java class containing #SpringBootApplication.

Spring Boot Starter Classes contain "public static void main(String[] args)" method. you can refer the container class by reflection.
It would maybe help you.I don't know how can your goal be done. but your custom annotation should be scanned with highest priority.

I was finally able to resolve this myself. The following is my solution:-
#Retention(RetentionPolicy.RUNTIME)
#Target(ElementType.TYPE)
#Import(AnnotationImplementationClass.class)
public #interface MyCustomAnnotation {
public String field1();
public String[] list1();
}
#SpringBootApplication
#MyCustomAnnotation(field1 = "field1 value", list1 = { "list1 value 1", "list1 value 2" })
public class Application
{
public static void main(String[] args)
{
SpringApplication.run(Application.class, args);
}
}
public class AnnotationImplementationClass implements ApplicationContextAware
{
private String field1;
private String[] list1;
#Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException
{
// Grab the beans from the app context that are annotated with my custom annotation
Map<String, Object> beanMap = applicationContext.getBeansWithAnnotation(MyCustomAnnotation.class);
Collection<Object> beans = beanMap.values();
// There is a possibility that multiple beans are annotated with the annotation. I only annotated one bean
// but I am using a "for" loop for illustration.
for (Object bean : beans)
{
// Spring annotated classes are often proxied when Spring is initializing. I found that I was unable to get
// the annotation and its parameter values from the proxy instance. I need to find the actual class that was
// annotated using the the proxy as a start point. The following "if" clause illustrates the process.
Class<? extends Object> annotatedClass = null;
if (bean instanceof TargetClassAware)
{
annotatedClass = ((TargetClassAware) bean).getTargetClass();
}
else if (ClassUtils.isCglibProxy(bean))
{
annotatedClass = bean.getClass().getSuperclass();
}
else
{
annotatedClass = bean.getClass();
}
// Now I can get the annotation and its parameter values
MyCustomAnnotation annotation = annotatedClass.getAnnotation(MyCustomAnnotation.class);
field1 = annotation.field1();
list1 = annotation.list1();
// Since I only want one of the classes annotated by my custom annotation I break out of the loop
break;
}
}
}
A couple of points to note:-
The use of #Import on the custom annotation interface. This allowed me to hook the implementation class into the Spring context. Not sure if this is a correct use of #Import but it was key to my eventual solution.
The use of "implements ApplicationContextAware" on the implementation class. This provided an entry point for me to take control during Spring initialization.
Hope this helps someone else.

Related

spring boot - integration test autowired interface no such bean found

I have a spring-boot app that now needs to support multiple Object stores and selectively use the desired store based on the environment. Essentially what i have done is create an interface that each store repository then implements.
I have simplified the code for the examples.
I have created 2 beans for each store type based on the spring profile determining the env:
#Profile("env1")
#Bean
public store1Sdk buildClientStore1() {
return new store1sdk();
}
#Profile("env2")
#Bean
public store2Sdk buildClientStore2() {
return new store2sdk();
}
in the service layer I have autowired the interface and then in the repositories i have used #Profile to specify which instance of the interface to use.
public interface ObjectStore {
String download(String fileObjectKey);
...
}
#Service
public class ObjectHandlerService {
#Autowired
private ObjectStore objectStore;
public String getObject(String fileObjectKey) {
return objectStore.download(fileObjectKey);
}
...
}
#Repository
#Profile("env1")
public class Store1Repository implements ObjectStore {
#Autowired
private Store1Sdk store1client;
public String download(String fileObjectKey) {
return store1client.getObject(storeName, fileObjectKey);
}
}
When I start the application with the configured "env" this actually runs as expected. however when running the test I get the "no qualifying bean of type ObjectStore. expected at least 1 bean which qualifies as autowire candidate."
#ExtendWith({ SpringExtension.class })
#SpringBootTest(classes = Application.class)
#ActiveProfiles("env1,test")
public class ComposerServiceTest {
#Autowired
private ObjectHandlerService service;
#Test
void download_success() {
String response = service.getObject("testKey");
...
}
}
As noted in the #ActiveProfile on the test class there are some other environments e.g. dev,test,prod. I have tried playing around with Component scan, having impl and interface in the same package, etc, to no success. I feel like I am missing something obvious with the test setup. But could be something with my overall application config? my main aim with the solution is to avoid having something a long the lines of
if (store1Sdk != null) {
store1Sdk.download(fileObjectKey);
}
if (store2Sdk != null) {
store2Sdk.download(fileObjectKey);
}
Try #ActiveProfiles({"env1", "test"}).
Activate multiple profiles using #ActiveProfiles and specify profiles as an array.
this probrom because Store1Repository use #Profile("env1"), when you use #test,this class not invoke. try delete #Profile("env1") of Store1Repository.
if you use #test, both of store1Sdk/store2Sdk don't instanse, try add default instanse.eg:
#Bean
public store2Sdk buildClientStoreDefault() {
return new store2sdk();
}

JUnit5: access extension field from test class

I need to use extensions to run code before and after all test cases in classes that use it. My test classes need to access a field in my Extension class. Is this possible?
Given:
#ExtendWith(MyExtension.class)
public class MyTestClass {
#Test
public void test() {
// get myField from extension and use it in the test
}
}
and
public class MyExtension implements
BeforeAllCallback, AfterAllCallback, BeforeEachCallback, AfterEachCallback {
private int myField;
public MyExtension() {
myField = someLogic();
}
...
}
How do I access myField from my test class?
You can achieve this via a marker annotation and a BeforeEachCallback extension.
Create a special marker annotation, e.g.
#Documented
#Target(ElementType.FIELD)
#Retention(RetentionPolicy.RUNTIME)
public #interface MyField {
}
Use the annotation to find and set the values from within the extension:
import org.junit.jupiter.api.extension.BeforeEachCallback;
public class MyExtension implements BeforeEachCallback {
#Override
public void beforeEach(final ExtensionContext context) throws Exception {
// Get the list of test instances (instances of test classes)
final List<Object> testInstances =
context.getRequiredTestInstances().getAllInstances();
// Find all fields annotated with #MyField
// in all testInstances objects.
// You may use a utility library of your choice for this task.
// See for example, https://github.com/ronmamo/reflections
// I've omitted this boilerplate code here.
// Assign the annotated field's value via reflection.
// I've omitted this boilerplate code here.
}
}
Then, in your tests, you annotate the target field and extend the test with your extension:
#ExtendWith(MyExtension.class)
public class MyTestClass {
#MyField
int myField;
#Test
public void test() {
// use myField which has been assigned by the extension before test execution
}
}
Note: you can alternatively extend BeforeAllCallback which is executed once before all test methods of the class, depending on your actual requirements.

Why is my Spring Boot #Autowired MyBatis static mapper null?

I have the following class, but Spring and MyBatis-Spring-Boot-Starter will not autowire my mapper.
When I run the request, I get the output from the println()
sourceMapper = null
Model
public class Source {
#Autowired
public static SourceMapper sourceMapper; #### Why isn't this set?
public static Source findOrCreate(String url) {
...
System.out.println("sourceMapper = " + sourceMapper);
source = sourceMapper.findByHost(host);
...
}
}
I followed the examples as closely as possible.
http://www.mybatis.org/spring-boot-starter/mybatis-spring-boot-autoconfigure/
The other #Autowired Mappers in the main #Controller class that handles requests work, even though they are private.
This is the Mapper class
package ...mapper;
#Mapper
public interface SourceMapper {
...
I ran into this issue again with a new model and mapper. I tried to follow Why is my Spring #Autowired field null? and the code sample, but it was still null! I tried #Configurable, #Service, #Component.
Model
#Configurable
public class Domain {
#Autowired
private static DomainMapper domainMapper;
public static void incrementCounter(String host) {
...
Domain d = getDomainMapper().find(host, thisMonth);
public static DomainMapper getDomainMapper() {
return domainMapper;
public static void setDomainMapper(DomainMapper domainMapper) {
Domain.domainMapper = domainMapper;
Mapper
#Mapper
public interface DomainMapper {
MyBatis 3.4.5, MyBatis Spring 1.3.1, MyBatis Spring Boot Autoconfigure 1.3.1, MyBatis Spring Boot Starter 1.3.1
I fixed it with
private static DomainMapper getDomainMapper() {
// https://stackoverflow.com/a/52997701/148844
if (domainMapper == null)
domainMapper = MyApplication.getApplicationContext().getBean(DomainMapper.class);
return domainMapper;
And
MyApplication
#Autowired // for AWS
private static ApplicationContext context;
// I believe this only runs during an embedded Tomcat with `mvn spring-boot:run`.
// I don't believe it runs when deploying to Tomcat on AWS.
public static void main(String[] args) {
context = SpringApplication.run(MyApplication.class, args);
But I don't like it!
Spring will only try to inject a bean for you if another bean requires it.
Your class Source is just a normal class with bunch of static methods.
Hence it's not under creation control of Spring.
If you want to inject SourceMapper to your Source, you should mark Source with #Component or #Service so that the container will know it should create a bean of Source type for you and give you an instance of SourceMapper.
Moreover, the SourceMapper should be declared non-static, to prevent if a class access the variable before injected. And static field can be injected only if it use field setter injection.

Spring inherited #Component with constructor arguments

I have a service which needs to create Agents on the runtime. Agents inherit from a base Agent class. I would like to use the Autowired ability of spring instead of doing my own dependency injections.
But I am running into this issue, even though I am marking the component as scope=prototype, and even #Lazy to prevent anything from happening at compile-time.
***************************
APPLICATION FAILED TO START
***************************
Description:
Parameter 0 of constructor in com.my.project.AgentType1 required a bean of type 'com.my.project.POJO' that could not be found.
This is the service that tries to create the agents:
#Service
public class ProjectMain {
#Autowired
ApplicationContext context;
List<IAgent> agents = new ArrayList<>();
void SetupAgents(List<POJO> agentPojos) {
for(POJO agentPojo: agentPojos) {
IAgent agent = AgentFactory.CreateAgent(agentPojo, context);
agents.add(agent);
}
}
}
This is the factory class, not marked as #Component etc. It uses the context passed to it to create the child class beans. It tries to pass the constructor argument via the getBean method.
public class AgentFactory {
public static IAgent CreateAgent(POJO agentPojo, ApplicationContext context) {
if (agentPojo.type.equals("AgentType1")) {
return context.getBean(AgentType1.class, agentPojo);
} else {
return context.getBean(AgentType2.class, agentPojo);
}
}
}
This is a custom annotation which I found is needed for inheritance scenarios.
#Target({ ElementType.TYPE })
#Retention(RetentionPolicy.RUNTIME)
#Component
#Inherited
#Lazy
#Scope("prototype")
public #interface AgentAnnotation {}
These are the base and child agent classes, which need a custom data structure called POJO to work.
#AgentAnnotation
public class BaseAgent implements IAgent {
#Autowired
Environment env;
public BaseAgent(POJO agentPojo, String someotherdata) {
}
}
public class AgentType1 extends BaseAgent {
public AgentType1(POJO agentPojo) {
super(agentPojo, "mydata1");
...
}
}
public class AgentType2 extends BaseAgent {
public AgentType2(POJO agentPojo) {
super(agentPojo, "mydata2");
...
}
}
This is the starter app.
#ComponentScan(basePackages = "com.my.project", includeFilters = #ComponentScan.Filter(AgentAnnotation.class))
#EnableScheduling
#SpringBootApplication
public class MyApplication {
public static void main(String[] args) {
SpringApplication.run(MyApplication.class, args);
}
}
I also tried the configuration approach:
#Configuration
public class BaseAgentConfig {
#Bean
#Scope("prototype")
public AgentType1 agentType1(POJO agentPojo) {
return new AgentType1(agentPojo);
}
#Bean
#Scope("prototype")
public AgentType2 agentType2(POJO agentPojo) {
return new AgentType2(agentPojo);
}
}
In this case, I removed the #AgentAnnotation from the baseAgent class as we are now instantiating through this config. Also removed the ComponentScan line from the main App.
This time around, the #Autowired doesn't work. All Autowired references in the baseAgent class are null.
Please advise on the best approach to solve this error. Thanks.
Found the issue and solution.
Basically, I was expecting child classes to inherit #Component and #Scope, which it doesn't.
So essentially, I need to annotate each child class with #Component and #Scope("prototype").
The other problem was that I was expecting Autowired items in the constructor, which was too early. Adding a #PostConstruct addressed that issue.
So I ended up deleting the custom annotation and the configuration class and making the changes I just described.

How to properly overload Spring bean configuration

I'm having two spring(4.2) java configurations, one in a base module and one in a client specific module:
#Configuration
public class BaseConfig {
#Bean
public A getA() {
return new A("aaa");
}
}
#Configuration
public class ClientConfig {
#Bean
public A getA() {
return new A("bbbb");
}
}
During the app load there is always BaseConfig.getA() called, how can I ovverride the base bean factory configuration to have some client specific stuff?
Personally I would NEVER override a bean in spring! I have seen people spend too much time debugging issues related to this. For the same reason I would never use #Primary.
In this case I would have 3 contexts
Context that contains beans unique to the parent context
Context that contains beans unique to the child context
Abstract context that contains all shared beans.
This way you will specify the 2 contexts to load. This could be done programatically, or using profiles. Potentially you will need more contexts, because you probably want some of your beans to be different in tests.
I think that you should take a look at the #Profile annotation. You could simply split configuration into different base and client specific one like:
#Configuration
#Profile("base")
public class BaseConfig {
#Bean
public A getA() {
return new A("aaa");
}
}
#Configuration
#Profile("client")
public class ClientConfig {
#Bean
public A getA() {
return new A("bbbb");
}
}
now run the specific profile by adding
#ActiveProfiles("base") on application main method
spring.profiles.active=base entry in application.properties
or even pass profile name into jvm params
I'm not sure on how to extend bean config classes. One solution is to mark the bean in ClientConfig with #Primary annotation. This will cause the ClientConfig definition of bean A to be used.
If you include both configurations you can check Primary annotation: Primary
Indicates that a bean should be given preference when multiple candidates are qualified to autowire a single-valued dependency. If exactly one 'primary' bean exists among the candidates, it will be the autowired value.
#Profile Annotation can be used for this...
#Configuration
#Profile("base")
public class BaseConfig {
#Bean
public A getA() {
return new A("aaa");
}
}
#Configuration
#Profile("client")
public class ClientConfig {
#Bean
public A getA() {
return new A("bbbb");
}
}
Use the following link
https://spring.io/blog/2011/02/14/spring-3-1-m1-introducing-profile/
This is an answer to a comment above, but since comments have limited formating and size, I will reply with an answer instead.
how does spring define the ordering and overriding of beans when loading configuration files
It depends what you mean by loading multiple configuration. If you have one spring context and have two classes with #Configuration and do a component scan, then Spring will build the dependency tree, and which ever context (bean) is loaded last will define the bean (as it overrides the fist definition).
If you have have multiple Spring contexts in a parent child relation, then the child and see parent beans, and will also 'override' parent beans if you use child.getBean(type.class). The parent can't see bean from children.
Using #Primary. If you have a Spring context (can come from multiple configurations) that defines two beans of the same type, you will not be able to use context.getBean(type.class) or #AutoWired (without #Qualifier) because you have multiple beans of the same type. This behaviour can be altered if one of the beans is #Primary. I try to avoid the use of #Primary in my own code, but I can see it is used heavily used in Spring boots auto configure system, so I think it has some subtle usage when it comes to framework design.
Here is a small example, note that if you load configuration classes directly, they don't need to have the #Configuration annotation.
public class ParentChildContext {
public static void main(String[] args) {
parentChildContext();
twoConfigurationsSameContext();
}
private static void twoConfigurationsSameContext() {
ApplicationContext ctx = new AnnotationConfigApplicationContext(Parent.class, Child.class);
// if you have two beans of the same type in a context they can be loaded by name
Object childA = ctx.getBean("childA");
System.out.println("childA = " + childA);
Object parentA = ctx.getBean("parentA");
System.out.println("parentA = " + parentA);
// since both configurations define A, you can't do this
ctx.getBean(A.class);
}
private static void parentChildContext() {
ApplicationContext parentCtx = new AnnotationConfigApplicationContext(Parent.class);
A parentA = parentCtx.getBean(A.class);
System.out.println("parent = " + parentA);
AnnotationConfigApplicationContext childCtx = new AnnotationConfigApplicationContext();
childCtx.register(Child.class);
childCtx.setParent(parentCtx);
childCtx.refresh();
A childA = childCtx.getBean(A.class);
System.out.println("child = " + childA);
}
public static class Parent {
#Bean
//#Primary // if you enable #Primary parent bean will override child unless the context is hierarchical
public A parentA() {
return new A("parent");
}
}
public static class Child {
#Bean
public A childA() {
return new A("child");
}
}
public static class A {
private final String s;
public A(String s) {
this.s = s;
}
#Override
public String toString() {
return "A{s='" + s + "'}";
}
}
}

Categories