Adding static files at runtime in Spring - java

I have a Java Spring Application and I need to serve some static content, so I use the well documented approach as described in e.g. https://www.baeldung.com/spring-mvc-static-resources. I create a folder src/main/resources/static, I add a file test.json. I build and run the app, and I can access localhost:8080/test.json
But if I want to add another file, e.g. test2.json I need to restart the server in order for the app to serve it.
Is there a way to "force a new file search" and circumvent the caching so that I can add files at runtime without writing a new Controller?
Performance doesn't matter to me in this case.

My recommendation would be to add your dynamic files to the a location on the filesystem, and then add a custom resource handler.
#Configuration
public class WebConfig implements WebMvcConfigurer {
#Override
public void addResourceHandlers(ResourceHandlerRegistry resourceHandlerRegistry) {
resourceHandlerRegistry.addResourceHandler("/fs_resources/**")
.addResourceLocations("file:///my/path/where/i/keep/stuff")
.setCachePeriod(0);
}
}

Related

Default resource handler for an URL path serving a static file in Spring Boot MVC

I have a Spring Boot-based project (based on spring-boot-starter-parent and spring-boot-starter-web in which I want to both serve static content (built in another module, and copied into the project with maven-resources-plugin).
With the default configuration/conventions, it looks for static content in misc. places, like classpath:/static and classpath:/public, which is useful if the static content is to be served from the root URL, which for this project is not the case.
I want to serve it off /web/ which I have successfully done by deriving from WebMvcConfigurer (and annotated the class with #Configuration) and overridden the public void addResourceHandlers(ResourceHandlerRegistry registry) method; registry.addResourceHandler("/web/**").addResourceLocations("classpath:/static/web/");
This works when explicitly referring to the file names in the /web/-folder, but I can't figure out how I can emulate the "default document" behavior, e.g. index.html should be served for /web/. If you drop off an index.html in one of the root directories for static content, it will be handled by the WelcomePageHandlerMapping.
Found the solution; I also had to override the addViewControllers() to create an explicit mapping and fix the precedence:
#Override
public void addViewControllers(ViewControllerRegistry registry) {
registry.addViewController("/web/").setViewName("index.html");
registry.setOrder(Ordered.HIGHEST_PRECEDENCE);
}

How to access a property in .properties file during or before the main() method in Spring?

We have converted an existing web app over to Spring Boot.
All I want to to do is be able to use #Profile("production"). We store that in myProps.properties in the resources folder.
I can access the .properties file all over the app just fine using #Value, but it seems in order to get the #Profile to work, you have to set the active profiles before the ServletContext is set/final which seems to happen during the main() method.
I've tried dozens of things and failed. I've found you can't set a static field with #Value in the main application class. I've tried making a class that implements WebApplicationInitializer like https://www.baeldung.com/spring-profiles but my onStartup() method that I override doesn't ever get run.. I put in a break point and it never gets hit. The only way I can get a break point to hit is if I #Autowire the servletContext on that method, but then the context is already set and cannot be altered.
I've tried AnnotationConfigWebApplicationContext, I've tried ServletContainerInitializer, I've tried ConfigurableEnvironment, etc. I feel like I'm going in circles.
I feel like there is a big piece I'm missing here in order to do things the "Spring way". Can anyone offer Java annotation-configured way for me to get a property and set the active profiles for using later in the product? Thanks.
Why do you need it before main? Why don't you just create multiple properties files and define the profile in the top-level property file?
(parent) application.properties
spring.profiles.active = development-application.properties
(profile) development-application.properties
my.value = foo
Based on your comments, I suggest something like the following as a solution:
Define your tasks as beans with #Components and annotate them with #Profile to divide them up by the environment you want them to be used in (I prefer setting profiles as environment variables).
#Component
#Profile("production")
public class TaskA implements Task {
public void doWork(){}
}
#Component
#Profile("staging")
public class TaskB implements Task {
public void doWork(){}
}
Marking these classes with #Component means Spring will manage them and they can be injected, perhaps into a #Component that executes the tasks:
#Component
public class TaskDoer {
private List<Task> tasksToDo;
#Inject
public TaskDoer(List<Task> tasksToDo) {
this.tasksToDo = tasksToDo;
}
}
When deploying your app, set the profile to use in the environment variables in whatever way is appropriate for your setup:
SPRING_PROFILES_ACTIVE = production, someOtherProfile
Now, when the application starts, Spring will see that the active profiles are "production" and "someOtherProfile" (multiple profiles can be set) and will not load any beans with an #Profile that aren't for that profile.

Serve dynamically changing static content with Spring Boot

Right now I have a simple Spring Boot application that serves static images that I have placed in resources/static/img. This works just fine for displaying the actual content, but there are two things I'd like to fix:
I don't want any of these images to be bundled with the resulting .jar file, and I know that placing these images in the resources folder will do that.
Using my current setup, in order to see a new image on the webapp, I have to add it to the folder and restart it. I would instead like Spring to serve any static content that exists in a specific folder, so I can add images while the application is running and have them automatically served at localhost:8080/img/{image name}.
I attempted to solve this by setting up a resource handler, but I'm not sure if this is any different than simply serving them from resources/static. Unfortunately, I'm still struggling with getting this configured correctly, as I'm unable to see any images at all. Here's what I tried:
#Configuration
public class StaticResourceConfiguration extends WebMvcConfigurerAdapter {
#Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/img/**").addResourceLocations("file:" + Application.IMAGE_DIR);
super.addResourceHandlers(registry);
}
}
And here is my Application configuration:
#SpringBootApplication
public class Application extends SpringBootServletInitializer {
static String IMAGE_DIR;
#Override
protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
return application.sources(Application.class);
}
public static void main(String[] args) throws IOException {
SpringApplication.run(Application.class, args);
IMAGE_DIR = new File(".").getCanonicalPath() + "/img/";
}
}
Again, my goal is to set up a folder in the root of my project called img which will store the images that I would like the webapp to serve on localhost:8080/img/{image name}. If possible, I'd like to be able to add content to this folder while the application is running and have Spring automatically serve them without having to restart it.
Does anyone have any ideas? Thanks in advance.
the problem with your approach is that you set IMAGE_DIR after spring boot application is ran and the constant IMAGE_DIR is not initialized and is null. Change it as follows:
public static void main(String[] args) throws IOException {
IMAGE_DIR = "/opt/img/";
SpringApplication.run(Application.class, args);
}
and remove all File(".").getCanonicalPath() related stuff and it will work. Your approach will fit in your needs when you have new image in selected directory it can be served.
A good solution for serving dynamic content with spring boot is to link a static content directory to a symbolic link with no cache and after that, you just need to rebuild symlink every time you need to reload. So in your case:
#Configuration
public class StaticResourceConfiguration extends WebMvcConfigurerAdapter {
#Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/img/**").addResourceLocations("file:/tmp/images/").setCacheControl(CacheControl.noCache());
super.addResourceHandlers(registry);
}
}
After that, you change your image directory like this:
Path link = Paths.get("/tmp/images"); //Symlink
Files.deleteIfExists(link);
Files.createSymbolicLink(link, Paths.get(your_new_images_directory));
The choice of symlink /tmp/images is personal, you can choose what you want. Pay attention, your application needs to have good administrator rights for symlink creation.
to solve the problem with your point 2.
Using my current setup, in order to see a new image on the webapp, I have to add it to the folder and restart it. I would instead like
Spring to serve any static content that exists in a specific folder,
so I can add images while the application is running and have them
automatically served at localhost:8080/img/{image name}.
You can create all those images under:
/resources/public
-img
-js,...
And to access just localhost:8080/img/yourImage.png
Everything under "public" folder will be accessible

How do you handle configuration on spring framework when developing custom libraries intended to be consumed by other applications?

I'm working on building a custom "event" library that encapsulates the technical details of an event buffer we are planning to share with multiple consumers. Ideally, we want this library to use the spring framework (note: not spring boot), and be environmentally aware. Something I am not groking from the current docs is how to make the library environmentally aware.
For example, we want to include with the library a static configuration for the queue end points the library will publish / consume from; however, we want to enable "overriding" these queues when in the development or integration environments. Ideally, I do not want to make multiple builds that swap out what the config file is, but include them all and know to read the "right" one.
Some of the things I am not understanding;
How to pass in a "profile" when debugging (it seems the Environment object won't honor the -Dspring.active.profiles property).
How to structure the #Configuration classes so that you do not hard code #Profile(prod).
Total Spring n00b, thanks in advance!
---UPDATE: Trying to provide a more concrete example.
So I have create a basic configuration class to hold the details that would be populated by configuration files:
#Configuration
public class EventConfiguration implements EnvironmentAware{
private static Environment env = null;
#Value("${events.queue1}")
private String queue1;
#Value("${events.queue2}")
private String queue2;
#Bean
public EventDispatcher eventDispatcher() {
return new EventDispatcher(this);
}
#Override
public void setEnvironment(Environment environment) {
env = environment;
}
... getters and setters
Essentially I want to either go the yaml approach and define the queues by environment "dev", "integration", "prod"; or have 3 different files following the application-{env}.properties convention.
Then, to help me understand how this works, I threw together a quick test so I can inspect the configuration / environment:
#Test
public void testContext() {
AnnotationConfigApplicationContext ctx =
new AnnotationConfigApplicationContext();
Environment env = ctx.getEnvironment();
ctx.scan("com...events");
ctx.refresh();
EventDispatcher dispatcher = ctx.getBean(EventDispatcher.class);
}
I started the debugger with a -Dspring.profiles.active=dev, after having created an application-dev.profile available on the class path.
Am I on the right track? Seems weird to have to have that type of boiler plate code to instantiate the objects, plus it didnt work. The Environment object only showed "default" as the active profile.

Adding external static files (css, js, png ...) in spring boot

Background
I have a spring boot application which has the logo.png file added to the static folder of the resource file, which is eventually built into the jar file which is used in the execution.
This jar application need to be run in multiple instances for different clients. So what I did is create an external application.properties file which distinguish the settings for each users.
http://docs.spring.io/spring-boot/docs/current/reference/html/boot-features-external-config.html
Problem
But the problem is, i need to change the logo of each instance of my application. I cannot embed the customer logos into my application jar. Rather I need to keep it external like my application.properties.
For the moment, what I have done is check for the file logo.png in the same folder of jar of execution, and if excist, read the file, get base64 data and show it in the img tag.
But I want this to be done in a proper way as static content. I need the static content to be externalized. so I can let each customer have a specific instance of the jar running with different static resource content
For example. I need to keep the external static files as below and access from the urls in my view href or src attributes of the html tags.
Summary
Required folder structure
+ runtime
- myapp-0.1.0.jar
- application.properties
+ static
- logo.png
Should be able to access
<img th:src="#{/logo.png}" />
You can use resource handlers to serve external files - e.g.
#Component
class WebConfigurer extends WebMvcConfigurerAdapter {
#Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/ext/**").addResourceLocations("file:///yourPath/static/");
}
}
The WebMvcConfigurerAdapter is deprecated. As from Spring Boot 2.x you could use WebMvcConfigurer instead.
#Configuration
public class MediaPathConfig {
// I assign filePath and pathPatterns using #Value annotation
private String filePath = "/ext/**";
private String pathPatterns = "/your/static/path/";
#Bean
public WebMvcConfigurer webMvcConfigurerAdapter() {
return new WebMvcConfigurer() {
#Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
if (!registry.hasMappingForPattern(pathPatterns)) {
registry.addResourceHandler(pathPatterns)
.addResourceLocations("file:" + filePath);
}
}
};
}
}

Categories

Resources