After starting my spring boot application I want to start an customer process like creating required folders, files, etc. For that I'm using ApplicationListener<ApplicationReadyEvent>. This works like expected. But I'm building my spring application context with SpringApplicationBuilder. Every child notifies that the application is started correctly. So my customer post-process startes even more than one time.
#SpringBootApplication
#EnableConfigurationProperties(value = {StorageProperties.class})
#EnableAsync
public class Application {
public static void main(String[] args) {
SpringApplicationBuilder parentBuilder
= new SpringApplicationBuilder(Application.class);
parentBuilder.child(Config1.class)
.properties("server.port:1443")
...
.run(args);
parentBuilder.child(Config2.class)
.properties("server.port:2443")
...
.run(args);
}
}
My first idea was, that I can create manuelly a new Bean with #Bean in Config1 for my Event-Listener. But I was not able to overhand the configuration file StorageProperties.class, which is necessary for this class.
Because the Listener has an constructor based dependency injection:
private final Path mPathTo;
public AfterStart(StorageProperties prop) {
this.mPathTo = Paths.get(prob.getPath());
}
How can I be able to start the listener just once per start?
For everyone who is interested in this question. This solution worked for me:
public void onApplicationEvent(ApplicationReadyEvent e) {
if (e.getApplicationContext().getParent == null) {
System.out.println("******************************");
System.out.println("Post-process begins.");
System.out.println("******************************");
}
}
Related
I am in the process of developing a common java library with reusable logic to interact with some AWS services, that will in turn be used by several consumer applications. For reasons outlined here, and the fact that Spring Boot seems to provide a lot of boilerplate free code for things like SQS integration, I have decided to implement this common library as a custom spring boot starter with auto configuration.
I am also completely new to the Spring framework and as a result, have run into a problem where my auto-configured class's instance variables are not getting initialized via the AutoWired annotation.
To better explain this, here is a very simplified version of my common dependency.
CommonCore.java
#Component
public class CommonCore {
#AutoWired
ReadProperties readProperties;
#AutoWired
SqsListener sqsListener; // this will be based on spring-cloud-starter-aws-messaging
public CommonCore() {
Properties props = readProperties.loadCoreProperties();
//initialize stuff
}
processEvents(){
// starts processing events from a kinesis stream.
}
}
ReadProperties.java
#Component
public class ReadProperties {
#Value("${some.property.from.application.properties}")
private String someProperty;
public Properties loadCoreProperties() {
Properties properties = new Properties();
properties.setProperty("some.property", someProperty);
return properties;
}
}
CoreAutoConfiguration.java
#Configuration
public class CommonCoreAutoConfiguration {
#Bean
public CommonCore getCommonCore() {
return new CommonCore();
}
}
The common dependency will be used by other applications like so:
#SpringBootApplication(exclude = {DataSourceAutoConfiguration.class })
public class SampleConsumerApp implements ApplicationRunner {
#Autowired
CommonCore commonCore;
public SampleConsumerApp() {
}
public static void main(String[] args) {
SpringApplication.run(SampleConsumerApp.class, args);
}
#Override
public void run(ApplicationArguments args) {
try {
commonCore.processEvents();
} catch (Exception e) {
e.printStackTrace();
}
}
}
The main problem I have like I mentioned, is the AutoWired objects in the CommonCore instance are not getting initialized as expected. However, I think the actual problems are more deeply rooted; but due to my lack of understanding of the Spring framework, I am finding it difficult to debug this on my own.
I am hoping for a few pointers along these points
Does this approach of developing a custom starter make sense for my use case?
What is the reason for the AutoWired dependencies to not get initialized with this approach?
Wild guess, but I think it's because of the order of how things are constructed. I am talking about this class:
#Component
public class CommonCore {
#AutoWired
ReadProperties readProperties;
#AutoWired
SqsListener sqsListener; // this will be based on spring-cloud-starter-aws-messaging
public CommonCore() {
Properties props = readProperties.loadCoreProperties();
//initialize stuff
}
processEvents(){
// starts processing events from a kinesis stream.
}
}
You are trying to use a Spring injected component in a constructor, but constructor is called before Spring can do its #Autowire magic.
So one option is to autowire as a constructor argument
Something like this (untested):
#Component
public class CommonCore {
private final ReadProperties readProperties;
private final SqsListener sqsListener; // this will be based on spring-cloud-starter-aws-messaging
#AutoWired
public CommonCore(SqsListener sqsListener, ReadProperties readProperties) {
this.readProperties = readPropertis;
this.sqsListener = sqsListener;
Properties props = readProperties.loadCoreProperties();
//initialize stuff
}
processEvents(){
// starts processing events from a kinesis stream.
}
}
Sidenote: I prefer to use dependency injection via constructor arguments always, wherever possible. This also makes unit testing a lot easier without any Spring specific testing libraries.
Background: I'm trying to set up a code-based data migration system for our Cassandra database. I don't have a ton of experience with Java, but if this were a .NET project I'd set up the migrations as a different project under the same solution. However, based on guidance from other team members that are more experienced, it was recommended that I include the migrations in the same package as the rest of the application (which I'm fine with). It was also suggested that the easiest method would be to run the migrations via a web API endpoint (which I'm more skeptical of). In the interest of avoiding opening up a potential security vulnerability, I thought I'd take a shot at making a command-line utility to execute the migrations.
I have a Spring Boot web application with an entry point class that looks like this:
#Configuration
#SpringBootApplication
#EnableAutoConfiguration
#EnableCaching
#EnableScheduling
public class MyApplication extends SpringBootServletInitializer {
#Override
protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
return application.sources(MyApplication.class);
}
public static void main(String[] args) {
new MyApplication().configure(new SpringApplicationBuilder(MyApplication.class)).run(args);
}
}
However, I'm trying to add the functionality to run a couple migration scripts that are packaged with this application via the command line (e.g. java -jar MyApplication.jar migrate), so I added the following class:
#Configuration
#SpringBootApplication
#EnableAutoConfiguration
public class MigrationRunner implements CommandLineRunner {
#Autowired
Session session;
#Override
public void run(String[] args)
{
MigrationResources mr = new MigrationResources();
mr.addMigration(...);
mr.addMigration(...);
MigrationEngine.withSession(session).migrate(mr);
}
}
And then updated my entry point class like this:
// annotations
public class MyApplication extends SpringBootServletInitializer {
private final static String MIGRATE_COMMAND = "migrate";
#Override
protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
return application.sources(MyApplication.class);
}
public static void main(String[] args) {
if (args.length > 0 && args[0].equalsIgnoreCase(MIGRATE_COMMAND)) {
new SpringApplicationBuilder()
.sources(MigrationRunner.class)
.run(Arrays.copyOfRange(args, 1, args.length));
} else {
new MyApplication().configure(new SpringApplicationBuilder(MyApplication.class)).run(args);
}
}
}
The problem is that when I execute this with the migrate arg, Spring throws this error:
Error creating bean with name 'migrationRunner': Unsatisfied dependency expressed through field 'session'
Error creating bean with name 'session' defined in class path resource [org/springframework/boot/autoconfigure/data/cassandra/CassandraDataAutoConfiguration.class]: Invocation of init method failed
All host(s) tried for query failed (tried: server022/XX.YY.ZZ.022:9042 (com.datastax.driver.core.exceptions.TransportException: [server022/XX.YY.ZZ.022:9042] Connection has been closed), server022/XX.YY.ZZ.020:9042 (com.datastax.driver.core.exceptions.TransportException: [server020/XX.YY.ZZ.020:9042] Connection has been closed), server020/XX.YY.ZZ.021:9042 (com.datastax.driver.core.exceptions.TransportException: [server020/XX.YY.ZZ.021:9042] Connection has been closed))
Running it without the migrate arg still works fine. I suspect that Spring is simply not picking up the correct certificates for this Cassandra server, even though it appears to be getting all the other configuration properties (server name, keyspace, etc.)
Question: How can I make a Spring Boot servlet that also has a command-line mode and can connect to the configured Cassandra server in both modes?
All you need to do is,
#SpringBootApplication
public class MyApplication
public static void main(String[] args) {
SpringApplication.run(MyApplication.class, args);
}
}
You have over complicated the application. If you run the MyApplication.main that will run in port 8080 by default.
Bonus, If you need both to start from same class.
#SpringBootApplication
public class MigrationRunner implements CommandLineRunner {
#Autowired
Session session;
public static void main(String[] args) {
SpringApplication.run(MyApplication.class, args);
}
#Override
public void run(String[] args)
{
MigrationResources mr = new MigrationResources();
mr.addMigration(...);
mr.addMigration(...);
MigrationEngine.withSession(session).migrate(mr);
}
}
I have a REST application that reads from a database repository.
I want to add a command line application that reads a CSV and imports the data to the database.
If I add another #SpringBootApplication class that implements CommandLineRunner to the project/jar, Spring starts it at the same time as my main server.
If I add a class that initialises the spring context itself, the jdbc url on the JPARepository uses the defaults instead of those from the spring boot properies
spring.datasource.url=jdbc:h2:file:./test;AUTO_SERVER=TRUE;DB_CLOSE_ON_EXIT=FALSE
#ComponentScan(basePackages = "com.test")
public class CsvImport {
#Autowired
private Repository repository;
public static void main(String[] args) {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(CsvImport.class);
context.start();
CsvImport csvImport = context.getBean(CsvImport.class);
File file = new File(args[0]);
if (file.isFile()) {
csvImport.importCsv(file);
}
context.stop();
}
private void importCsv(File file) {
....
....
Is there a better way to do this?
There are several steps required to get this working.
The first thing is that #SpringBootApplication auto-scans any packages below it and will auto-start any CommandLineRunner that it finds so the command line application needs to be in a parallel package.
e.g.
- com
- test
- rest
- model
- repository
- controller
#RestSpringBoot.java
- Commands
#ImportCsv.java
The next thing is that since the command line application is in a parallel package you need to specify the scanning yourself.
There are 3 parts to this:
#ComponentScan
#EntityScan
#EnableJpaRepositories
The first ensures the beans are created, the second ensures that the hibernate entities are created and the third ensures that the JPA classes are generated correctly.
The last part is to disable the start of the spring boot webserver otherwise you will have port conflicts.
This ends up with:
#SpringBootApplication
#ComponentScan(basePackageClasses = {Repository.class})
#EntityScan(basePackageClasses = {MyEntity.class})
#EnableJpaRepositories(basePackageClasses = {Repository.class})
public class CsvImport {
#Autowired
private RiskRepository repository;
public static void main(String[] args) {
SpringApplication app = new SpringApplication(CsvImport.class);
app.setBannerMode(Banner.Mode.OFF);
app.setWebApplicationType(WebApplicationType.NONE);
app.run(args);
}
#Override
public void run(String... args) throws Exception {
File file = new File(args[0]);
if (file.isFile()) {
importCsv(file);
}
}
private void importCsv(File file) {
....
....
vaadin-spring introduces a couple of Spring scoped objects, the vaadin-session and the vaadin-ui scope. It is necessary to have these two scopes bound before referencing any Vaadin objects in your spring context if:
they are decorated with the #VaadinSessionScope or #UIScope annotations, or
they through some dependency chain reference any bean that is decorated this way.
All runs perfectly well when you start it up in a servlet container like jboss or tomcat. The question is:
If you would like to load a spring application context that contains any of the vaadin beans so decorated for unit testing purposes, how can you create a minimal test that allows the context to be loaded and accessed without starting up a web application container?
Spring MVC is very good at this but when you're using vaadin-spring it's not as straightforward - the relevant vaadin components are highly connected.
(The following example of how to construct a set of Vaadin components to allow access through the abovementioned scopes does not include configuration of the full container, just the minimum required to get a functioning application context.)
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(loader = AnnotationConfigWebContextLoader.class)
#WebAppConfiguration
public class SpringConfigurationTest extends Assert {
#Configuration
#ComponentScan({ "org.example" }) // contains SomeClassReferencingASpringVaadinBean.class
public static class Config {
}
#Autowired
WebApplicationContext applicationContext;
class MyDeploymentConfiguration extends DefaultDeploymentConfiguration {
public MyDeploymentConfiguration(Class<?> servletClass, Properties initParameters) {
super(servletClass, initParameters);
initParameters.put(Constants.SERVLET_PARAMETER_UI_PROVIDER, DefaultUIProvider.class.getName());
}
}
class MyVaadinServlet extends VaadinServlet {
#Override
public String getServletName() {
return getClass().getSimpleName();
}
}
class MyUI extends UI {
#Override
protected void init(VaadinRequest request) {
}
}
#Before
public void setupVaadinScopes() throws Exception {
MyVaadinServlet vaadinServlet = new MyVaadinServlet();
MyDeploymentConfiguration deploymentConfiguration = new MyDeploymentConfiguration(MyVaadinServlet.class,
new Properties());
VaadinServletService vaadinService = new VaadinServletService(vaadinServlet, deploymentConfiguration);
VaadinServletRequest vaadinRequest = new VaadinServletRequest(new MockHttpServletRequest(), vaadinService);
// creates vaadin session and vaadin ui, binds them to thread
VaadinSession vaadinSession = vaadinService.findVaadinSession(vaadinRequest);
Integer uiId = Integer.valueOf(vaadinSession.getNextUIid());
UI ui = new MyUI();
ui.setSession(vaadinSession);
UI.setCurrent(ui);
ui.doInit(vaadinRequest, uiId, null);
vaadinSession.addUI(ui);
}
#Test
public void test0() {
try {
applicationContext.getBean(SomeClassReferencingASpringVaadinBean.class);
} catch (Exception e) {
fail("scopes were probably not set up correctly");
}
}
}
I have small test project to test Spring annotations:
where in nejake.properties is:
klucik = hodnoticka
and in App.java is:
#Configuration
#PropertySource("classpath:/com/ektyn/springProperties/nejake.properties")
public class App
{
#Value("${klucik}")
private String klc;
public static void main(String[] args)
{
AnnotationConfigApplicationContext ctx1 = new AnnotationConfigApplicationContext();
ctx1.register(App.class);
ctx1.refresh();
//
App app = new App();
app.printIt();
}
private void printIt()
{
System.out.println(klc);
}
}
It should print hodnoticka on console, but prints null - String value is not initialized. My code is bad - at the moment I have no experience with annotation driven Spring. What's bad with code above?
You created the object yourself
App app = new App();
app.printIt();
how is Spring supposed to manage the instance and inject the value?
You will however need
#Bean
public static PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer() {
return new PropertySourcesPlaceholderConfigurer();
}
to make the properties available. Also, because the App bean initialized for handling #Configuration is initialized before the resolver for #Value, the value field will not have been set. Instead, declare a different App bean and retrieve it
#Bean
public App appBean() {
return new App();
}
...
App app = (App) ctx1.getBean("appBean");
You need to access the property from a Spring bean, and you need to properly wire in the properties. First, add to your config class this:
#Bean
public static PropertySourcesPlaceholderConfigurer propertyPlaceHolderConfigurer() {
PropertySourcesPlaceholderConfigurer props = new PropertySourcesPlaceholderConfigurer();
props.setLocations(new Resource[] { new ClassPathResource("com/ektyn/springProperties/nejake.properties") }); //I think that's its absolute location, but you may need to play around with it to make sure
return props;
}
Then you need to access them from within a Spring Bean. Typically, your config file should not be a bean, so I would recommend you make a separate class, something like this:
#Component //this makes it a spring bean
public class PropertiesAccessor {
#Value("${klucik}")
private String klc;
public void printIt() {
System.out.println(klc);
}
}
Finally, add this to your config to make it find the PropertiesAccessor:
#ComponentScan("com.ektyn.springProperties")
Then you can access the PropertiesAccessor bean from your app context and call its printIt method.