Add runtime generated class bean to spring boot application context - java

I am working on a project where I generate a class after the spring boot context starts and compile it to the target/classes folder. I know Spring scans the package and registers all the beans in the application context when the application starts. But here, we are generating a class with annotation #Service at runtime, and the application context is unaware of this new bean. So, other beans cannot find this runtime-generated class. This bean is also not in the bean list of Actuator -> Beans in IntelliJ IDEA
When I rerun the application, this runtime-generated bean gets registered in the application context, and other beans can find out or utilize that bean. Actuator -> Beans in IntelliJ IDEA also shows this bean. I want to register this runtime-generated bean to the application context without restarting/rerunning the application. Please let me know what I am missing here.
I tried this, but it did not work for me.
#RestController
#Scope("prototype")
#Slf4j
public class myClass {
#Autowired
private ApplicationContext appContext;
#GetMapping("/beanRegistration")
public void beanRegistration() {
try {
Class<?> generatedClass = Class.forName("myApp.sam.protocompiler.GrpcServerImpl");
log.info("generatedClass : {} | Methods count: {} | Super class : {}", generatedClass.getCanonicalName(), generatedClass.getMethods().length, generatedClass.getSuperclass());
ConfigurableListableBeanFactory beanFactory = ((ConfigurableApplicationContext) appContext).getBeanFactory();
beanFactory.registerSingleton(generatedClass.getCanonicalName(), generatedClass);
String[] beans = appContext.getBeanDefinitionNames();
Arrays.sort(beans);
for (String bean : beans) {
if(bean.contains(generatedClass.getCanonicalName())) {
System.out.println("New Bean : " + bean);
}
}
} catch (Exception ex) {
log.error("Error : {}", ex.getMessage(), ex);
}
}
}
Also, Is it possible to Scan components after the application is started running? If we can scan runtime-generated components at runtime, we can solve this problem.

Since you are definitely in a Web application, you might try using GenericWebApplicationContext instead.
#Autowired private GenericWebApplicationContext context;
Then,
context.registerBean(generatedClass.getCannonicalName(), generatedClass);
I don't know if this does anything different than what you have, but I think it is the more spring-approved way to do it.

Related

How to inject a service Bean in a groovy script?

I have a spring boot application and a separate groovy script that runs within the app and I need to inject a CustomService into the groovy script.
How do i achieve that? i tried reading the documentation but it's not so clear
i tried adding the #Autowired annotatino but it gives me an error and I dont think that's how it should be done.
How to inject dependency into IOC container of spring boot project with Groovy
Let imagine here is you CustomService
public class CustomService {
private final String smtPerhaps;
...
}
Then inject the bean as
beans {
yourDesiredBeanNameInContainer(CustomService) {
smtPerhaps = 'stackOverFlow'
}
}
Its quit the same as the Java base dependency injection
#Configuration
public class JavaBeanConfig {
#Bean
public CustomService yourDesiredBeanNameInContainer() {
return new CustomService("stackOverFlow");
}
}
Find out more in here.

Ensure spring bean loaded from non spring context

I have spring application alongside (jersey 2.6 classes and ) servlets .
I need to get Spring bean(s) from jersey/non spring context,
Similar question suggested to get context in a static wrapper of context
public static ApplicationContext getContext() {
return context;
}
How can I be sure the context is already loaded or not null?
If I can't, how should I wait/check until it spring context is loaded?
In case of calling from jersey context or calling bean from a simple HttpServlet code
EDIT
Jersey is working fine using jersey-spring3 dependency jar, so my question is only about Servlets out of Spring control
EDIT 2
The application is loading spring different than #entpnerd suggested article
It register a Servlet implementing a WebApplicationInitializer
public class MyWebAppInitializer implements WebApplicationInitializer {
But also have DispatcherServlet configured in web.xml
How can the DispatcherServlet loaded only after Spring loaded?
Because we add Autowiring capabilities on its init method:
WebApplicationContextUtils.getRequiredWebApplicationContext(config.getServletContext())
.getAutowireCapableBeanFactory().autowireBean(this);
Is adding a timeout before serving requests is the most prefer solution or is there a tweak in class loading that can take care of it?
EDIT 3
I found answers and answers of injecting, but not why Spring is loaded before Servlet.
The idea is quite simple, although the actual implementation may vary depending on an exact way of Spring boot and Jersery initialization.
An idea:
Spring boot, as being a purely runtime framework, is all about proper loading the application context (from the question standpoint).
So, bottom line, when it's loaded there is an application context somewhere in memory, and its possible to access beans from this application context.
Now, since you say that Jersey is not spring/spring-boot driven, this application context has to be reachable from some kind of static global variable by Jersey, it's quite ugly but should work.
So the idea has two steps:
Put an application context reference to some static holder accessible from Jersey.
Read this value in some infrastructure level code from Jersey component.
A Possible Implementation
Technically step one can be done by implementing some kind of spring boot listener that will store application context in some kind of singleton:
enum ApplicationContextHolder {
INSTANCE;
private ApplicationContext ctx;
void setApplicationContext(ApplicationContext ctx) {
this.ctx = ctx;
}
ApplicationContext getCtx() {
return this.ctx;
}
}
// and a listener (spring boot provides many ways to register one, but the
// implementation should be something like this):
// The main point is that its managed by spring boot, and hence and access to
// the application context
class StartupListener implements ApplicationListener<ContextRefreshedEvent> {
#Override
public void onApplicationEvent(ContextRefreshedEvent event) {
ApplicationContextHolder
.INSTANCE
.setApplicationContext(event.getApplicationContext());
}
}
Now the step 2 is:
class MyJerseyOrWhateverComponentThatWantsToAccessApplicationContext {
public void foo() {
ApplicationContext ctx = ApplicationContextHolder.INSTANCE.getCtx();
...
ctx.getBean(...);
}
}
So a viable solution for this could happen in two stages:
A Spring bean gets the ApplicationContext instance and sends it to a static singleton outside of the Spring context.
Your standalone servlet gets the ApplicationContext instance from the static singleton and verifies that the right beans have been loaded.
Consider the following code as an example:
SpringMetaBean.java
// #Component so that it's part of the Spring context
// Implement ApplicationContextAware so that the ApplicationContext will be loaded
// correctly
#Component
public class SpringMetaBean implements ApplicationContextAware {
private ApplicationContext appCtx;
public setApplicationContext(ApplicationContext appCtx) {
this.appCtx = appCtx;
}
// #PostConstruct so that when loaded into the Spring context, this method will
// automatically execute and notify ApplicationContextHolder with a reference to
// the ApplicationContext
#PostConstruct
public void setup() {
ApplicationContextHolder.set(this.appCtx);
}
}
ApplicationContextHolder.java
public class ApplicationContextHolder {
// ensure the reference is thread-safe because Spring and standalone Servlet will
// probably be running on different threads.
private final AtomicReference<ApplicationContext> appCtxContainer = new AtomicReference<>();
public void set(ApplicationContext appCtx) {
this.appCtxContainer.set(appCtx);
}
public ApplicationContext get() {
return this.appCtxContainer.get();
}
}
MyStandaloneServlet.java
public class MyStandaloneServlet {
// my request handler method
public void getResponse(HttpServletRequest rq) {
ApplicationContext springAppCtx = ApplicationContextHolder.get();
// if not null, we know that Spring has been loaded and we can dig into the
// application context.
}
}

Spring autowire a class on server startup

I have a spring application. I am autowiring classes and they are working fine.
For e.g
#Controller
public class SearchController {
#Autowired
private EnvironmentControl envControl;
#Autowired
private SearchControl searchControl;
...
But now i have on server startup class called ScheduleServlet which uses init method to schedule something...
public class SchedulerServlet extends HttpServlet {
#Override
public void init(ServletConfig config) throws ServletException {
super.init(config);
this.LOGGER.info("timer servlet is initialized ");
try {
InitialContext ic = new InitialContext();
TimerManager tm = (TimerManager) ic.lookup("java:comp/env/tm/TimerManager");
Timer timer = tm.schedule(new GlobalTemplateScheduler(), 0, 3600000);// one hour interval
System.out.println("Timer..... " + timer);
}
...
In this my GlobalTemplateScheduler class has timerExpired method which is scheduled to execute after every one hour interval.
public class GlobalTemplateScheduler implements TimerListener {
#Autowired
private TemplateControl templateControl;
#Override
public void timerExpired(Timer timer) {
try {
templateControl.updateMappings(names);
} catch (Exception e) {
this.LOGGER.error(e.getMessage());
e.printStackTrace();
}
...
So i have to autowire templateControl which i am getting null. This should happen on server startup.
Further inside updateMappings there's a datasource object which is also autowired as constructor-arg(This is working fine on browser request but need to do it on server startup).
Note: I cannot use the ApplicationListener interface.
Any suggestions would really help.
Thankyou.
On application startup beans initialization would not be completed, beans can be used after the application context refresh or after the intialization of the bean, it will make no sense to execute a logic which requires the bean on the startup unless you detect whether the bean is ready or not.
You can execute some logic using #PostConstruct in the bean which will be executed after the initialization of the bean so you can manipulate your logic in a way to do so after the intialization of the bean or you could detect and execute logic after the ContextRefreshedEvent by impelementing applicationListener and put your logic in onAppplicationEvent method.
One solution would be to use Spring's Container within your servlet. There are many implementations for this purpose, for instance the AnnotationConfigApplicationContext. Spring's documentation describes how to use the ClassPathXmlApplicationContext
Suppose GlobalTemplateScheduler is also a bean, then the key point is this:
ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
GlobalTemplateScheduler sheduler = context.getBean("sheduler", GlobalTemplateScheduler.class);
sheduler.someMethod();
The content of the XML, which is used by the ClassPathXmlApplicationContext is small. But you need to enable component scan:
<context:component-scan base-package="foo.bar.baz" />
Another approach, I could suggest, is to use Spring's DispatcherServlet to wire all the beans together. It can use the same XML, it is just a matter of loading it. The benefit is that is you don't need to load the application context by yourself and launch a bean as an entry point
There are plenty of tutorials how to use this servlet.
If you dont't like to write XML, you could use the WebApplicationInitializer
As i said the beans which i was autowiring were working fine. I just needed those beans in my Scheduler Servlet.
Here's the solution which worked...
In my scheduler servlet i got the application context xml and used the beans which were required...
ApplicationContext context = WebApplicationContextUtils.getWebApplicationContext(this.getServletContext());
GlobalTemplateControl globalTemplateControlObject = context.getBean("globalTemplateControl", GlobalTemplateControl.class);
Thanks #duffymo #Amer Qarabsa #Spindizzy for your suggestions :)

Spring : Create beans at runtime

I am working on a module which needs spring beans to be created at runtime. My driver app is a spring boot app, it needs to create a bean (which is outside the component scan packages of the app) and invoke a method in that bean. Fully qualified class name and method name will be runtime parameters. Below is an example:
Bean to be created:
#Component
#ComponentScan(basePackages = {"com.test"})
public class TestClass {
#Autowired
private AnotherTestClass anotherTest;
public void test(){
System.out.println("In test");
anotherTest.test();
}
}
Here is the driver class code (which obviously doesn't work):
public void test(){
try{
Class testClass = Class.forName("com.test.TestClass");
Object newInstance = testClass.newInstance();
applicationContext.getAutowireCapableBeanFactory().autowireBean(newInstance);
Method testMetod = testClass.getMethod("test");
testMetod.invoke(newInstance, null);
}catch(Exception e){
e.printStackTrace();
}
}
Here, applicationContext is autowired.
As it is instantiating the class via reflection, the object won't have autowired dependency set. What I want to achieve is, to scan all the packages (as mentioned in the component scan annotation), create the beans and set the dependencies. i.e. I want to do exactly the same as what spring does when application starts, but at runtime.
Also, I want it to be completely generic as the beans will mostly be residing in external jars and fully qualified class path and method name will be passed at runtime (like a pluggable architecture).
Any ideas how to achieve this?

AnnotationConfigApplicationContext has not been refreshed yet

I am developing a spring MVC application. When I try to use AnnotationConfigApplicationContext in my controller class I am getting the following error. I have no idea what this statement exactly means.
#RequestMapping(value = "/generate", method = RequestMethod.POST)
public ModelAndView generateMappingFile(#ModelAttribute Mapping mapping)
{
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
MappingFileGenerator mfg = ctx.getBean(MappingFileGenerator.class);
}
Error Message -->
java.lang.IllegalStateException:org.springframework.context.annotation.AnnotationConfigApplicationContext#116b3c0 has not been refreshed yet
Can someone explain me what went wrong here ? I am using Spring 4.0.1.. I am new to spring mvc.
When you are creating a new instance of an ApplicationContext (regardless which type) you are basically creating new instances of each and every bean configured in that ApplicationContext. That is nice the first time, it might work the second and depending on the amount of beans, the type of beans will crash after that. As the context will never be destroy (until the app crashed and is restarted) you will run into possible memory issues, performance issues, strange transactional problems etc.
A general rule of thumb is to never construct a new instance of an ApplicationContext but to use dependency injection instead.
If you really want access to the ApplicationContext put a field of that type in your controller and put #Autowired on it.
#Controller
public class MyController {
#Autowired
private ApplicationContext ctx;
….
}
Then you can do a lookup for the bean you need in the method. This can be handy if you use the ApplicationContext as a factory for your beans. If all the beans you need are singletons it is better to simply inject the bean you need.
#Controller
public class MyController {
#Autowired
private MappingFileGenerator mfg ;
….
}
Now Spring will inject the MappingFileGenerator and it is available for use in your methods. No need to create a new instance of an ApplicationContext.
More information is in the Spring Reference Guide.
#M.Deinum's comment will get quite a few more upvotes.
Think of creating a new ApplicationContext as instantiating a new (instance of an) application. Do you want to do that every time this (or any other method in said application) is called? No, you don't.
I'm guessing you think you do because you need access to your ApplicationContext in this method. To do that - i.e. to get access to the running application context (rather than creating a new one), you want to do
#Controller // or #Service / #Component / ... : tells Spring that this is a bean, and to inject the specified dependencies
class YourClass {
#Autowired // tells Spring that this object is a dependency should should be injected
ApplicationContext ctx;
#RequestMapping(value = "/generate", method = RequestMethod.POST)
public ModelAndView generateMappingFile(#ModelAttribute Mapping mapping) {
MappingFileGenerator mfg = ctx.getBean(MappingFileGenerator.class);
}
The key here is the Autowired annotation, which tells Spring to inject the annotated object as a dependency.
I highly suggest following the links I've included (for starters), as what you're doing here suggests pretty strongly that you haven't wrapped your head around what DI is and does for you, and until you do, using it is likely to be counterproductive toward it's own ends for you.
In case it helps someone, i was having this issue on a new URL Mapping added to a gradle project, i was missing the first slash of the url and that causing this "illegalstate not refreshed yet" on my tests

Categories

Resources