how to load additional bean configuration file in spring at run time - spring

I have a spring app, and in the future we will develop more classes, and for these class we will also use additional configuration files (not overwrite the existing ones) to define the beans. Then how to dynamically load them? I know there is an interface of ApplicationContextAware, I could have a bean running checking whether new configuration files are available, if they come, I could run the
setApplicationContext(ApplicationContext applicationContext)
But then how to use ApplicationContext to load the additional configuration file?
update:
If the app is loaded from XML then I could convert ApplicationContext to ClassPathXmlApplicationContext and then use the load method,but what if AnnotationConfigApplicationContext, it only has scan method to scan package, but what if I want to load from xml?
update:
The following is the code I want to use, it used spring integration to monitor a fold, at run time I could put jar file on the class path, and then put the xml configuration in that folder, this will trigger the loadAdditionBeans function to run, and the xml File object will be passed in, what need to do is to add the context in that File to the current context but not create a child context.
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.integration.annotation.MessageEndpoint;
import org.springframework.integration.annotation.ServiceActivator;
import java.io.File;
#MessageEndpoint
public class FolderWatcher implements ApplicationContextAware {
//private ApplicationContext ctx;
private AnnotationConfigApplicationContext ctx; // it's a spring boot,so the ctx is AnnotationConfigApplicationContext
#Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.ctx=(AnnotationConfigApplicationContext)applicationContext;
}
#ServiceActivator
public void loadAdditionBeans(File file){
/*
file is an xml configuration file, how to load the beans defined it into the currect context,
I don't what to have another hierarchy, since that will make the beans defined in the file not
available in parent.
*/
}
}

PathMatchingResourcePatternResolver pmrl = new PathMatchingResourcePatternResolver(context.getClassLoader());
Resource[] resources = pmrl.getResources(
"classpath*:com/mycompany/**/applicationContext.xml"
);
for (Resource r : resources) {
GenericApplicationContext createdContext = new GenericApplicationContext(context);
XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(createdContext);
int i = reader.loadBeanDefinitions(r);
}
Have a look at the above code and let me know if it helps to resolve your problem.

If you're using the classpath scanning but you still want o load additional configurations from XML, you can simply use the #ImportResource annotation on your #Configuration class and import the XML resource you need:
#Configuration
#ImportResource( { "classpath*:/rest_config.xml" } )
public class MyConfig{
...
}
That way it's easy to mix in legacy XML configuration with newer, Java configs and you don't have to - for example - migrate your entire configuration in one go.
Hope that helps.

Related

How does Spring know where to search for Components or Beans?

In an interview i was asked by the interviewer that "How does Spring know where to search for Components or Beans?".
As I was not aware about the internal flow details I was not able to answer the question properly.
I said through #Component and #Bean we can find. But the interviewer was not happy with the question.
If anybody knows please share your knowledge. TIA
I love to nswer interview questions. Read below...
#ComponentScan
If you understand Component Scan, you understand Spring.
Spring is a dependency injection framework. It is all about beans and wiring in dependencies.
The first step of defining Spring Beans is by adding the right annotation — #Component or #Service or #Repository.
However, Spring does not know about the bean unless it knows where to search for it.
This part of “telling Spring where to search” is called a Component Scan.
You define the packages that have to be scanned.
Once you define a Component Scan for a package, Spring would search the package and all its sub packages for components/beans.
Defining a Component Scan
If you are using Spring Boot, check the configuration in Approach 1.
If you are doing a JSP/Servlet or a Spring MVC application without
using Spring Boot, use Approach 2.
Approach 1: Component Scan in a Spring Boot Project
If your other package hierarchies are below your main app with the #SpringBootApplication annotation, you’re covered by the implicit Component Scan.
If there are beans/components in other packages that are not sub-packages of the main package, you should manually add them as #ComponentScan
Consider below class
package com.in28minutes.springboot.basics.springbootin10steps;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ConfigurableApplicationContext;
#SpringBootApplication
public class SpringbootIn10StepsApplication {
public static void main(String[] args) {
ApplicationContext applicationContext =
SpringApplication.run(SpringbootIn10StepsApplication.class, args);
for (String name: applicationContext.getBeanDefinitionNames()) {
System.out.println(name);
}
}
}
#SpringBootApplication is defined in the SpringbootIn10StepsApplication class which is in the package com.in28minutes.springboot.basics.springbootin10steps
#SpringBootApplication defines an automatic Component Scan on the package com.in28minutes.springboot.basics.springbootin10steps.
You are fine if all your components are defined in the above package or a sub-package of it.
However, let’s say one of the components is defined in package com.in28minutes.springboot.somethingelse
In this case, you would need to add the new package into Component Scan.
You have two options:
Option 1:
#ComponentScan(“com.in28minutes.springboot”)
#SpringBootApplication
public class SpringbootIn10StepsApplication {...}
Option 2:: Define as array
#ComponentScan({"com.in28minutes.springboot.basics.springbootin10steps","com.in28minutes.springboot.somethingelse"})
#SpringBootApplication
public class SpringbootIn10StepsApplication {...}
Approach 2: Non-Spring Boot Project
Option 1:
#ComponentScan(“com.in28minutes)
#Configuration
public class SpringConfiguration {...}
Option 2:
#ComponentScan({"com.in28minutes.package1","com.in28minutes.package2"})
#Configuration
public class SpringConfiguration {...}
XML application context:
<context:component-scan base-package="com.in28minutes" />
Specific multiple packages:
<context:component-scan base-package="com.in28minutes.package1, com.in28minutes.package2" />
The IoC (Inversion of Control) container, represented in Spring by the class ApplicationContext, is the brain behind all of it. It all comes down to using reflection in a really powerful way.
To simplify, let's consider the following steps (all done through reflection):
Search all classes in the classpath
From those classes, get all classes annotated with #Component
For each class annotated with #Component, create a new instance of that class
Check for dependencies, i.e, for each created instance, check all fields annotated with #Autowired and create an instance for each one of them.
Keep everything in the context so they can be used later.
The remaining of this answer is an oversimplified version of how this happens as if we did it ourselves. Thankfully, Spring exists and we don't need to do this ourselves.
The annotations
#Retention(RetentionPolicy.RUNTIME)
public #interface Node {}
#Retention(RetentionPolicy.RUNTIME)
public #interface Wire { }
Some annotated classes for testing
#Node
public class ServiceA {
#Wire
private ServiceB serviceB;
public void doAStuff() {
System.out.println("A stuff");
serviceB.doBStuff();
}
}
#Node
public class ServiceB {
public void doBStuff() {
System.out.println("B stuff");
}
}
The IoC Container
import org.reflections.Reflections;
/* dependency org.reflections:reflections:0.9.12 */
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
public class IoC {
private final Map<Class<?>, Object> allNodes = new HashMap<>();
public void start() {
Reflections reflections = new Reflections(IoC.class.getPackageName());
Set<Class<?>> nodeClasses = reflections.getTypesAnnotatedWith(Node.class);
try {
for (Class<?> c : nodeClasses) {
Object thisInstance = c.getDeclaredConstructor().newInstance();
for (Field f : c.getDeclaredFields()) {
f.setAccessible(true);
if (f.getDeclaredAnnotation(Wire.class) != null) {
Object o = f.getType().getDeclaredConstructor().newInstance();
f.set(thisInstance, f.getType().cast(o));
}
}
allNodes.put(c, thisInstance);
}
} catch (Exception e) {
e.printStackTrace();
}
}
public <T> T getNodeByType(Class<T> cls) {
return cls.cast(allNodes.get(cls));
}
}
And the main class to get it all started.
public class Application {
public static void main(String[] args) {
IoC ioc = new IoC();
ioc.start();
ServiceA serviceA = ioc.getNodeByType(ServiceA.class);
serviceA.doAStuff();
}
}
This will output:
A stuff
B stuff
Of course, Spring is a lot more powerful (and robust) than this. It allows for custom package scanning using #ComponentScan, beans of the same type with different names, singleton/prototype scoped beans, constructor wiring, properties files injection, amongst many other things. When it comes to Spring Boot, the #SpringBootApplication annotation make sure it finds and wire all #Controller annotated classes and set up a Netty/Jetty/Tomcat embedded server to listen to the requests and redirect to the proper controller based on the annotated types.
Well where to search for the beans is defined by the #ComponentScan which can be annotated on the #Configuration class that is used to bootstrap Spring.
For example , it has an attribute called scanBasePackages which tells Spring to scan the beans (A class that is annotated with #Component or its sterotypes such as #Service , #Repository , #Controller etc. ) from certain packages and its sub-packages only.
Then for each bean that are registered , it goes on see if there are any methods annotation with #Bean.If yes, also register them as beans.

How to convert plain java main method program on docker

I wanted to migrate/run old java code to docker using Jenkins.
It is structured to run using normal main method of java (Jar file having main method is executed through some script).
Its making use of spring.xml(applicationContext.xml) files with spring-context-2.5.xsd
Uses properties file for all configurations.
Questions as I am looking for recommendations now on:
Does this project needs to be migrated to spring-boot application for migrating to/creating docker image?
If yes, please have a look at current code block
Do I need to replace properties files by yml files?
Current code of main class can be framed as :
public class SIIRunner {
public static void main(String[] args){
String envStr = null;
if (args != null && args.length > 0) {
envStr = args[0];
}
ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
SIIExecutor siiExecutor= (SIIExecutor) ctx.getBean("SIIExecutor");
siiExecutor.pollAndOperate();
}
}
#SpringBootApplication
public class Application {
public static void main(String[] args) throws Exception {
ApplicationContext app = SpringApplication.run(Application.class,
args);//init the context
SIIExecutor siiExecutor = (SIIExecutor)
app.getBean(SIIExecutor.class);//get the bean by type
}
#Bean // this method is the equivalent of the <bean/> tag in xml
public SIIExecutor getBean(){
return new SIIExecutor();
}
}
As long as you are starting with a base #Configuration class to begin with, which it maybe sounds like you are with #SpringBootApplication, you can use the #ImportResource annotation to include an XML configuration file as well.
#SpringBootApplication
#ImportResource("classpath:beanFileName.xml")
public class SpringConfiguration {
//
}
Spring boot ideal concept is avoid xml file. but if you want to keep xml bean, you can just add #ImportResource("classPath:beanFileName.xml")
I would recommend remove the beanFileName.xml file. and, convert this file to spring annotation based bean. So, whatever class has been created as bean. Just write #Service or #Component annotation before class name. for example:
XML based:
<bean ID="id name" class="com.example.MyBean">
Annotation based:
#Service or #Component
class MyBean {
}
And, add #ComponentScan("Give the package name").
This is the best approach. Hope this helps.

#WebMvcTest mapperscan conflict

My project used spring-boot (1.4.0.release) and mybatis-spring-boot-starter. When I try to have some test code for controller, I always get a exception
Caused by: java.lang.IllegalArgumentException: Property 'sqlSessionFactory' or 'sqlSessionTemplate' are required
at org.springframework.util.Assert.notNull(Assert.java:115)
at org.mybatis.spring.support.SqlSessionDaoSupport.checkDaoConfig(SqlSessionDaoSupport.java:75)
at org.mybatis.spring.mapper.MapperFactoryBean.checkDaoConfig(MapperFactoryBean.java:74)
at org.springframework.dao.support.DaoSupport.afterPropertiesSet(DaoSupport.java:44)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.invokeInitMethods(AbstractAutowireCapableBeanFactory.java:1637)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1574)
... 42 more`
But when I comment #MapperScan("com.toby.mapper"), it runs very well.
Here is my example class:
#MapperScan("com.toby.mapper")
#EnableTransactionManagement
#EnableConfigurationProperties(AppConfig.class)
#SpringBootApplication(scanBasePackages = "com.toby.configuration,com.toby.web.controller,com.toby.service,com.toby.dao")
public class Example {
public static void main(String[] args) throws Exception {
//new SpringApplicationBuilder().sources(Example.class).run(args);
SpringApplication application=new SpringApplication(Example.class);
application.addInitializers(new PropertyPasswordDecodingContextInitializer());
application.run(args);
}
}
Here is my test code:
package com.toby.web.controller;
import com.toby.common.config.AppConfig;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.test.web.servlet.MockMvc;
/**
* Created by Toby on 2016/8/10.
*/
#RunWith(SpringRunner.class)
#WebMvcTest(value = MyRestController.class)
public class MyRestControllerTests {
#Autowired
private MockMvc mvc;
#MockBean
private AppConfig appConfig;
#Test
public void testHome() throws Exception {
/*this.mvc.perform(get("/users").accept(MediaType.TEXT_PLAIN))
.andExpect(status().isOk()).andExpect(content().string("Honda Civic"));*/
}
}
I guess you've updated the description or I didn't read it properly the first time. #MapperScan is a mybatis specific annotation that triggers something but is missing some guard of some sort.
We had the same problem in boot actually. Let's say you put #EnableCaching on your main app. Because slicing disables all auto-configurations but a list of specific ones, the cache auto-configuration would not kick in and you'll get an exception because the CacheManager isn't found. To fix that issue, we've started to create some annotation to easily enable those. If you look at WebMbcTest you'll see it's annotated with AutoConfigureCache that's going to provide a dummy no-op cache manager unless specified otherwise.
Your problem is that the mybatis support is a third party integration and there isn't any support for that. Some solutions:
Change #WebMbvcTest to provide the class of another configuration class, effectivly disabling the use of your main spring boot app. Of course that class shouldn't define the #MapperScan annotation
Move the MapperScan (and anything that's not required with slicing) to another Configuration class. It could be a class in the same package as your app. Slicing won't scan those by default so you'll be fine. It's by far the easiest
Create an issue in the mybatis support so that they improve the auto-configuration to back-off (prevent this exception). I am not sure that's possible actually
Long story short, since #MapperScan is a way to tell mybatis to scan your entities, maybe you shouldn't add it on your main boot app if you use slicing. Because your #WebMbcTest doesn't want to use that obviously.

Multiple ApplicationRunners on classpath, how to make SpringApplication.run() only run one

Context: I have a project with some utilities to do things like data fixing. Each utility is a Java application, i.e. class with main() method. I want to define them as Spring Boot applications so I can use the ApplicationRunner and ApplicationArguments facility. The Spring configuration is defined via annotations in a shared configuration class. I've put a minimal example of this setup below.
Expectation: if I call SpringApplication.run(SomeClass.class, args) where SomeClass is an ApplicationRunner, it runs the run() on that class and not on any other classes that may be in the app context.
What actually happens: it calls all ApplicationRunners that it has in the context.
Why? I understood SpringApplication.run(Class, String[]) to mean, "run this class" whereas it appears to mean "load an app context from this class and run anything you can find in it". How should I fix it to run only 1 class? I don't mind if my other application class isn't in the app context, because all the configuration I need is in the shared config class. But I don't want to have to edit code (e.g. add or remove annotations) according to which class I need to run.
Minimal example:
A Spring config class (shared):
package com.stackoverflow.example;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
#Configuration
public class ExampleSpringConfig {
/** Some bean - just here to check that beans from this config are injected */
#Bean public FooService fooService () {
return new FooService();
}
}
Two application classes
package com.stackoverflow.example;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import javax.annotation.Resource;
#SpringBootApplication
public class SomethingJob implements ApplicationRunner {
#Resource private FooService fooService;
public void run(ApplicationArguments args) throws Exception {
System.out.println("Doing something"); // do things with FooService here
}
public static void main(String[] args) {
SpringApplication.run(SomethingJob.class, args);
}
}
and another that is identical except that it prints "Doing something else".
Output:
[Spring Boot startup logs...]
Doing something else
Doing something
[Spring Boot shutdown logs...]
Firstly, only one class should be annotated with #SpringBootApplication. As you've noticed in your answer, this defines the external "main" entry point. I would recommend this is a different class to your ApplicationRunner classes for clarity and conceptual separation.
To only have some but not all runners run, I've done this by parsing the arguments, and quickly exiting from the runner which should not be called. e.g.
package com.stackoverflow.example;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import javax.annotation.Resource;
#Component
public class SomethingJob implements ApplicationRunner {
#Resource private FooService fooService;
public void run(ApplicationArguments args) throws Exception {
if (!args.containsOption("something")) return
System.out.println("Doing something"); // do things with FooService here
}
}
That way you can do java -jar myjar.jar --something or java -jar myjar.jar --something-else depending which one you want to be run.
I found a workaround while experimenting with my minimal example.
#SpringBootApplication is just an alias for #ComponentScan, #EnableAutoConfiguration and #Configuration. By applying them separately, I discovered that it's the #Configuration annotation that causes this behaviour. If I only apply the other 2, I don't get the issue.
I guess this is because #Configuration means "I'm a configuration class, and any beans I define should be pulled into the context during component scan" and although this class doesn't define an ApplicationRunner, it is one, which has the same effect. Therefore if you have 2 such beans on the classpath, they both get pulled into the app context.
Without #Configuration, the bean you want to run still gets registered since it's referenced by the call to run(), but other ApplicationRunners on the classpath don't.
This fixes my immediate problem by making sure I only have one ApplicationRunner in my app context. But it doesn't answer the wider question, "If I do have several ApplicationRunners, how do I tell Spring Boot which one to run?" So I'd still appreciate any more complete answer or suggestions for a different approach.

How to service external static HTML files in Spring Boot Embedded tomcat?

I'm new to Spring framework and Spring Boot.
I've implemented a very simple RESTful Spring Boot web application.
You can see the nearly full source code in another question: Spring Boot: How to externalize JDBC datasource configuration?
How can the app service external static HTML, css js files?
For example, the directory structure may be as follows:
MyApp\
MyApp.jar (this is the Spring Boot app that services the static files below)
static\
index.htm
images\
logo.jpg
js\
main.js
sub.js
css\
app.css
part\
main.htm
sub.htm
I've read the method to build a .WAR file that contains static HTML files, but since it requires rebuild and redeploy of WAR file even on single HTML file modification, that method is unacceptable.
An exact and concrete answer is preferable since my knowledge of Spring is very limited.
I see from another of your questions that what you actually want is to be able to change the path to static resources in your application from the default values. Leaving aside the question of why you would want to do that, there are several possible answers.
One is that you can provide a normal Spring MVC #Bean of type WebMvcConfigurerAdapter and use the addResourceHandlers() method to add additional paths to static resources (see WebMvcAutoConfiguration for the defaults).
Another approach is to use the ConfigurableEmbeddedServletContainerFactory features to set the servlet context root path.
The full "nuclear option" for that is to provide a #Bean definition of type EmbeddedServletContainerFactory that set up the servlet container in the way you want it. If you use one of the existing concrete implementations they extend the Abstract* class that you already found, so they even have a setter for a property called documentRoot. You can also do a lot of common manipulations using a #Bean of type EmbeddedServletContainerCustomizer.
Is enough if you specify '-cp .' option in command 'java -jar blabla.jar' and in current directory is 'static' directory
Take a look at this Dave Syer's answer implementation.
You can set the document root directory which will be used by the web context to serve static files using ConfigurableEmbeddedServletContainer.setDocumentRoot(File documentRoot).
Working example:
package com.example.config;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.context.embedded.ConfigurableEmbeddedServletContainer;
import org.springframework.boot.context.embedded.EmbeddedServletContainerCustomizer;
import org.springframework.boot.web.servlet.ServletContextInitializer;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.env.Environment;
import org.springframework.util.StringUtils;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import java.io.File;
import java.nio.file.Paths;
#Configuration
public class WebConfigurer implements ServletContextInitializer, EmbeddedServletContainerCustomizer {
private final Logger log = LoggerFactory.getLogger(WebConfigurer.class);
private final Environment env;
private static final String STATIC_ASSETS_FOLDER_PARAM = "static-assets-folder";
private final String staticAssetsFolderPath;
public WebConfigurer(Environment env, #Value("${" + STATIC_ASSETS_FOLDER_PARAM + ":}") String staticAssetsFolderPath) {
this.env = env;
this.staticAssetsFolderPath = staticAssetsFolderPath;
}
#Override
public void onStartup(ServletContext servletContext) throws ServletException {
if (env.getActiveProfiles().length > 0) {
log.info("Web application configuration, profiles: {}", (Object[]) env.getActiveProfiles());
}
log.info(STATIC_ASSETS_FOLDER_PARAM + ": '{}'", staticAssetsFolderPath);
}
private void customizeDocumentRoot(ConfigurableEmbeddedServletContainer container) {
if (!StringUtils.isEmpty(staticAssetsFolderPath)) {
File docRoot;
if (staticAssetsFolderPath.startsWith(File.separator)) {
docRoot = new File(staticAssetsFolderPath);
} else {
final String workPath = Paths.get(".").toUri().normalize().getPath();
docRoot = new File(workPath + staticAssetsFolderPath);
}
if (docRoot.exists() && docRoot.isDirectory()) {
log.info("Custom location is used for static assets, document root folder: {}",
docRoot.getAbsolutePath());
container.setDocumentRoot(docRoot);
} else {
log.warn("Custom document root folder {} doesn't exist, custom location for static assets was not used.",
docRoot.getAbsolutePath());
}
}
}
#Override
public void customize(ConfigurableEmbeddedServletContainer container) {
customizeDocumentRoot(container);
}
}
Now you can customize your app with command line and profiles (src\main\resources\application-myprofile.yml):
> java -jar demo-0.0.1-SNAPSHOT.jar --static-assets-folder="myfolder"
> java -jar demo-0.0.1-SNAPSHOT.jar --spring.profiles.active=myprofile

Resources