Spring boot - executable war also deployable to app server - spring

Let's say I have a spring boot web application - It is runnable via gradle (embedded tomcat).
But I need it also to be possible to deploy war in standard way into app server.
How the app should be configured? Standard web.xml along with xml configuration?
Currently I have something like:
#SpringBootApplication
public class MyApplication extends SpringBootServletInitializer {
public static void main(String[] args) {
System.setProperty("spring.profiles.active", "dev");
SpringApplication.run(MyApplication.class, args);
}
#Override
protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
return application.sources(MyApplication.class);
}
#Configuration
#ConditionalOnWebApplication
public static class WebConfiguration {
#Bean
public ServletListenerRegistrationBean<ServletContextListener> registerClientCookieConfigListener () {
ServletListenerRegistrationBean<ServletContextListener> srb =
new ServletListenerRegistrationBean<>();
srb.setListener(new MyConfigListener());
return srb;
}
#Bean
public ServletListenerRegistrationBean<HttpSessionListener> registerMySessionConfigListener () {
ServletListenerRegistrationBean<HttpSessionListener> srb =
new ServletListenerRegistrationBean<>();
srb.setListener(new MySessionConfigListener());
return srb;
}
#Bean
public FilterRegistrationBean registerLoginFilter() {
FilterRegistrationBean filter = new FilterRegistrationBean(new MyFilter());
filter.setUrlPatterns(Collections.singletonList("/*"));
return filter;
}
#Bean
public ServletRegistrationBean registerSAMLDispatcherServlet() {
ServletRegistrationBean bean = new ServletRegistrationBean(
new DispatcherServlet(), "/test/*");
bean.setLoadOnStartup(1);
return bean;
}
}
}
which is 1:1 mapping to web.xml.
Is it even possible to deploy it to app server without web.xml?

You don't need web.xml to deploy spring boot to standalone tomcat server or any other web server.
spring boot does not rely on xml configurations, it configures an equivalent to the dispatcher servlet automatically.
to deploy a spring boot app to an another server, you need to update your packaging to war in maven
<packaging>war</packaging>
and tell maven that a webserver will be available in the runtime and don't package it with scope provided
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
<scope>provided</scope>
</dependency>
few documentations
https://www.baeldung.com/spring-boot-war-tomcat-deploy
https://www.mkyong.com/spring-boot/spring-boot-deploy-war-file-to-tomcat/

Related

How to set equivalent of web.xml JNDI <env-entry> in a Spring Boot project?

Referring to this SO answer, I'd like to setup the equivalent of this web.xml configuration in a JSF / JoinFaces / SpringBoot application (that doesn't have web.xml).
<env-entry>
<env-entry-name>jsf/ClientSideSecretKey</env-entry-name>
<env-entry-type>java.lang.String</env-entry-type>
<env-entry-value>[AES key in Base64 format]</env-entry-value>
</env-entry>
Any pointers?
If you are using spring boot and embedded tomcat server, you can add <env-entry> programmatically with the following configuration.
#SpringBootApplication
public class DemoApplication extends SpringBootServletInitializer {
#Bean
public TomcatServletWebServerFactory tomcatFactory() {
return new TomcatServletWebServerFactory() {
#Override
protected TomcatWebServer getTomcatWebServer(org.apache.catalina.startup.Tomcat tomcat) {
tomcat.enableNaming();
return super.getTomcatWebServer(tomcat);
}
#Override
protected void postProcessContext(Context context) {
// adding <resource-ref>
ContextResource resource = new ContextResource();
resource.setName("jdbc/myJndiResource");
resource.setType(DataSource.class.getName());
resource.setProperty("driverClassName", "org.postgresql.Driver");
resource.setProperty("url", "jdbc:postgresql://hostname:port/dbname");
resource.setProperty("username", "username");
resource.setProperty("password", "password");
context.getNamingResources()
.addResource(resource);
// adding <env-entry>
ContextEnvironment ce = new ContextEnvironment();
ce.setName("jsf/ClientSideSecretKey");
ce.setType(String.class.getName());
ce.setValue("[AES key in Base64 format]");
context.getNamingResources().addEnvironment(ce);
}
};
}
public static void main(String[] args) throws NamingException {
SpringApplication.run(DemoApplication.class, args);
}
}
Once defined the jndi naming resources they can be accessed in your application using JndiTemplate of InitialContext.
JndiTemplate jndiTemplate = new JndiTemplate();
String str = (String) jndiTemplate.lookup("java:comp/env/jsf/ClientSideSecretKey");
Hope this helps you in resolving your problem.
Essentially <env-entry> declares a web application context attribute.
You can initialize your servlet context and provide the equivalent servlet context attributes in your Spring Boot application.
For that purpose, you can register a bean that implements the ServletContextInitializer interface (or WebApplicationInitializer if your app has to be deployed in a traditional servlet container). For example:
public class JsfServletContextInitializer implements ServletContextInitializer {
#Override
public void onStartup(ServletContext servletContext) throws ServletException {
servletContext.setAttribute("jsf/ClientSideSecretKey", "[AES key in Base64 format]");
}
}
Do not forget to register it as a bean in your configuration.

How to use load time weaving without -javaagent?

I am trying to enable loadtimeweaving without javaagent jar files of aspectweaver and spring-instrument. This what I have implemented to achieve the same But it's not working.
#ComponentScan("com.myapplication")
#EnableAspectJAutoProxy
#EnableSpringConfigured
#EnableLoadTimeWeaving(aspectjWeaving = EnableLoadTimeWeaving.AspectJWeaving.AUTODETECT)
public class AopConfig implements LoadTimeWeavingConfigurer {
#Override
public LoadTimeWeaver getLoadTimeWeaver() {
return new ReflectiveLoadTimeWeaver();
}
/**
* Makes the aspect a Spring bean, eligible for receiving autowired components.
*/
#Bean
public InstrumentationLoadTimeWeaver loadTimeWeaver() throws Throwable {
InstrumentationLoadTimeWeaver loadTimeWeaver = new InstrumentationLoadTimeWeaver();
return loadTimeWeaver;
}
}
A workaround I found was to hot-attach InstrumentationSavingAgent from spring-instrument instead of starting the agent via -javaagent command line parameter. But for that you need an Instrumentation instance. I just used the tiny helper library byte-buddy-helper (works independently of ByteBuddy, don't worry) which can do just that. Make sure that in Java 9+ JVMs the Attach API is activated if for some reason this is not working.
So get rid of implements LoadTimeWeavingConfigurer and the two factory methods in your configuration class and just do it like this:
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-instrument</artifactId>
</dependency>
<dependency>
<groupId>net.bytebuddy</groupId>
<artifactId>byte-buddy-agent</artifactId>
<version>1.10.14</version>
</dependency>
#SpringBootApplication
public class Application {
public static void main(String[] args) {
Instrumentation instrumentation = ByteBuddyAgent.install();
InstrumentationSavingAgent.premain("", instrumentation);
ConfigurableApplicationContext context = SpringApplication.run(Application.class, args);
// ...
}
}
Feel free to ask follow-up questions if there is anything you do not understand.
Update: One more thing I noticed is that this only works for me with aspectjWeaving = ENABLED, not with AUTODETECT. And for one sample Spring bean I noticed that #Component did not work, probably because of some bootstrapping issue between Spring vs AspectJ. Hence, I replaced it by an explicit #Bean configuration, then it worked. Something like this:
#Configuration
#ComponentScan("com.spring.aspect.dynamicflow")
#EnableLoadTimeWeaving(aspectjWeaving = ENABLED)
public class ApplicationConfig {
#Bean
public JobProcess jobProcess() {
return new JobProcessImpl();
}
}

Run Spring Batch (JSR-352) application on Spring Boot

I have a simple Spring Batch application complying with JSR-352.
I need to deploy this as a managed Task on Spring Cloud Data Flow server. As far as I know - to be able to deploy this as a Task I need to convert this application as a Spring Boot app.
I have tried to add Spring Boot dependencies and Main class however it is not running the Batch job when I start the app.
Main Class
#SpringBootConfiguration
#EnableAutoConfiguration
#EnableBatchProcessing
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
Batch File created at
META-INF/batch-jobs/myjob.xml
It works when I use JobOperator in the main class to start the job (without Spring Boot).
What am I missing to run this as a Spring Boot app?
You're missing #EnableTask annotation. With that, your batch-job will be run as a short-lived application. In other words, the application will run as long as the business logic in your XML needs to run, and it will gracefully shut down and free-up resources.
Please clone and try out the Spring Cloud Task samples [see: BatchJobApplication]. All of them should work as-is in SCDF as well.
#EnableBatchProcessing
#SpringBootApplication
public class BatchApplication {
public static void main(String[] args) {
SpringApplication.run(BatchApplication.class, args);
}
#Bean
public CommandLineRunner run(JobOperator jobOperator) {
return $ -> jobOperator.start("myjob", new Properties());
}
#Bean
JobParametersConverter jobParametersConverter(DataSource dataSource) {
return new JsrJobParametersConverter(dataSource);
}
#Bean
JobOperator jsrJobOperator(ApplicationContext applicationContext, JobExplorer jobExplorer,
JobRepository jobRepository, JobParametersConverter jobParametersConverter,
PlatformTransactionManager transactionManager) {
JsrJobOperator jobOperator = new JsrJobOperator(jobExplorer, jobRepository, jobParametersConverter,
transactionManager);
jobOperator.setApplicationContext(applicationContext);
jobOperator.setTaskExecutor(new SimpleAsyncTaskExecutor());
return jobOperator;
}
}
https://gist.github.com/rixwwd/8091a717ca24fd810ff71b4fdebbf9cc

Ribbon MaxAutoRetries properties is not working

I've set a couple of retry configurations in my application.properties file. However, none of them is working when I ran the ribbon application.
//this is my service
#RestController
#SpringBootApplication
public class HelloApplication {
#Value("${server.port}")
private int port;
public static void main(String[] args) {
SpringApplication.run(HelloApplication .class, args);
}
#GetMapping(value="/app")
public String notification() {
return "This Is HelloService running on port:"+ port;
}
}
Here is my RibbonAppApplication class:
#SpringBootApplication(scanBasePackages={"com.netflix.client.config.IClientConfig"})
#RestController
#RibbonClient(name= "hello", configuration=RibbonConfig.class )
public class RibbonAppApplication {
#Autowired
private RestTemplate restTemplate;
public static void main(String[] args) {
SpringApplication.run(RibbonAppApplication.class, args);
}
#GetMapping
public String getService() {
return restTemplate.getForObject("http://hello/app",String.class);
}
#Bean
#LoadBalanced
public RestTemplate restTemplate() {
return new RestTemplate();
}
}
This is the application.properties for the RibbonAppApplication:
ribbon.eureka.enabled=false
eureka.client.register-with-eureka=false
eureka.client.fetch-registry=false
hello.ribbon.listOfServers=http://localhost:1111, http://localhost:2222
hello.ribbon.OkToRetryOnAllOperations=false
hello.ribbon.MaxAutoRetries=0
hello.ribbon.MaxAutoRetriesNextServer=1
Thank you guys so much for helping!
Missing dependency of Sprint Retry is almost always the reason for Ribbon not able to retry. Spring Retry a dependency for retry functionality for Zuul/Ribbon.
When a request fails, you may want to have the request be retried automatically. To do so when using Sping Cloud Netflix, you need to include Spring Retry on your application’s classpath. When Spring Retry is present, load-balanced RestTemplates, Feign, and Zuul automatically retry any failed requests (assuming your configuration allows doing so)
Adding Spring Retry to pom.xml should fix this.
Related docs: https://cloud.spring.io/spring-cloud-netflix/multi/multi_retrying-failed-requests.html
You have to add the spring-retry dependency to your pom.xml file:
<!-- https://mvnrepository.com/artifact/org.springframework.retry/spring-retry -->
<dependency>
<groupId>org.springframework.retry</groupId>
<artifactId>spring-retry</artifactId>
<version>1.2.4.RELEASE</version>
</dependency>

Spring Boot: Web.xml and embedded server jar

I'm trying to convert a legacy spring-mvc app to Spring boot (in order to have a self contained JAR enabling easier upgrade to Java-8).
I see no reason to use replace my existing web.xml file with code as the code looks like configuration and web.xml is more established.
Is it possible to use my existing web.xml in a Spring Boot application (in embedded JAR mode)?
Edit: I also want to avoid using #EnableAutoConfiguration
Thanks
ok, thanks to Mecon, I'm slightly closer. I had to remove the ContextLoaderListener in the web.xml; also had to import the xml Spring config even though it was referenced in the contextConfigLocation.
#Configuration
#ComponentScan
#EnableAutoConfiguration
#ImportResource(value = {"classpath:/webapp-base.xml"})
public class WebApp {
#Autowired
private ServerProperties serverProperties;
#Autowired
private MediaConfiguration mediaConfig;
#Bean
public EmbeddedServletContainerFactory servletContainer() {
JettyEmbeddedServletContainerFactory factory = new JettyEmbeddedServletContainerFactory();
factory.setContextPath(serverProperties.getContextPath());
factory.addConfigurations(new WebXmlConfiguration());
factory.addServerCustomizers(server -> {
List<Handler> resourceHandlers = getResourceHandlers();
Handler original = server.getHandler();
HandlerList handlerList = new HandlerList();
Handler[] array = getHandlers(original, resourceHandlers);
handlerList.setHandlers(array);
server.setHandler(handlerList);
}
);
return factory;
}
private List<Handler> getResourceHandlers() {
return mediaConfig.getMappings().stream().map(m -> {
ContextHandler contextHandler = new ContextHandler(m.getUrlpath());
ResourceHandler resourceHandler = new ResourceHandler();
resourceHandler.setResourceBase(m.getFilepath());
contextHandler.setHandler(resourceHandler);
return contextHandler;
}).collect(Collectors.toList());
}
private Handler[] getHandlers(Handler original, List<Handler> resourceHandlers) {
ArrayList<Handler> handlers = new ArrayList<>();
handlers.add(original);
handlers.addAll(resourceHandlers);
return handlers.toArray(new Handler[resourceHandlers.size()+1]);
}
public static void main(String[] args) {
SpringApplication.run(WebApp.class, args);
}
}
You don't need Spring-Boot to have a self-contained JAR, all you really need is Embedded Tomcat, or Jetty.
Create a class with public static void main(String[] a), and this Class will be used when the Jar is "executed" by java -jar myWebapp.jar command.
In the main method, you can fire up the Embedded Tomcat or Jetty, and make it load your webapp by referring to existing web.xml.

Resources