Set scope for all Beans in a Spring Configuration - spring

I have a Spring application doing some batch job for me. It is started up, is properly configured while start time of the ApplicationContext, does it's job and is ended after finished. Thus all Beans are defined in Singleton scope.
Now I want to embed this job also into a Web Application. A lot of the information (configuration) I need to start the Job's ApplicationContext is only available when the Request arrives to start the job. So I want to start my Jobs Beans in request scope without having to rewrite all Bean definitions in the origin code.
A simplified example:
// from Job Application
#Configuration
class MyJobConfig {
#Bean
SomeJobService someJobService() {
return new SomeJobService();
}
#Bean
SomeOtherStuff someOtherStuff() {
return new SomeOtherStuff();
}
}
// from Web Application
class MyJobController {
Autowired
private SomeJobService someJobService;
#RequestMapping(value = "/startJob", method = { RequestMethod.POST })
public String startJob(#ModelAttribute JobConfig jobConfig) {
// define some of the config required to start the JobBeans
System.setProperty("some.foo", jobConfig.getFoo());
someJobService.runJob();
}
}
What magic can I do, so the Controller will instantiate someJobBean and all its dependencies from MyJobConfig for each request?

Related

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.

Scheduled method in Spring Boot

I want to send a post request within a period. I created my method like this ;
#Scheduled(cron = "0 0 */6 * *")
#PostMapping
public List<TagsRes> getTags(Date date) {
return null;
}
#Scheduled(cron = "0 0 5 * * ?")
#PostMapping
public List<TagsRes> getAll() {
return null;
}
Should i use #Scheduled in my controller ? Is there any better way to do it?
Thanks!
Controllers are meant to receive web requests, not to post anything.
You can think about them as endpoints exposed by your application and called by external service from time to time.
Now, the Controller abstraction by itself should do any business logic. You may want to validate some parameters received in the request, maybe convert the request parameters to java object with some customization and then call the class (usually mentioned as Service in spring universe) that actually executes your business logic.
Now back to your question.
I suspect you should not "POST a request" but should invoke some piece of code "as if someone called the controller's method (endpoint)". But this time not the external "user" will cause the code execution but an internal scheduler.
If so you can slightly refactor your code to achieve the better clarity:
Create a service that will execute the code
Do not put any scheduling related stuff on controller
From controller call the service
Create a bean and put a "#Scheduled" method on it. The bean will have the service injected and will call it just like the controller does.
Don't forget to put #EnableScheduling annotation - otherwise the scheduled code won't run.
public class MyService {
public void doBusinessLogic(){ ... }
}
#RestController
public class MyController {
#Autowired
private MyService service;
public void myPostMethod(...) {
service.doBusinessLogic(...);
}
}
public class MyScheduledInvoker {
#Autowired
private MyService service;
#Scheduled(...cron expression or whatever...)
public void invokeScheduled() {
service.doBusinessLogic(...);
}
}
#SpringBootApplication
#EnableScheduling
public class MyApp {
public static void main(String [] args) { .... run the app ...}
}
To schedule a job in spring boot application to run periodically, spring boot provides #EnableScheduling and #Scheduled annotations. In my opinion, since spring boot provides the annotation and functionality for scheduler using it will make more sense
Add #EnableScheduling annotation to your spring boot application class.#EnableScheduling is a Spring Context module annotation. It internally imports the SchedulingConfiguration via the #Import(SchedulingConfiguration.class) instruction
#SpringBootApplication
#EnableScheduling
public class SpringBootWebApplication {
}
Now you can add #Scheduled annotations on methods that you want to schedule. The only condition is that methods should be without arguments.
ScheduledAnnotationBeanPostProcessor that will be created by the
imported SchedulingConfiguration scans all declared beans for the
presence of the #Scheduled annotations.
For every annotated method without arguments, the appropriate executor thread pool will be created. This thread pool will manage the scheduled invocation of the annotated method.
#Scheduled(initialDelay = 1000, fixedRate = 10000)
public void run() {
logger.info("Current time is :: " + Calendar.getInstance().getTime());
}
Source: https://howtodoinjava.com/spring-boot/enable-scheduling-scheduled-job-example/

Spring Batch not finding global singleton scoped beans in web application - what is wrong?

I have a spring web application which registers multiple spring batch jobs for lengthy background processing at launch time. As near as I can tell, the spring-batch contexts are not aware of the singletons declared in the root AnnotationConfigWebApplicationContext bean, so multiple copies of complex-to-initialize beans are being instantiated for every job both at initial application load time and again at execution time.
I have determined that in AbstractBeanFactory.doGetBean() the bean is being correctly identified as a singleton, but for some reason the bean factory for the job is unaware of the bean factory for the parent context.
I have refactored some of the beans as Application scoped, but the application scope bean is (apparently) not legal in the spring batch context.
Either I am grossly misunderstanding something about spring scopes, or there is something out of kilter with my initialization of the spring-batch elements (code below). I am leaning towards both, as the one would lead to the other.
As I understand Spring scopes, I should see something like this, with each child scope being able to see singletons defined in the parent scope:
AnnotationConfigWebApplicationContext (web application context)
|
v
ResourceXmlApplicationContext (1 per registered job)
|
v
ResourceXmlApplicationContext (1 per registered step)
Initialization code in question:
#Component("mySingletonScopedBean")
#Scope(value = "singleton", proxyMode = ScopedProxyMode.DEFAULT)
#Order(1)
public class MySingletonScopedBean {
// getters, setters, etcetera
}
// EDIT: Added in response to comment below
#Autowired
public ApplicationContext applicationContext;
#Bean
public ClasspathXmlApplicationContextsFactoryBean classpathXmlApplicationContextsFactoryBean () throws IOException
{
String resourcePath = somePath "*.xml";
logger.trace("classpathXmlApplicationContextsFactoryBean() :: {} ", resourcePath);
Resource[] resources = applicationContext.getResources(resourcePath);
ClasspathXmlApplicationContextsFactoryBean bean = new ClasspathXmlApplicationContextsFactoryBean ();
bean.setApplicationContext(applicationContext);
bean.setResources(resources);
return bean;
}
#Bean
public AutomaticJobRegistrar automaticJobRegistrar() throws IOException, Exception {
ClasspathXmlApplicationContextsFactoryBean c = classpathXmlApplicationContextsFactoryBean ();
AutomaticJobRegistrar automaticJobRegistrar = new AutomaticJobRegistrar();
automaticJobRegistrar.setApplicationContext(applicationContext);
automaticJobRegistrar.setApplicationContextFactories(c.getObject());
automaticJobRegistrar.setJobLoader(jobLoader());
return automaticJobRegistrar;
}
#Bean
public JobLoader jobLoader() {
DefaultJobLoader jobLoader = new DefaultJobLoader(jobRegistry(), stepRegistry());
return jobLoader;
}
#Bean
public StepRegistry stepRegistry() {
MapStepRegistry stepRegistry = new MapStepRegistry();
return stepRegistry;
}
#Bean
public JobRegistry jobRegistry() {
JobRegistry jobRegistry = new MapJobRegistry();
return jobRegistry;
}
Edit: Closing
The whole spring environment initialization was messed up.
I'm saving my rants about the "clean" injection model and what a pain it is to figure things out when they break for a massive blog post after this project.

How to pass a POJO to the SpringBoot application run method?

I have this
#Bean public CommandLineRunner commandLineRunner(ApplicationContext ctx) {
// use a lambda expression to define a CommandLineRunner
return args -> {
... work ...
};
}
which is invoked like this
SpringApplication app = new SpringApplication(MyApp.class);
app.run(args); // perform configuration magic and invoke the above lambda function
This works great as long as the application was only used from the CLI. Now, the application is going through some refactoring to support a new run-time platform, and now I would like to do this:
app.run(complexOject); // pseudo-code, no such method in SpringApplication
i.e. I need to pass an ComplexObject to the application, while still preserving all the magic auto-configuration.
How can this be accomplished? Solutions with the least amount of code change are preferred.
The refactoring steps to enable a CommandLineRunner to receive arbitrary parameters is roughly as follows:
Move the guts (the ... work ... part) of the commandLineRunner method to a new method in a new bean class e.g. #Component public class GenericRunner { public void run(String ... args) ... }.
This is the most important step: Delete the original CommandLineRunner #Bean definition in its entirety. This will cause the application's run() method to exit after performing configuration.
Replace the app.run(args); invocation with the following
ConfigurableApplicationContext ctx = app.run(); // oh yeah
GenericRunner runner = ctx.getBean(GenericRunner.class);
runner.run(args);
Re-run all tests, commit the code changes.
The actual refactoring is now trivial: modify the runner.run(args) call at will. This is just a straight call into the GenericRunner POJO and is free of SpringBoot rules and limitations.
SpringApplication class obviously doesn't have a method that gets this output stream, its an entry point to the complicated by very powerful spring boot application loading process.
If the goal is to store the log of the application consider logging configuration rather than using output streams.
Otherwise please describe the requirement, what is the purpose of this output stream and I'll do my best to update this answer.
Update:
SpringApplication starts up an application context that is used as a registry for spring beans in the application.
So the most "spring friendly solution is to define a ComplexObject to be a spring bean, so that it will be injected into other beans that might need it.
This will work great if this bean can be created during the application startup.
Example:
class ComplexObject {...}
class ServiceThatMaintainsAReferenceOnObject {
private ComplexObject complexObject;
// all arg constructor
}
#Configuration
class SpringConfiguration {
#Bean
public ComplexObject complexObject() {
return new ComplexObject();
}
#Bean
public ServiceThatMaintainsAReferenceOnObject service(ComplexObject complexObject) {
return new ServiceThatMaintainsAReferenceOnObject(complexObject);
}
}
Now, if this complex object has to be created outside the spring application, maybe you need to pass it to some bean method as a parameter, after the application context is created. This can also be a case in the question, although it's definitely not a Spring way to do things.
Here is an example:
class ComplexObject {}
class Service {
void foo(ComplexObject complexObject);
}
#Configuration
class MyConfiguration {
#Bean
public Service service() {
return new Service();
}
}
// inside the main class of the application:
SpringApplication app = ...
ComplexObject complexObject = ... // get the object from somewhere
ApplicationContext ctx = app.run(args);
// by this time, the context is started and ready
Service service = ctx.getBean(Service.class);
service.foo(complexObject); // call the method on bean managed by spring
All in all, usually the second approach is not a regular use case of spring application, although its kind of feels like you're looking for something like this in the question.
All-in-all I think you should learn and understand how Spring works in a nutshell, and what exactly the ApplicationContext is to provide the best solution (I'm sorry for mentioning this, I said so because from the question it looks like you haven't really worked with Spring and don't really understand what does it do and how does it manage the application).

Spring using #Scheduled annotation on a prototype bean along with object pooling

I currently have a spring bean which has a method annotated with #Scheduled to support automatic refresh of its state from external source. I have also annotated the bean with scope as prototype. Now, I am trying to have a pool of these beans and use them in one of my service. When the application starts I get the error below
Need to invoke method 'refreshModelManager' declared on target class 'ModelManagerService', but not found in any interface(s) of the exposed proxy type. Either pull the method up to an interface or switch to CGLIB proxies by enforcing proxy-target-class mode in your configuration.
I have Autowired the ModelManagerServicePool in my other service and using the getTarget() and releaseTarget API to get access to the model manager object.
I read in Spring documentation that #Scheduled is supported in non-singleton beans as of Spring 4.3.x, so I suspect there is something wrong with the way I am using the object pool or the way the instances of ModelManagerService are getting created in the object pool.
Any suggestions would be very helpful.
I tried setting the property 'spring.aop.proxy-target-class=true' but it didn't help.
Source Code:
#Configuration
public class DataApplicationConfiguration {
#Bean(initMethod="initializeMinIdleObjects")
public ModelManagerServicePool modelManagerServicePool() {
ModelManagerServicePool modelManagerServicePool =
new ModelManagerServicePool();
modelManagerServicePool.setMinIdle(1);
modelManagerServicePool.setMaxSize(1);
modelManagerServicePool.setMaxIdle(1);
modelManagerServicePool.setTargetBeanName("modelManagerService");
modelManagerServicePool.setTargetClass(ModelManagerService.class);
return modelManagerServicePool;
}
}
public class ModelManagerServicePool extends CommonsPool2TargetSource {
public void initializeMinIdleObjects() throws Exception {
List<ModelManagerService> services = new ArrayList<ModelManagerService>();
for(int i = 0; i < getMinIdle(); i++) {
services.add((ModelManagerService) this.getTarget());
}
for(ModelManagerService service : services) {
this.releaseTarget(service);
}
services.clear();
}
}
#Service("modelManagerService")
#Scope("prototype")
public class ModelManagerService {
private AtomicReference<ModelManager> ar =
new AtomicReference<ModelManager>();
#Scheduled(initialDelay = 5000, fixedDelayString = "${modelmanagerservice.refresh.internal}")
public void refreshModelManager() {
//Refresh state of the model manager
}

Resources