Spring dynamic autowire services - spring

I've started to build some kind of a CMS and I'm stuck over one idea.
The description is:
I have standard MVC Controller (Home) in which I'm downoading modules settings which will be set in this Controller.
The response is, that I have to implement module with name "HPModule".
So I'm trying to load this module by Class.forName("com.app.something.HPModule"); and then call method init();
My HPModule is:
public class HPModule
{
#Resource(name = "hpModuleService")
private HPModuleService hpModuleService;
public String init()
{
SomeObject someObject = hpModuleService.getArticle();
}
}
And I found that when I'm trying to do SomeObject someObject = hpModuleService.getArticle(); Spring is blind for #Resource when I'm calling class by Class.forName.
How to solve this issue?

The HPModule has to be a Spring Bean retrieved by means of DI or directly from Spring BeanFactory. You cannot expect Spring to autowire a class that is not instantiated by Spring, unless You use #Configurable and AspectJ to weave the class.
If HPModule already is a Spring Bean, than just #Autowire or #Inject it directly into the MVC controller that needs it.
If You don't know in compile time what modules You'll need, than inject ListableBeanFactory and use BeanFactoryUtils to get the modules You need in runtime by type or by name.

Related

Inject/Access Spring Bean into Log4j2 Plugin

I have a configuration properties class that I want to inject into a custom log4j2 RewritePolicy.
e.g.
#Plugin(name = "MyPolicy", category = "Core", elementType = "rewritePolicy", printObject = true)
public class MyPolicy implements RewritePolicy {
private MyPolicyProperties myPolicyProperties; // <-- want to inject/autowire this
public MyPolicy() {}
#PluginFactory
public static MyPolicy createPolicy() {
return new MyPolicy();
}
#Override
public LogEvent rewrite(LogEvent logEvent) {
// do something with myPolicyProperties here
return Log4jLogEvent.newBuilder()
.setLoggerName(logEvent.getLoggerName())
.setMarker(logEvent.getMarker())
.setLoggerFqcn(logEvent.getLoggerFqcn())
// ... etc
.build();
}
}
#ConfigurationProperties("app.mypolicy")
#Getter
#Setter
public class MyPolicyProperties {
private String property1;
private int property2;
// ... etc
}
I've tried implementing an ApplicationListener to reconfigure log4j as described here but was can't seem to get the appender and/or rewritepolicy to configure. Also tried implementing ApplicationContextAware described here but also didn't work.
Is there anyway to access the MyPolicyProperties in MyPolicy?
It can be done but it is almost never pretty. This is because Log4j Plugins are loaded by Log4j's plugin system while Spring Beans are loaded by Spring. Furthermore, they are not instantiated at the same time.
If you are using Spring Boot the very first thing that will happen is for Log4j2 to initialize because SpringApplication requests a Logger. So there would be no way to resolve the Spring Bean at that point as it doesn't exist. Later, Spring's bootstrap process will initialize Log4j again and then during application setup it will initialize once or twice more. During these subsequent initializations the bean may be available.
Depending on the type of application you are using you may be able to locate Spring's ApplicationContext so that you can call getBean() and inject it.
There is no automatic way to do this via an annotation or something similar.
The simplest way to do it is to either add a static method in the target class that gets initialized to reference itself when Spring is initialized or to create another class with a method that initializes a static method to reference the Spring created bean. So Spring will cause these static methods to reference the bean it creates. Then have your Log4j plugin call that static method to get the bean reference. Once it is non-null you can save it in the plugin and after that it should function as you want.

Adding legacy singleton to Spring ApplicationContext for Injection

I am trying to create a lightweight web service around a legacy java library to expose it as a web service using Spring Boot. I am new to Spring, while I have a lot of java experiance writing libraries all my web service experiance is in ASP.NET.
I can instantiate an instance of my library object but I can't figure out how to then have that object be injected into my controllers via #Autowired when the application is spun up.
This is my main application:
#SpringBootApplication
public class ResolverWebServiceApplication {
private static ArgumentParser newArgumentParser() {
ArgumentParser parser = ArgumentParsers.newFor("Resolver").build();
// configuring the parser
return parser;
}
public static void main(String[] args) throws ArgumentParserException {
ArgumentParser parser = newArgumentParser();
Namespace ns = parser.parseArgs(args);
ResolverOptions options = new ResolverOptions.Builder(ns)
.build();
ResolverContext context = new ResolverContext(options);
// ^^^ I need to get this injected into my controllers ^^^
SpringApplication.run(ResolverWebServiceApplication.class, args);
}
}
And then a simple controller which needs the class injected:
#RestController
public class VersionController {
#Autowired
private ResolverContext context; // And here the instance needs to be injected.
#GetMapping(path = "/version", produces = MediaType.APPLICATION_JSON_VALUE)
public long version() {
return context.getResolver().getVersionAsLong();
}
}
I could make the context a singleton which the controllers just refer to but I want to be able to test my controllers by mocking the context. There is also obviously a lot of validation and error handeling that needs to be added.
I can't have it be a Bean since I only want to instantiate one for my entire application.
The closest question I have found is this one: Registering an instance as 'singleton' bean at application startup. But I can't put the options in the configuration files. The application might be spun up in a container or on a users machine and requires the ability to accept arguments to initialize the library class. It would be a real usability degradation if someone had to manually edit the application config for these options.
You need to tell spring to consider the required classes from your lib when initializing the application context i.e Configure and let spring know how to create a bean and then let spring handle dependency injection for you.
First of all, add required jar that you have in your build file, say pom.xml for maven, in your current project. Idea is to have it on your classpath when you build the project.
As you said it is legacy lib and I am assuming it is not a spring bean, then
In your configuration class, return it as a bean, using #Bean annotaion.
#Configuration
public class YourConfigurationClass {
#Bean
SomeBean returnSomeBeanFromLegacyLib() {
return new SomeClassFromLib();
}
Once you return this bean from your config, it should be available to Spring Context for dependency injection whereever you #Autowire the required dependency.

Not able to inject values in a field

#Component
#PropertySources({ #PropertySource("classpath:mail.properties") })
public class A implements B {
#Value("${mail.team.address}")
private String teamAddress;
// has getter and setters .not shown for brevity.
Now when i call the class i get the value of teamAddress as NULL .But in the property file mail.team.address has some value.
My property file is present under src/main/resource folder
Making a call
A a = new A ();
a.someMethodinClassA();
You can not create instance of class by yourself when you want Spring to resolve #Value annotation.
See documentation:
Note that actual processing of the #Value annotation is performed by a BeanPostProcessor which in turn means that you cannot use #Value within BeanPostProcessor or BeanFactoryPostProcessor types. Please consult the javadoc for the AutowiredAnnotationBeanPostProcessor class (which, by default, checks for the presence of this annotation).
Simple solution for you: just annotate class with any #Component annotation and let Spring to create an instance of your class.
You can't create (with a "new" keywoard) for spring bean. If you do it like this, spring doesn't participate in the object creation and configuration, which means that there is no autowiring, the bean is not in Application Context, etc. And of course, #Value annotation won't be processed among other things
The better way is to inject the class A to the code that you used in your example:
A a = new A ();
a.someMethodinClassA();
Show become:
#Component
public class SomeClass {
private final A a;
public SomeClass(A a) {
this.a = a;
}
public void foo() {
a.someMethodinClassA();
}
}
You should read some basics around spring dependency injection. The class that you have autowired with #Component is scanned via component scanning and its object is created by spring container for you.
that is the reason you should not create the object yourself using new keyword.
wherever in your new class you want to use your class A object you can autowire it as below:
#Component
public class TestC{
private A a; // this object will be injected by spring for you
}

Best practice: Spring Autowired, concrete class, and Builder

We have an interface:
public interface NotifyService {
public void send();
And a class that implements it
public class EmailNotifyService implements NotifyService {
private EmailBuilder _builder;
#Autowired
PersonRepository _personRepository;
... other Autowired Repositories ...
public EmailNotifyService(EmailBuilder builder) {
this._builder = builder;
}
public void send() {
// send mail using _builder.getRecipient().getEmailAddress(), etc.
}
We used to instantiate EmailNotifyService with a builder:
public class EmailBuilder {
private Person _recipient;
private EmailType _type;
private Event _event;
public EmailNotifyService build() {
return new EmailNotifyService(this);
}
public EmailBuilder recipient(Person recipient) {
this._recipient = recipient;
return this;
}
... and so on. But now, instead of using build() to create a new EmailNotifyService, we are trying to use Autowire with Spring instead. The problem is that everywhere else in our app, we are Autowiring interfaces, not classes. And from what I've read it's a good idea in general. In fact, I've tried rewriting the NotifyService to be an Abstract class, and then have EmailNotifyService just extend it. But Spring isn't Autowiring it correctly, it doesn't create a Proxy like it does for interfaces, and all of my Autowired fields are null.
So it would seem we're stuck with Autowiring the NotifyService interface. Fine. What I can't get my head around is - how can I get the data I used to assign with the builder -- the Person, EmailType and Event -- into a Spring Autowired interface?
I suppose I could change the interface definition to have a setPerson(), setEmailType(), etc., but apart from being really ugly, it defeats the purpose of using an interface in the first place. A different NotifyService (WebServiceNotifyService or RestNotifyService for example) night not have need for that info.
Is there any elegant, best-practice way to do this?
Thanks.
EDIT
I am using annotations, very little xml. And I am also using transaction management, which might explain why the abstract class isn't properly autowired? This is the only pertitnent info I have in xml:
<context:annotation-config />
<context:component-scan base-package="com.myco.myapp" />
<tx:annotation-driven transaction-manager="transactionManager"/>
What I mean when I say "autowiring isn't working correctly" is that when I try to autowire the abstract class, Spring doesn't seem to be creating a Proxy like it does for interfaces, and all the Autowired fields in my EmailNotifyService (PersonRepository, others ...) are null. When I use an interface, all the Autowired fields are wired correctly.
But my main problem is that I used to work explicitly with a concrete class, using a builder to create a new EmailNotifyService() directly, and pass it info -- Person, EmailType and Event. These are just normal beans. There are no setters/getters for them in EmailNotifyService but there are the EmailBuilder, which used to live inside EmailNotifyService.
But now I am using the NotifyService interface, which knows nothing about Person, EmailType or Event. But I need this info in order for EmailNotifyService to work.
So my question is, if I use Spring to Autowire my EmailNotifyService like this:
#Autowired
#Qualifier("email") // so Spring knows I want to use the EmailNotifyService implementation
NotifyService _notifyService
How can I set the Person, EmailType and Event data, since NotifyService knows nothing about them?
Currently we are using the mailer service within a web app but theoretically the mailer service should be able to work stand-alone. Regardless, I don't see how request scoped beans can help me here.
Robert what do you mean by not autowiring correctly? Are you getting any error?
Generally both interface and class auto-wiring works in Spring unless you have some autoproxy configured example #Transactional.
You do not need to have setPerson(), setEmailType(), etc. in your interface but have them autowired in the concrete class which requires them.
But seems Person is not a service but a bean which holds data and its specific to a request. If yours is a web application then look at request scope proxy to inject Person like bean.
So you are using transactions which is why class based injection is failing. Add proxy-target-class="true" to tx:annotation-driven.
Regarding your injection of Person and EmailType then you have to do that to the bean EmailNotifyService. In EmailNotifyService I do not see any Person or EmailType variables defined. Also read what I said about Person bean above.
Your design is not correct. You should not make EmailBuilder a bean and look to autowire to the EmailNotifyService. Instead in EmailNotifyService you should have a method send(EmailBuilder builder) where you pass the builder which you created somewhere dynamically.

Spring 3.0.5 prototype Bean Instance variables become null after method call

Having the strangest Spring problem where after a Spring bean is created using Dependency Injection, and then running some method on the bean, the instance variables that are set during the bean method call all return to their default Java values. Found this happening after we moved from Spring 2.5.5 to Spring 3.0.5.
So for clarity sake here is the example
The Prototype bean:
#Component("bean1")
#Scope("prototype")
public class Bean1{
String someString;//There are other instance variables but same happens to them
#Autowired
#Qualifier("otherBean")
private OtherBean otherBean;
public void doSomething(){
someString="1234ABC";
}
//setters and getters ....
}
And the code that grabs the bean from spring and uses it:
Bean1 bean1 = (Bean1) applicationContext.getBean("bean1");
bean1.doSomething();//updates some instance variables in bean1
String value = bean1.getSomeString(); //Instance variables is null
Object otherObject = bean1.getOtherBean(); //This Spring injected bean is correctly initialized
So if I debug into the code, the instance variables (someString) is set in bean while in the doSomething method call but right after I return, the value goes back to null.
Worst thing is that this all works as expected in 2.5.5 but not in the updated Spring 3.0.5
This is legacy code so I know that you are supposed to code to interfaces and so the Bean1 should be an interface and the class that implements the interface should do the actual work. I changed the code to this model as well and still does not work.
Try #Scope(value="prototype", proxyMode=ScopedProxyMode.TARGET_CLASS)
I was facing the same issue but couldn't got the exact solution. Then I just changed the scope to request.

Resources