Is it possible to add a context to Spring Boot's embedded tomcat that can access the filesystem?
I'm trying to get the same functionality as adding <Context docBase="/path/to/my/files" path="/MyFiles"/> where the context is used for directly viewing/downloading files.
In Spring Boot I've managed to add a context with the following:
#Bean
public ServletWebServerFactory factory() {
return new TomcatServletWebServerFactory() {
#Override
protected TomcatWebServer getTomcatWebServer(Tomcat tomcat) {
tomcat.addContext("/MyFiles", "/path/to/my/files");
return super.getTomcatWebServer(tomcat);
}
};
}
However this context is only created/restricted to where Spring Boot puts the running tomcat in /private/var/folders/zy/T. Therefore any requests to that context just return a 404 because the file I'm asking for doesn't exists in /private/var...etc.
Is there any way to create the Tomcat context so that it has access to files elsewhere on the system?
Related
I have an application which allows to dynamically generate web applications (wars) and I would like to deploy these applications in a server to test them and I think of putting them in the same embedded server of spring, here is how I solved the problem with a simple main java.
public class Main {
private final static Logger logger = LoggerFactory.getLogger(Main.class);
private final static File catalinaHome = new File(
"C:\\Users\\Dev\\Desktop\\demo\\userstory-2\\compiler\\patternHost");
private static Tomcat tomcat = null;
public static void main(String[] args) {
tomcat = new Tomcat();
tomcat.setPort(8080);
tomcat.setBaseDir(catalinaHome.getAbsolutePath());
tomcat.getHost().setAutoDeploy(true);
tomcat.getHost().setDeployOnStartup(true);
tomcat.getServer().addLifecycleListener(new VersionLoggerListener());
tomcat.getHost().addLifecycleListener(new HostConfig());
try {
tomcat.start();
} catch (LifecycleException e) {
logger.error("Tomcat could not be started.");
e.printStackTrace();
}
logger.info("Tomcat started on " + tomcat.getHost());
tomcat.getServer().await();
}
}
How can I do the same with spring boot. ?
I have converted a non-spring app to spring boot in this way and it worked for me. I was able to run it with spring boot embedded tomcat. Hope this helps.
Spring boot is all about speed, it comes with embedded-tomcat server(provided you use spring-boot-starter-web dependency) and now all you need is java to run your standalone spring boot application. It reduces the manual steps of copying war file to tomcat's webapp folder and then starting it.
Try the approach which suits your app.
If your old app is spring based :
Create a new spring boot starter web project and copy your old source code to this new project. Modify application.properties, resources folder, add all required dependencies in pom.xml file and change package as war.
Do a mvn clean install it will generate a war file (with embedded
tomcat) in target folder of your project's root directory. Now to run it all you
need to do is, in your target folder open terminal and run java -jar your_warFileName.war it will start the application.
If your old app is not a spring based:
Again start with new spring boot starter web project and copy your source code but then to use your old code with spring-boot, first you have to do clean-up stuff like adding #RestController to controller classes, declaring beans by marking classes with #Service or #Component and autowiring beans in the appropriate places. Once your code compiles fine then to run it your can use step 2 as above.
Here i have a bean initialized InitializingBean in Spring MVC application. In order to register in spring-servlet application context, i two lines of code below:
#Autowired
private ApplicationContext context;
#Override
public void afterPropertiesSet() throws Exception {
if (context.getParent() != null) { // debug mode and add a breakpoint here, four times came here while application start up
}
}
Obviously, if four root application context initialized, some where error. but i really don't know why?
Here i make some assumption:
contextConfigLocation in web.xml file is classpath*:, and tomcat may detect multiple applicationContext.xml files under classpath jar files.(but spring xml files are really not locate in such jar files)
there are some configuration class in jar files and who make spring mvc application scan the same jar file twice or more(but i did not find such class)
Maybe i'm totally error, hope someone can give me some hint and thanks.
when starting your tomcat,the tags in your web.xml ,specificly,your codes will detect the configuration you wrote,initilized the spring container,such as,Application-Context.xml,if there is any tag in this file , the Spring will add the file you write, for example, you write "Application-context-data.xml" in your tag,the Spring container will initilized it !
Can't get my spring boot application running on AWS instance.It works fine on my machine but it looks like autowiring resolves correctly in one environment but not in another.Looks like I need to clean up configuration classes a bit.Any ideas here? Thanks much.
**Main class:**
#Configuration
#EnableAutoConfiguration
#EnableConfigurationProperties
#ComponentScan
public class Data {
public static void main(String[] args) throws Exception {
SpringApplication.run(Data.class, args);
}
}
Configuration:
#EnableWebMvc
#Configuration
public class AquilaDataWebConfig extends WebMvcConfigurerAdapter {
#Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/charts/**").addResourceLocations(
"file:///var/lib/aquila/");
registry.addResourceHandler("/**").addResourceLocations(
"classpath:/static/");
}
#Override
public void addViewControllers(ViewControllerRegistry registry) {
registry.addViewController("/").setViewName("forward:/index.html");
}
#Bean
public InternalResourceViewResolver defaultViewResolver() {
// Need this so we can forward to index.html.
return new InternalResourceViewResolver();
}
}
Exception:
Error starting ApplicationContext. To display the auto-configuration report enabled debug logging (start with --debug)
2015-07-09 19:30:55.773 ERROR 18723 [main] --- o.s.boot.SpringApplication : Application startup failed
org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'defaultServletHandlerMapping' defined in class path resource [org/springframework/web/servlet/config/annotation/DelegatingWebMvcConfiguration.class]: Instantiation of bean failed; nested exception is org.springframework.beans.factory.BeanDefinitionStoreException: Factory method [public org.springframework.web.servlet.HandlerMapping org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport.defaultServletHandlerMapping()] threw exception; nested exception is java.lang.IllegalArgumentException: A ServletContext is required to configure default servlet handling
at org.springframework.beans.factory.support.ConstructorResolver.instantiateUsingFactoryMethod(ConstructorResolver.java:601)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.instantiateUsingFactoryMethod(AbstractAutowireCapableBeanFactory.java:1113)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBeanInstance(AbstractAutowireCapableBeanFactory.java:1008)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:505)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:476)
at org.springframework.beans.factory.support.AbstractBeanFactory$1.getObject(AbstractBeanFactory.java:302)
at
In your application.properties (or application.yml), make sure that spring.main.web-environment isn't set to false. This should solve the missing servlet context problem.
You can use Spring Cloud
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-aws-autoconfigure</artifactId>
<version>{spring-cloud-version}</version>
</dependency>
</dependencies>
Spring Cloud AWS also provides dedicated Spring Boot support. Spring Cloud AWS can be configured using Spring Boot properties and will also automatically guess any sensible configuration based on the general setup.
Also here is an example that uses Elastic Beanstalk to deploy on AWS.
There are two differences between the War variant and the Jar variant. The War variant doesn’t need an embedded tomcat because it will be deployed in a tomcat server so the pom.xml has the spring-boot-starter-tomcat dependency set to “provided”. The Jar variant has the scope tags removed to include this dependency in the jar.
org.springframework.boot spring-boot-starter-tomcat provided
The second difference is the ServletInitializer
public class ServletInitializer extends SpringBootServletInitializer {
#Override protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
return application.sources(SpringBootAwsApplication.class);
} }
Now that you have this application created, we need to generate the war file for deployment onto Amazon AWS. Right click on the pom.xml and select Run As->Maven Install. This will run the build and create the war file in the target folder of your application.
Deploy your application using Amazon Elastic Beanstalk
1) Login to Amazon AWS.
2) In the main control panel select Elastic Beanstalk under Deployment & Management.
3) Click on Create Application in the top right corner.
4) Enter the Application Name and click Next.
5) Environment Tier – Web Server
6) Predefined Configuration – Tomcat
7) Environment Type – Single instance
8) Click Next
9) Select Upload your own, click Browse and locate the war you created earlier.
10) When the application is uploaded you will see the next page where you select your URL.
11) Enter a name and click check availability to see if you can use it.
12) Click Next
13) We don’t need a RDB in this example so click next here.
14) In this next step you are defining the EC2 instance that will be created, if you are using a free trial then stick to the free t1.micro instance type.
15) EC2 Key Pair, can be left unselected. You won’t need it for now and most likely you won’t have one configured yet. This will be covered in a later post.
16) Click Next.
17) In Environment Tags click next again because we don’t care about this.
18) Review the configuration, and then click Launch.
I am assuming that you have other #Configuration in your package, which get selected by your #ComponentScan (DelegatingWebMvcConfiguration that appears in your exception is, most likely, imported by #EnableWebMvc somewhere in external #Configuration).
Possible solution is to use a filter in your component scan.
#ComponentScan( excludeFilters = { #Filter(type = FilterType.ANNOTATION, value = Configuration.class) })
also if possible include basePackages = { "org.yourpackage" } in your #ComponentScan
If possible also try to include for better performance
#Override
public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {
configurer.enable();
}
It tells Spring to use the container's default servlet for certain requests, like for static resources.
I have a spring-boot application which I want to run with external configuration file.
When I run it as jar (with embedded servlet container), everything is fine.
But I want to run it under external servlet container (Tomcat) and here i have problem with external configuration. I have tried a #PropertySource, but in this case application gets only properties absent in war file configuration: external configuration doesn't override internal configuration.
So the question: how can I configure external configuration which will override internal configuration?
You're probably using external configuration in the form of application.properties in the current directory when you're running your application as a jar. However, "current directory" isn't very useful when deploying as a war in an external tomcat. Even if you find out what the current directory is, it's most likely the same location for all applications running in that tomcat, so when you're running more than one application, that's not going to work very well.
What we do here is this declare two PropertySources on our application:
#PropertySources({#PropertySource(value={"classpath:internal.properties"}), #PropertySource(value={"file:${application.properties}"})})
internal.properties contains "built in" default values for propeties. The second PropertySource is a file containing external configuration. Note how the name of the file is itself a property.
We define this externally in the Context element of our application (in tomcat):
<Context docBase="/path/to/your/war/your.war">
<Parameter name="application.properties" value="/path/to/your/properties/application.properties"/>
</Context>
This allows you to have multiple applications running in tomcat, each application using it's own external properties file. You can even have multiple instances of the same application running with different properties.
Spring Boot offer many ways to specify the location of your properties, it´s not needed to modify your sources.
Yo can define the spring.config.location value for example:
In your tomcat/conf/Catalina/<host> context descriptors:
<Context>
<Parameter name="spring.config.location" value="/path/to/application.properties" />
</Context>
As a JVM parameter in your tomcat setenv.sh file:
-Dspring.config.location=/path/to/application.properties
As a SPRING_CONFIG_LOCATION environment variable.
To externalize the Spring Boot application.properties when deploying as a war file you can set spring.config.location at the beginning when Spring Boot application is configured:
public class Application extends SpringBootServletInitializer {
#Override
protected SpringApplicationBuilder configure(SpringApplicationBuilder springApplicationBuilder) {
return springApplicationBuilder
.sources(Application.class)
.properties(getProperties());
}
public static void main(String[] args) {
SpringApplicationBuilder springApplicationBuilder = new SpringApplicationBuilder(Application.class)
.sources(Application.class)
.properties(getProperties())
.run(args);
}
static Properties getProperties() {
Properties props = new Properties();
props.put("spring.config.location", "classpath:myapp1/");
return props;
}
For more details check this solution.
You can add configuration files folder to set Classpath line catalina.bat, catalina.sh(which one if you want to use.) or you can add to setenv.bat/sh file. Your config files will be added to war classpath.
For Example;
In Windows env.
set CLASSPATH=D:\app\conf
I'm trying to initialize Spring Security from a main() method in a "fat" executable JAR with Spring Boot and embedded Jetty.
I use Spring Security with Java config (no web.xml). The problem is that embedded Jetty fails to register the springSecurityFilterChain filter.
When I run the same JAR as a WAR in Jetty (mvn jetty:run) it works normally and I see this:
Initializing Spring embedded WebApplicationContext
But when running in embedded Jetty I see no WebApplicationContext getting initialized.
I have this EmbeddedServletContainerFactory:
#Bean
public EmbeddedServletContainerFactory servletContainerFactory() {
JettyEmbeddedServletContainerFactory factory = new JettyEmbeddedServletContainerFactory();
factory.setPort(8080);
factory.addServerCustomizers(new JettyServerCustomizer() {
public void customize(Server server) {
// TODO: INITIALIZE SPRING SECURITY SOMEHOW...
}
});
return factory;
}
I've tried creating a subclass of AbstractSecurityWebApplicationInitializer but it is in conflict with my SpringBootServletInitializer. In my pom.xml I have these dependencies:
spring-boot-starter-jetty
spring-boot-starter-logging
spring-boot-starter
When I add spring-boot-web it throws:
java.lang.IllegalArgumentException: A ServletContext is required to configure default servlet handling
Also I tried to register the DelegatingFilterProxy with Jetty but then it throws:
java.lang.IllegalStateException: No WebApplicationContext found: no ContextLoaderListener registered?
I'm guessing I need to tell Jetty to use WebApplicationContext, but how?
Your setup disables Spring Boot to do its magic. Instead of setting up the container yourself let Spring Boot handle it. You can simply add your JettyServerCustomizer to the configuration as a #Bean. This will execute it and you can do your registration of whatever you need.
This still allows Spring Boot to do its magic and you have registered the additional endpoint y ou need.
Another solution could be to add the servlet as a ServletRegistrationBean to your configuration it will then automatically be added by Spring Boot.