I have a spring boot application that I wish to script with groovy.
I wish to autowire spring boot services to the script but when I compile and run the script autowired accountService turns out to be null.
I see "setApplicationContext called with org.springframework.context.annotation.AnnotationConfigApplicationContext#34b56772" in the log so setApplicationContext does get called.
What am I missing. Thanks.
PS. I cleaned the code a little to better illustrate the problem.
More #1:
Purpose of the application is to extend some base functionality that is written in java , using groovy scripts.
hello.groovy is loaded from resources folder and extends CustomScript which has more functionality that script will override in production.
CustomScript is now ApplicationContextAware but autowired AccountService in groovy script is still null.
I need to make Spring know about the groovy script that extended CustomScript that I instantiated using "newInstance".
Making CustomScript ApplicationContextAware by implementing ApplicationContextAware and/or making groovy script itself ApplicationContextAware by implementing ApplicationContextAware didn't help so far.
The accountService in "#Autowired AccountService accountService" line in the groovy script always returns null.
AccountService.java
#Service
public class AccountService
{
public void hello()
{
System.out.println( "hello from account" );
}
}
CustomScriptInterface.java
public interface CustomScriptInterface
{
public void main();
}
CustomScript.java
#Component
public class CustomScript implements CustomScriptInterface, Serializable, ApplicationContextAware, BeanNameAware
{
private ApplicationContext applicationContext;
private String name;
#Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException
{
System.out.println( "*** setApplicationContext called with " + applicationContext );
this.applicationContext = applicationContext;
}
public ApplicationContext getApplicationContext()
{
return applicationContext;
}
#Override
public void setBeanName(String name)
{
this.name = name;
}
public void main()
{
}
}
CustomScriptImportCustomizer.java
public class CustomScriptImportCustomizer extends ImportCustomizer
{
public CustomScriptImportCustomizer()
{
addStarImports("gro2vy");
addStarImports("org.springframework.beans.factory.annotation");
addImports("org.springframework.stereotype.Component");
}
}
Gro2vyApplication.java
#SpringBootApplication
public class Gro2vyApplication
{
#Autowired private ApplicationContext ctx;
public CustomScript compile(String groovy) throws Throwable
{
CompilerConfiguration compilerConfiguration = new CompilerConfiguration();
CustomScriptImportCustomizer importCustomizer = new CustomScriptImportCustomizer();
compilerConfiguration.addCompilationCustomizers( importCustomizer );
ClassLoader classLoader = this.getClass().getClassLoader();
GroovyClassLoader groovyClassLoader = new GroovyClassLoader( classLoader, compilerConfiguration );
Class<CustomScriptInterface> clazz = groovyClassLoader.parseClass( groovy );
CustomScript script = (CustomScript) clazz.newInstance();
return script;
}
public void init() throws Throwable
{
File resource = new ClassPathResource( "hello.groovy" ).getFile();
String groovy = new String( Files.readAllBytes(resource.toPath() ) );
CustomScript script = compile( groovy );
postProcess( script );
script.main();
}
public void postProcess(Object object) throws BeansException, IllegalArgumentException, IllegalAccessException
{
Field[] fields = object.getClass().getDeclaredFields();
for(int i = 0; i < fields.length; i++)
{
Annotation[] annotations = fields[i].getDeclaredAnnotations();
if ( annotations.length > 0 )
{
for(Annotation annotation : annotations)
{
if ( annotation instanceof Autowired )
{
Autowired a = (Autowired) annotation;
Class claz = fields[i].getType();
fields[i].set( object, applicationContext.getBean( claz ) );
}
}
}
}
}
public static void main(String[] args) throws Throwable
{
SpringApplication.run(Gro2vyApplication.class, args);
new Gro2vyApplication().init();
}
}
hello.groovy
class Runner extends CustomScript
{
// has to be defined public
#Autowired public AccountService accountService
#Override
public void main() throws Throwable
{
accountService.hello()
}
}
You are creating a new instance for Runner class using reflection and not leveraging Spring's Dependency Injection. Either you need to instantiate AccountService as well while you instantiate Runner, because that way you are not using Spring container capabilities. Or, you need to use ApplicationContextAware on your Runner class.
I solved my problem as follows:
I instantiate my groovy script which has an autowired Spring service field.
Before I call the groovy script's main method , I process the autowired annotations in it and "autowire" them myself.
If anyone has a more elegant solution , I'd like to hear. Thanks.
There is an abstract class groovy.lang.Script, which lets you CustomScript extends groovy.lang.Script, and you could put your #Autowire service to that Binding property and run with the exec() method
Related
I have a spring boot batch application which use program arguments to get some files and manipulate it. the app works fine but i have problems when running the junit tests.
Here is my code :
#Component
public class ApplicationArguments implements InitializingBean {
#Autowired private org.springframework.boot.ApplicationArguments appArgs;
private String filePath;
#Override
public void afterPropertiesSet() throws Exception {
filePath = appArgs.getSourceArgs()[0];
}
}
this bean is used by another one to build the full path :
#Component
public class InitPaths implements InitializingBean {
#Autowired private ApplicationArguments myAppArgs;
private String fullPath;
#Override
public void afterPropertiesSet() throws Exception {
fullPath = myAppArgs.getFilePath(); //this will be null when launching tests
fullPath.toString();//this will throw a NullPointerException if we run the test
}
}
the application works fine using this command :
java -jar myApp.jar fileName.txt
is there any solution to pass the same argument to the junit test ?
I tried to use mocks but i had the same issue :
#RunWith(SpringRunner.class)
#SpringBootTest
public class BatchTest {
#MockBean
ApplicationArguments applicationArguments;
#Autowired
#InjectMocks
InitPaths initPaths;
#Before
public void before() {
when(applicationArguments.getFilePath()).thenReturn("myCustomFile.dat");
}
#Test
public void contextLoad() {
}
}
Here is the error :
Invocation of init method failed; nested exception is java.lang.NullPointerException
The problem is because method afterPropertiesSet() in InitPaths have been running a earlier them before in test. Thats mean your mocked ApplicationArguments do not have any mocked behavior. From my perspective you might create new mocked ApplicationArguments with predefined behavior
#RunWith(SpringRunner.class)
#SpringBootTest
#Import(ApplicationArgumentsTestConfig.class)
public class BatchTest {
#Autowired
InitPaths initPaths;
#Test
public void contextLoad() {
}
public static class ApplicationArgumentsTestConfig {
#Bean
#Primary
public ApplicationArguments mockArg() {
ApplicationArguments mocked = Mockito.mock(ApplicationArguments.class);
Mockito.when(mocked.getFilePath()).thenReturn("test-mock-path");
return mocked;
}
}
}
I just checked work for me.
I am trying to create a Spring boot application with JFrame. I can see my beans in applicationContext but they are not getting autowired. I am unable to find the reason for this issue. Can someone help me with this?
Here is the code:
JavauiApplication - it is showing both userManager and userNameRepository is beans
#SpringBootApplication
public class JavauiApplication implements CommandLineRunner {
#Autowired
private ApplicationContext appContext;
public static void main(String[] args) {
new SpringApplicationBuilder(JavauiApplication.class).headless(false).run(args);
java.awt.EventQueue.invokeLater(() -> new InputNameForm().setVisible(true));
}
#Override
public void run(String... args) throws Exception {
String[] beans = appContext.getBeanDefinitionNames();
Arrays.sort(beans);
for (String bean : beans) {
System.out.println(bean);
}
}
}
InputNameForm.java -> userManager coming null
#Component
public class InputNameForm extends javax.swing.JFrame {
/**
* Creates new form InputNameForm
*/
public InputNameForm() {
initComponents();
}
#Autowired
UserManager userManager;
private void submitButtonActionPerformed(java.awt.event.ActionEvent evt) {
userManager.setName(firstName.getText(), lastName.getText());
}
/**
* #param args the command line arguments
*/
public static void main(String args[]) {
try {
for (javax.swing.UIManager.LookAndFeelInfo info : javax.swing.UIManager.getInstalledLookAndFeels()) {
if ("Nimbus".equals(info.getName())) {
javax.swing.UIManager.setLookAndFeel(info.getClassName());
break;
}
}
} catch (ClassNotFoundException ex) {
java.util.logging.Logger.getLogger(InputNameForm.class.getName()).log(java.util.logging.Level.SEVERE, null, ex);
}
/* Create and display the form */
java.awt.EventQueue.invokeLater(new Runnable() {
public void run() {
new InputNameForm().setVisible(true);
}
});
}
// Variables declaration - do not modify//GEN-BEGIN:variables
private javax.swing.JTextField firstName;
private javax.swing.JLabel firstNameLabel;
private javax.swing.JTextField lastName;
private javax.swing.JLabel lastNameLabel;
private javax.swing.JButton submitButton;
// End of variables declaration//GEN-END:variables
}
UserManager.java -> userNameRepository is coming null
#Component
public class UserManager {
#Autowired
UserNameRepository userNameRepository;
public void setName(String firstName, String lastName) {
userNameRepository.save(new UserName(firstName, lastName));
System.out.println(userNameRepository.findAllByFirstName(firstName));
}
}
It's a very common problem and it occurs because newcomers don't understand how the IoC container works.
Firstly, BeanDefinitionReader reads metadata about your beans from XML, Annotations(#Component, #Service etc), JavaConfig or Groovy script.
There are several BeanPostProcessor's which is responsible for reading all of these Spring annotation you're writing(#Autowired etc).
BeanFactory creates all BeanPostProcessor's then it creates all of your beans.
What happen if you create your bean with #Autowired dependencies via new operator? Nothing, because it isn't actually a bean. The object you created isn't related to IoC container. You may have the bean already in your ApplicationContext if you marked it with #Component(for example) but the object which was created via new operator wont be processed by Spring(annotations won't work).
Hope this helps.
PS: The lifecycle is simplified.
I had the same problem few days ago. What I undertood was that GUI builders like the one that comes with netbeans will automatically create components using new keyword. This means that those components won't be manage by spring. The code usually loks like this:
private void initComponents() {
jPanel1 = new javax.swing.JPanel(); //This component will not be managed by spring.
//...
}
You could use the following class provided here, to make it work.
#Component
public class BeanProvider {
private static ApplicationContext applicationContext;
// Autowires the specified object in the spring context
public static void autowire(Object object) {
applicationContext.getAutowireCapableBeanFactory().autowireBean(object);
}
#Autowired
private void setApplicationContext(ApplicationContext applicationContext) {
BeanProvider.applicationContext = applicationContext;
}
}
The top level SwingApp class:
#SpringBootApplication
public class SwingApp implements CommandLineRunner {
public static void main(String[] args) {
new SpringApplicationBuilder(SwingApp.class)
.headless(false).bannerMode(Banner.Mode.OFF).run(args);
}
#Override
public void run(String... args) throws Exception {
SwingUtilities.invokeLater(() -> {
MainFrame frame = new MainFrame();
frame.setVisible(true);
});
}
}
The MainFrame class:
public class MainFrame extends javax.swing.JFrame {
public MainFrame() {
initComponents();
}
private void initComponents() {
//Gui Builder generated code. Bean not managed by spring.
//Thus, autowired inside CustomPanel won't work if you rely on ComponentScan.
jPanel1 = new CustomJPanel();
//...
}
private CustomJPanel jPanel1;
}
The panel class where you want to autowire things:
//#Component //not needed since it wont work with gui generated code.
public class CustomJPanel extends javax.swing.JPanel{
#Autowired
private SomeRepository someRepository
public CustomJPanel(){
BeanProvider.autowire(this); //use someRepository somewhere after this line.
}
}
I have the same problem in a JavaFx project. Service and Component annotated classes were null in UI controllers even if it was shown in context that it was created. Below code worked for me
#Component
public class FxmlLoaderWithContext {
private final ApplicationContext context;
#Autowired
public FxmlLoaderWithContext(ApplicationContext context) {
this.context = context;
FXMLLoader fxmlloader = new FXMLLoader();
fxmlloader.setControllerFactory(context::getBean); //this row ensure services and components to be autowired
}
}
I think it returns null because you using command new to create object, such as new InputNameForm(). When creating object like that, the object isn't managed by Spring. That's why autowired not working.
The solution is registering your class as a bean.
You can use a class like in here.
#Component
public class BeanProvider {
private static ApplicationContext applicationContext;
public static void autowire(Object object) {
applicationContext.getAutowireCapableBeanFactory().autowireBean(object);
}
#Autowired
private void setApplicationContext(ApplicationContext applicationContext) {
BeanProvider.applicationContext = applicationContext;
}
}
And then, in your class InputNameForm constructor, call this:
class InputNameForm() {
BeanProvider.autowire(this);
...
}
And that's it. Spring will take care the rest.
I'm trying to figure out how to build a Spring Boot standalone app. Of course to have things autowired requires some initial context starting point. If I just try to Autowire a class to run a job it is null even if I make it static.
Is there a way to use Spring #Services in a standalone non-web app?
#SpringBootApplication
public class MyApplication {
#Autowired
private static JobRunnerService job;
public static void main(String[] args) {
SpringApplication.run(MyApplication.class, args);
job.send(); //job is null !
}
}
So first wired in a static JobRunnerService to the main running MyApplication the JobRunner(Service) Class has a non-static SshSessionService wired into it.
the SshSession(Service) finally just has a no-arg constructor.
#Service("jobRunnerService")
public final class JobRunner implements JobRunnerService{
#Autowired
private SshSessionService ssh;
#Autowired
public JobRunner(SshSessionService ssh){
this.ssh = ssh;
}
public void sendToAgent() { ....
}
#Service("sshSessionService")
public class SshSession implements SshSessionService {
public SshSession() {
}
}
It starts off being null at the JobRunnerService job reference.
Several different solutions comes to mind:
If you take a look at the SpringApplication.run() method you will notice that it returns a ApplicationContext. From that, you can fetch the JobRunnerService, e.g.
#SpringBootApplication
public class MyApplication {
public static void main(String[] args) {
ApplicationContext ctx = SpringApplication.run(MyApplication.class, args);
JobRunnerService job = ctx.getBean(JobRunnerService.class);
job.send();
}
}
Another solution is to use #PostConstruct annotation for the send() method:
#Service("jobRunnerService")
public class JobRunner implements JobRunnerService {
#PostConstruct
public void send() { ... }
}
However in your case, I would implement the ApplicationRunner interface, either as a separate bean which autowires the JobRunnerService and then calls its send() method
#Component
public class SendRunner implements ApplicationRunner {
#Autowired
private JobRunnerService job;
#Override
public void run(ApplicationArguments args) {
job.send();
}
}
or let the JobRunner implement the ApplicationRunner interface directly:
#Service("jobRunnerService")
public class JobRunner implements JobRunnerService, ApplicationRunner {
#Override
public void send() { ... }
#Override
public void run(ApplicationArguments args) {
send();
}
}
You haven't provided the code for JobRunnerService but I am assuming it has a default constructor and that it is annotated by #Component for Spring to figure it out as a bean before you can actually autowire it. your job is null probably because it's not able to find an autowired bean for JobRunnerService and that's probably because you don't have an identifier for Spring to scan and create bean of type JobRunnerService
You can use #Servicesor #Component to the JobRunnerService class then add annotation #ComponentScan("package of JobRunnerService") below #SpringBootApplication, see this link:
How to scan multiple paths using the #ComponentScan annotation?
You need a few steps to get your standalone app working:
A class with main() method.
A #SpringBootApplication annotation to your main class.
And a call to the SpringApplication.run() method.
package com.example.myproject;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
#SpringBootApplication // same as #Configuration #EnableAutoConfiguration #ComponentScan
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
As noted, the #SpringBootApplication is a composite annotation which consist of #Configuration #EnableAutoConfiguration and #ComponentScan. In other words, it can be replaced by the three latter annotations. Alternatively, you can use the alias scanBasePackage or scanBasePackageClasses to customize which directories that should be used for component scanning.
The example is copied from the #SpringBootApplication paragraph in the Spring Boot reference docs (see link above). If you would like to quick start your project, complete with build scripts (Maven or Gradle), dependencies, etc, you can generate a project skeleton using the Spring Initializr
I'm trying to run as Thread/runnable now as mentioned in the Spring document 3. Task Execution and Scheduling..
import org.springframework.core.task.TaskExecutor;
public class TaskExecutorExample {
private class MessagePrinterTask implements Runnable {
private String message;
public MessagePrinterTask(String message) {
this.message = message;
}
public void run() {
System.out.println(message);
}
}
private TaskExecutor taskExecutor;
public TaskExecutorExample(TaskExecutor taskExecutor) {
this.taskExecutor = taskExecutor;
}
public void printMessages() {
for(int i = 0; i < 25; i++) {
taskExecutor.execute(new MessagePrinterTask("Message" + i));
}
}
}
So in my case I'm trying...
#Service("jobRunnerService")
#Component
public class JobRunner implements JobRunnerService, ApplicationRunner{
#Autowired
public TaskExecutor taskExecutor;
#Autowired
private SshSessionService ssh;
private class JobTask implements Runnable{
public void run(){
Boolean success = connectToAgent();
if(success){
log.debug("CONNECTED!!!");
}
}
}
/**
* Construct JobRunner with TaskExecutor
* #param taskExecutor
*/
#Autowired
public JobRunner(TaskExecutor taskExecutor, SshSessionService ssh) {
this.taskExecutor = taskExecutor;
this.ssh = ssh;
}
private Map<String, String> sessionParams;
private final Logger log = LoggerFactory.getLogger(this.getClass());
#Override
public void run(ApplicationArguments args) {
/**
* Starting point of application
*
*/
taskExecutor.execute(new JobTask());
}
just getting org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type [org.springframework.core.task.TaskExecutor] found for dependency
How can i get the imported lib to be accepted as a TaskExecutor Bean ??
So I'm trying to write a standalone application using Spring and Mybatis. I used the same DAO's and stuff for a web application, and everything works fine. I want to be able to reuse as much of that code as possible to automate a process. I need to autowire a DAO and use it from a main() method. Right now, I have it modeled somewhat off of this question: Autowiring a Spring 3.2 standalone application fails so I have something like
public class Main{
#Autowired
DAO dao;
public Main(){
final ApplicationContext context = new FileSystemXmlApplicationContext("src/beans.xml");
AutowireCapableBeanFactory acbFactory = context.getAutowireCapableBeanFactory();
acbFactory.autowireBean(this);
System.out.println(dao);
}
public static void main(String[] args) throws Exception {
Main m = new main();
}
The output is null. My DAO file is an interface and the implemented interface has something like
#Repository
#Component
public class DAOImpl implements DAO{
#Autowired
mapper m;
//some methods
}
How do I get it to autowire properly?
EDIT: As suggested, I tried doing this:
public class Main{
#Autowired
DAO dao;
public Main(){
}
public static void main(String[] args) throws Exception {
Main m = new main();
final ApplicationContext context = new FileSystemXmlApplicationContext("src/beans.xml");
AutowireCapableBeanFactory acbFactory = context.getAutowireCapableBeanFactory();
acbFactory.autowireBean(m);
System.out.println(m.dao);
}
It still prints out null
I have a Spring class.
#Service("dbManager")
#Repository
#Transactional
public class DatabaseManager {
GenericXmlApplicationContext context;
#PersistenceContext
private EntityManager em;
public DatabaseManager(GenericXmlApplicationContext context) {
this.context = context;
}
....
} //end of class DatabaseManager
I have SpringUtil class
public class SpringUtil {
public static GenericXmlApplicationContext loadSpringContext(String springXmlFile) {
GenericXmlApplicationContext context = new GenericXmlApplicationContext();
context.load(springXmlFile);
context.refresh();
return context;
} //end of loadSpringContext()
} //end of class SpringUtil
Now in main i am using some thing like
public class Regulator {
public static void main( String[] args ) {
Test test = new Test;
test.start();
} //end of main()
} //end of class Regulator
Here is test class
public class Test {
public void start() {
String springXmlFile = "classpath:spring/plcb-app-context-xml.xml";
GenericXmlApplicationContext context = SpringUtil.loadSpringContext(springXmlFile);
} //end of reportStudent()
} //end of class Test
But i am getting error that
Could not instantiate bean class [...DatabaseManager]: No default constructor
found; nested exception is java.lang.NoSuchMethodException:
...DatabaseManager.<init>()
I want that when DatabaseManager class created then spring context taht i am creating using SpringUtil.loadSpringContext(springXmlFile) must pass to it. How can i do it ?
Thanks
Edit
-------------------
public void switchDataSource(DatabaseType databaseType) {
DriverManagerDataSource dataSource = null;
if (databaseType == DatabaseType.LEGACY) {
dataSource = (DriverManagerDataSource)context.getBean("myLegacyDataSource");
} else if (databaseType == DatabaseType.LS360) {
dataSource = (DriverManagerDataSource)context.getBean("myLs360DataSource");
}
LocalContainerEntityManagerFactoryBean emf = context.getBean("myEmf", LocalContainerEntityManagerFactoryBean.class);
emf.setDataSource(dataSource);
}
#SuppressWarnings("unchecked")
#Transactional(readOnly=true)
public List<Object> getResultList(String query, Class mappingClass) throws Exception {
Query emQuery = em.createNativeQuery(query, mappingClass);
return emQuery.getResultList();
} //end of findTraineeFromLegacy()
Actually i have these two methods in my DatabaseManager class. I am setting context so i can get bean from the context in switchDataSource() method.
One thing that i can do is remove instance filed and change the method to
public void switchDataSource(DatabaseType databaseType, GenericXmlApplicationContext context) {
....
}
This is why i am doing this ?
Thanks
Have a no-arg constructor for DatabaseManager.
Implements ApplicationContextAware in DatabaseManager. Spring will know this bean needs to be notified of the application context:
#Service("dbManager")
#Repository
#Transactional
public class DatabaseManager implements ApplicationContextAware {
private ApplicationContext context;
public DatabaseManager() {...}
#Override
public void setApplicationContext(ApplicationContext appContext) {
this.context = appContext;
}
} //end of class DatabaseManager
however, double think if you really need that injected. In most case you are doing something wrong.
Update:
For your requirement in your update, which you want your DB Manager to switch datasource base on input type, although it doesn't seems very normal doing such thing, you can simply have your DB Manager injected with a Map and do whatever you want, instead of injecting the app context.
#Service("dbManager")
#Repository
#Transactional
public class DatabaseManager implements ApplicationContextAware {
#Resource("&emfBean")
private LocalContainerEntityManagerFactoryBean emfBean;
#Resource("dbManagerDsMap")
private Map<DatabaseType, Datasource> dsMapping;
public DatabaseManager() {...}
public void switchDataSource(DatabaseType databaseType) {
emfBean.setDatasource(dsMapping.get(databaseType));
}
} //end of class DatabaseManager
However I strongly suggest you not doing such thing. Consider having individual entityManagerFactory for each DB you are connecting to, and use the correct emf to connect to DB, instead doing this weird switching logic. I believe it is not supposed to be changed after your application start.