I am migrating a tiny Spring Boot application to Micronaut 1.0.1 and I was wondering if there is an equivalent for org.springframework.core.io.Resource and their implementation such as ClasspathResource?
In Spring Boot I inject a resource into a service using its constructor.
#Service
public class MyService() {
private final Resource resource;
public MyService(Resource resource) { this.resource = resource; }
}
How can I do this in Micronaut?
#Singleton
public class MyService() {
private final Resource resource;
#Inject
public MyService(Resource resource) { this.resource = resource; }
}
In Micronaut you can use io.micronaut.core.io.ResourceLoader variants, such as io.micronaut.core.io.scan.ClassPathResourceLoader or io.micronaut.core.io.file.FileSystemResourceLoader. One option to get them is via io.micronaut.core.io.ResourceResolver:
ClassPathResourceLoader loader = new ResourceResolver().getLoader(ClassPathResourceLoader.class).get();
Optional<URL> resource = loader.getResource("classpath:foo/bar.txt");
I used io.micronaut.core.io.ResourceLoader. Wired through the constructor:
#Controller("root")
public class MyController {
private final ResourceLoader loader;
public MyController(ResourceLoader loader) {
this.loader = loader;
}
#Get("/index")
#Produces(MediaType.TEXT_HTML)
public String greet() throws IOException {
return new String(loader.getResourceAsStream("index.html").get().readAllBytes());
}
}
path to my index.html: src/main/resources/index.html
ResourceLoader works well but you can do better. In Micronaut you can use io.micronaut.core.io.Readable. It is roughly equivalent to Spring's Resource. You can also use Readable in ConfigurationProperties and thus bind your configuration yaml/properties directly to Readable properties:
micronaut:
application:
name: Demo
images:
image-file: "classpath:images/bismarckia-nobilis.jpg"
# image-file: "file:/path/to/images/bismarckia-nobilis.jpg"
other-files:
- "classpath:images/bismarckia-nobilis.jpg"
- "classpath:images/bamboo.jpg"
- "classpath:images/hibiscus.jpg"
I have created:
Blog post: https://hillert.com/blog/from-resource-to-readable/
Demo: https://github.com/ghillert/resource-readable-demo
You can do it this way
#Singleton
public class MyService {
#Value("classpath:your-file.json")
private Readable readable;
}
or
#Singleton
public class MyService {
private final Readable readable;
public MyService(#Value("classpath:your-file.json") Readable file) {
this.readable = readable;
}
}
Related
Trying to build a task executor app in spring boot. The idea is to design a template to retrieve the default TaskConfig so that executor can just execute it.
#Component
public class TaskExecutor {
private final TaskTemplate taskTemplate;
#Autowired
public TaskExecutor(TaskTemplate taskTemplate) {
this.taskTemplate=taskTemplate;
}
public void runTask() {
final TaskConfiguration taskConfig = taskTemplate.getTaskConfig("taskName");
taskConfig.do();
}
}
#Component
public class TaskTemplate {
private final TaskParam1 taskParam1;
private final TaskParam2 taskParam2;
#Autowired
public TaskTemplate(TaskParam1 taskParam1, TaskParam2 taskParam2) {
this.taskParam1 = taskParam1;
this.taskParam2 = taskParam2;
}
public TaskConfiguration getTaskConfig() {
// Logic to build the task configuration from task template params
}
}
The problem I see is that the TaskTemplate is coupled with the TaskExecutor (Autowired), which I wish to remove.
I wanted to replace it with a static convenient method to return the singleton Template so that I could execute the getTaskConfig with it.
Looking for suggestion to improve upon this.
Thanks
You can inject ApplicationContext to another bean like below code. After spring initialized you can use BeanGetter.getTaskTemplate() to get TaskTemplate singleton bean.
...
final TaskConfiguration taskConfig = BeanGetter.getTaskTemplate().getTaskConfig("taskName");
...
#Service
public class BeanGetter {
private static ApplicationContext applicationContext;
#Autowired
public BeanGetter(ApplicationContext applicationContext) {
this.applicationContext = applicationContext;
}
public static TaskTemplate getTaskTemplate(){
return applicationContext.getBean(TaskTemplate.class);
}
}
I have a spring boot application(microservice).
I have to use an external library within this service.
External Library is as below:
public class ExternalConfig {
private String config1;
private boolean config2;
}
public class ExternalService {
private ExternalConfig config;
public ExternalService(ExternalConfig config) {
this.config = config;
}
public void performSomeAction() {
}
}
Now, this library is not annotated with any spring annotation. And in my spring app, I have a Config and usage as below:
public class Config {
#Value("${abc.config1}")
private String config1;
#Value("${abc.config2}")
private String config2;
// How do I load the config from the application.properties.
private ExternalConfig externalConfig;
}
#Service
#ComponentScan
public class ExternalLibraryService {
private ExternalService externalService;
public ExternalLibraryService(ExternalConfig config) {
externalService = new ExternalService(config);
}
public void doSomething() {
.
.
externalService.performSomeAction();
.
.
}
}
And my application.properties is as below:
# ABC Config
abc.config1=value1
abc.config2=value2
# External Config
external.config1=exvalue1
external.config2=true
Now how do I load the config to the external library?
One possible way is to extend the ExternalConfig with my spring app and load it. I want to know if there is a way in spring boot to load these.
You could create an ExternalConfig as a spring bean.
Something like this
#Bean
public ExternalConfig externalConfig(#Value("${external.config1}") String config1,
#Value("${external.config2}") boolean config2) {
return new ExternalConfig(config1, config2);
}
I have a Spring-Boot-Application as a multimodule-Project in maven. The structure is as follows:
Parent-Project
|--MainApplication
|--Module1
|--ModuleN
In the MainApplication project there is the main() method class annotated with #SpringBootApplication and so on. This project has, as always, an application.properties file which is loaded automatically. So I can access the values with the #Value annotation
#Value("${myapp.api-key}")
private String apiKey;
Within my Module1 I want to use a properties file as well (called module1.properties), where the modules configuration is stored. This File will only be accessed and used in the module. But I cannot get it loaded. I tried it with #Configuration and #PropertySource but no luck.
#Configuration
#PropertySource(value = "classpath:module1.properties")
public class ConfigClass {
How can I load a properties file with Spring-Boot and access the values easily? Could not find a valid solution.
My Configuration
#Configuration
#PropertySource(value = "classpath:tmdb.properties")
public class TMDbConfig {
#Value("${moviedb.tmdb.api-key}")
private String apiKey;
public String getApiKey() {
return apiKey;
}
#Bean
public static PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer() {
return new PropertySourcesPlaceholderConfigurer();
}
}
Calling the Config
#Component
public class TMDbWarper {
#Autowired
private TMDbConfig tmdbConfig;
private TmdbApi tmdbApi;
public TMDbWarper(){
tmdbApi = new TmdbApi(tmdbConfig.getApiKey());
}
I'm getting an NullPointerException in the constructor when I autowire the warper.
For field injection:
Fields are injected right after construction of a bean, before any config methods are invoked. Such a config field does not have to be public. Refer Autowired annotation for complete usage. Use constructor injection in this case like below:
#Component
public class TMDbWarper {
private TMDbConfig tmdbConfig;
private TmdbApi tmdbApi;
#Autowired
public TMDbWarper(final TMDbConfig tmdbConfig){
this.tmdbConfig = tmdbConfig;
tmdbApi = new TmdbApi(tmdbConfig.getApiKey());
}
(or)
Use #PostConstruct to initialise like below:
#Component
public class TMDbWarper {
#Autowired
private TMDbConfig tmdbConfig;
private TmdbApi tmdbApi;
#PostConstruct
public void init() {
// any initialisation method
tmdbConfig.getConfig();
}
Autowiring is performed just after the creation of the object(after calling the constructor via reflection). So NullPointerException is expected in your constructor as tmdbConfig field would be null during invocation of constructor
You may fix this by using the #PostConstruct callback method as shown below:
#Component
public class TMDbWarper {
#Autowired
private TMDbConfig tmdbConfig;
private TmdbApi tmdbApi;
public TMDbWarper() {
}
#PostConstruct
public void init() {
tmdbApi = new TmdbApi(tmdbConfig.getApiKey());
}
public TmdbApi getTmdbApi() {
return this.tmdbApi;
}
}
Rest of your configuration seems correct to me.
Hope this helps.
Here is a Spring Boot multi-module example where you can get properties in different module.
Let's say I have main application module, dataparse-module, datasave-module.
StartApp.java in application module:
#SpringBootApplication
public class StartApp {
public static void main(String[] args) {
SpringApplication.run(StartApp.class, args);
}
}
Configuration in dataparse-module. ParseConfig.java:
#Configuration
public class ParseConfig {
#Bean
public XmlParseService xmlParseService() {
return new XmlParseService();
}
}
XmlParseService.java:
#Service
public class XmlParseService {...}
Configuration in datasave-module. SaveConfig.java:
#Configuration
#EnableConfigurationProperties(ServiceProperties.class)
#Import(ParseConfig.class)//get beans from dataparse-module - in this case XmlParseService
public class SaveConfig {
#Bean
public SaveXmlService saveXmlService() {
return new SaveXmlService();
}
}
ServiceProperties.java:
#ConfigurationProperties("datasave")
public class ServiceProperties {
private String message;
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
}
application.properties in datasave-module in resource/config folder:
datasave.message=Multi-module Maven project!
threads.xml.number=5
file.location.on.disk=D:\temp\registry
Then in datasave-module you can use all your properties either through #Value.
SaveXmlService.java:
#Service
public class SaveXmlService {
#Autowired
XmlParseService xmlParseService;
#Value("${file.location.on.disk: none}")
private String fileLocation;
#Value("${threads.xml.number: 3}")
private int numberOfXmlThreads;
...
}
Or through ServiceProperties:
Service.java:
#Component
public class Service {
#Autowired
ServiceProperties serviceProperties;
public String message() {
return serviceProperties.getMessage();
}
}
I had this situation before, I noticed that the properties file was not copied to the jar.
I made the following to get it working:
In the resources folder, I have created a unique package, then stored my application.properties file inside it. e.g: com/company/project
In the configuration file e.g: TMDBConfig.java I have referenced the full path of my .properties file:
#Configuration
#PropertySource("classpath:/com/company/project/application.properties")
public class AwsConfig
Build and run, it will work like magic.
You could autowire and use the Enviornment bean to read the property
#Configuration
#PropertySource(value = "classpath:tmdb.properties")
public class TMDbConfig {
#Autowired
private Environment env;
public String getApiKey() {
return env.getRequiredProperty("moviedb.tmdb.api-key");
}
}
This should guarantee that property is read from the context when you invoke the getApiKey() method regardless of when the #Value expression is resolved by PropertySourcesPlaceholderConfigurer.
I am working on a Spring Boot application wherein I am using that application to expose a SOAP webservice. I am using Apache CFX framework for SOAP impl in Spring boot app. I am using Annotation based approach.
I am facing issue in setting the Application Context from the Spring Boot Configuration file in one of the Beans. Below is my code.
#SpringBootApplication
#ComponentScan("com.test")
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
The configuration file is as below.
#Configuration
public class WebServiceConfiguration {
//All individual bean definitions should go here
#Autowired
ApplicationContext appContext;
#Bean
public ServletRegistrationBean cxfServlet() {
return new ServletRegistrationBean(new CXFServlet(), "/soap-api/*");
}
#Bean(name = Bus.DEFAULT_BUS_ID)
public SpringBus springBus() {
return new SpringBus();
}
#Bean(name="IValidator")
public IValidator getValidator(){
return new Validator();
}
#Bean(name="SOAPprocessImpl")
public IPSoap getService() {
return new SOAPprocessImpl();
}
#Bean
public Endpoint endpoint() {
EndpointImpl endpoint = new EndpointImpl(springBus(), getService());
endpoint.publish("/WS_1.0");
endpoint.setWsdlLocation("process.wsdl");
return endpoint;
}
Now I have the bean SOAPprocessImpl implementation in which I need to get the Application Context so that I can get handle to the Validator bean. I have declared SOAPprocessImpl as a bean in the configuraton file. The code is as below
#javax.jws.WebService (endpointInterface="com.test.IPSoap")
public class SOAPprocessImpl implements IPSoap, ApplicationContextAware {
private static ApplicationContext context;
public static ApplicationContext getApplicationContext() {
return context;
}
#Override
public void setApplicationContext(ApplicationContext ac)
throws BeansException {
context = ac;
}
private static final Logger logger = Logger.getLogger(SOAPprocessImpl.class.getName());
private IValidator validator = (IValidator) context.getBean("IValidator"); // context is NULL here
public IRResponse GetBalance(TSSearchParams SearchParams) {
// Some processing logic
}
}
So the issue is that when I run the boot application by deploying to the embedded Tomcat then the Application Context is not getting set in the SOAPprocessImpl class even after implementing the ApplicationContextAware. I also tried Autowiring but that also is not working.
Strangely I tried to see if I can get the ApplicationContext in the Configuration file where all the bean are defined. Here it is getting setting properly.
Can anyone help me how to solve this issue. I am new to Spring Boot and may have missed some configutaion. Thanks in advance.
Option(1): To fix the issue, you need to use #Configuration to register your SOAPprocessImpl bean to the Spring container as shown below so that ApplicationContext object can be injected :
#Configuration
#javax.jws.WebService (endpointInterface="com.test.IPSoap")
public class SOAPprocessImpl implements IPSoap, ApplicationContextAware {
private static ApplicationContext context;
private IValidator validator;
public static ApplicationContext getApplicationContext() {
return context;
}
#Override
public void setApplicationContext(ApplicationContext ac)
throws BeansException {
SOAPprocessImpl.context = ac;
}
#PostConstruct//use PostConstruct
public void init() {
validator = (IValidator) context.getBean("IValidator");
}
//add your current code
}
The important point is that you can't use the context object until the bean is prepared by the container, so you need to use #PostConstruct method as shown above to initialise your variables.
Option2 (recommended):
The best approach is that you can use #Autowired to inject IValidator object into SOAPprocessImpl as shown below so that you don't need your SOAPprocessImpl bean to be aware of ApplicationContextAware. Spring container will inject the instance for the implementation provided for the IValidator class (provided it is under the packages of #Componentscan).
#Component
#javax.jws.WebService (endpointInterface="com.test.IPSoap")
public class SOAPprocessImpl implements IPSoap {
private static final Logger logger = Logger.getLogger(SOAPprocessImpl.class.getName());
#Autowired //spring directly injects this object
private IValidator validator;
public IRResponse GetBalance(TSSearchParams SearchParams) {
// Some processing logic
}
}
I am new to Spring & WebService and trying a few guides on Spring.io.
I planned to create a basic RESTful WebService which consumes Google Direction API and returns just the status.
Here are the classes:
Resource
#JsonIgnoreProperties(ignoreUnknown=true)
public class Direction {
// getters & setters
public Direction() {
super();
}
private String status;
public String toString() {
return status;
}
}
Controller
#Controller
public class Consumer {
public Consumer() {
super();
}
#Resource
private String url;
#Resource
private RestTemplate client;
#Resource
private String apiKey;
#RequestMapping(value = "/directions", method=RequestMethod.GET)
public #ResponseBody Direction consume(#RequestParam(value="source") String source, #RequestParam(value="destination") String destination) {
return consumeDirections(buildURI(source, destination));
}
// Builds URI
private String buildURI(...) {
...
}
private Direction consumeDirections(final String requestURI) {
return client.getForObject(requestURI, Direction.class);
}
}
Configuration v1
#Configuration
#ComponentScan
#EnableAutoConfiguration
public class Application {
public static void main(String[] args) {
SpringApplication.run(Consumer.class, args);
}
}
Springconfig
http://pastebin.com/dsNVBWQq
Spring returns that No qualifying bean of type [java.lang.String] found for dependency.
This happens for all the beans in Consumer.
However, this works Configuration v2
#Configuration
#ComponentScan
#EnableAutoConfiguration
public class Application {
#Resource
private Consumer consumer;
public void execute() {
System.out.println(consumer.consume("x", "z"));
}
public static void main(String[] args) {
ConfigurableApplicationContext context = new ClassPathXmlApplicationContext("application-config.xml");
context.getBean(Application.class).execute();
}
}
Some observations
#Resouce(Explicitly define bean) doesnt work for v1
SpringApplication is not aware of the Springconfig and fails during bean instantiation
I would like to understand why this issue crops up and how to resolve it?
The reason is very easy, the xml config is not loaded. have a look at Spring-Boot: XML Config
if you don't wanna touch existing xml, you need another #configuration annotated class and #ImportResource to load the xml configuration, just like the document says.
IMO, you don't need apiKey and url in the config, you should annotate them with #value, and define them in a .properties file. There are also default settings of spring boot, you get take advantage of it. like, name the properities application.properities and put it on classpath, spring boot will load it automatically.