Spring: set custom error message for missing "--spring.config.location" argument - java

I'm writing Spring Boot 3.0.2 application.
My application using external yaml config file:
java -jar app.jar --spring.config.import="file:///config.yml"
# or
java -jar -Dspring.config.import="file:///config.yml" app.jar
If I forget to start app with spring.config.import argument, it crash with error:
org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'hostingController': Unsatisfied dependency expressed through field 'hostingService': Error creating bean with name 'hostingService': Unsatisfied dependency expressed through field 'appConf': Error creating bean with name 'appConf': Injection of autowired dependencies failed
at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredFieldElement.resolveFieldValue(AutowiredAnnotationBeanPostProcessor.java:712)
at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredFieldElement.inject(AutowiredAnnotationBeanPostProcessor.java:692)
at org.springframework.beans.factory.annotation.InjectionMetadata.inject(InjectionMetadata.java:133)
at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor.postProcessProperties(AutowiredAnnotationBeanPostProcessor.java:481)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.populateBean(AbstractAutowireCapableBeanFactory.java:1397)
it's logical. But is there any way how to set custom error message? This exception looks terrible for my users. I want to add some information on how to fix it.
I tried:
package net.example.app;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.data.web.SpringDataWebAutoConfiguration;
#SpringBootApplication(
proxyBeanMethods = false,
exclude=SpringDataWebAutoConfiguration.class,
scanBasePackages="net.example.app")
public class MyApplication {
private static Logger log = LoggerFactory.getLogger(MyApplication.class);
public static void main(String[] args) throws Exception {
boolean springConf = false;
for (String a : args) {
if (a.contains("--spring.config.location")) {
springConf = true;
}
}
if (! springConf) {
log.error("Missing '--spring.config.import' argument.");
}
SpringApplication.run(MyApplication.class, args);
}
}
but it doesn't work.

Related

Micronaut - Create a bean without creating ApplicationContext

I have a Micronaut declarative HTTP client written using #client annotation. I want to call this while starting micronaut app before creating the ApplicationContext itslef.
HttpClient : SampleHttpClient.java
import io.micronaut.http.HttpResponse;
import io.micronaut.http.MediaType;
import io.micronaut.http.annotation.Get;
import io.micronaut.http.annotation.Header;
import io.micronaut.http.client.annotation.Client;
#Client("http://127.0.0.1:8200")
#Header(name = "X-Vault-Token", value = "hvs.CEGT7cKyMA8wsDbgKZqxC34q")
public interface SampleHttpClient {
#Get(value = "/v1/kv/data/KMS", produces = MediaType.APPLICATION_JSON)
HttpResponse<String> getVaultSecret();
}
Application.java (Main class)
import io.micronaut.context.ApplicationContext;
import io.micronaut.runtime.Micronaut;
public class Application {
public static void main(String[] args) {
// Following code works perfect. I am creating context here. But I dont want to do this
SampleHttpClient client = Micronaut.run(Application.class, args).
getBeansOfType(SampleHttpClient.class).stream().findFirst().get();
System.out.println("Response Body ="+client.getVaultSecret().body());
// How do we get the instance of SampleHttpClient without using Micronaut's dependency
injection process???
}
}
I want to call this while starting micronaut app before creating the
ApplicationContext itslef.
Micronaut doesn't provide a mechanism to support that. You could write your own thing that instantiates the beans, but that is a large undertaking. You would be writing your own bean container.

How to launch CommandLineRunner from Spring Controller

I have a main Spring Boot application in the top-level package that I launch, with different controllers in the children packages:
i.e.
ca.example.batch.MainBatchApplication
ca.example.batch.job1.Job1Controller (/batch/startJob1)
ca.example.batch.job2.Job2Controller (/batch/startJob2)
I am able to start the Spring Batches by going to the URL: http://localhost:8080/batch/startJob1 or http://localhost:8080/batch/startJob2.
However, in another package I have:
ca.example.batch.job3.Job3Controller
ca.example.batch.job3.Job3Application
... which is not a Spring Batch, but a Spring CommandLineRunner. I want to know if there is a way to launch MainBatchApplication without starting that CommandLineRunner automatically, but run it through the controller, i.e. http://localhost:8080/batch/startJob3.
The controller code I am looking at is:
package ca.example.batch.job3;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
#Controller
public class Job3Controller {
#RequestMapping("/batch/startJob3")
public String handle() throws Exception {
Job3Application app = new Job3Application();
Logger logger = LoggerFactory.getLogger(this.getClass());
logger.info("app: " + app);
String args = "";
app.run(args);
return "COMPLETE";
}
}
Job3Application is:
package ca.example.batch.job3;
import static java.lang.System.exit;
import java.util.List;
import javax.sql.DataSource;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.Banner;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.WebApplicationType;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.context.annotation.Import;
import ca.example.batch.common.CommonLibraryReference;
import ca.example.batch.common.domain.WasHost;
import ca.example.batch.common.svc.WasHostService;
#SpringBootApplication
#Import(CommonLibraryReference.class)
public class Job3Application implements CommandLineRunner {
private final Logger logger = LoggerFactory.getLogger(Job3Application.class);
#Autowired
public DataSource dataSource;
#Autowired
public WasHostService wasHostService;
public Job3Application() {
}
public static void main(String[] args) throws Exception {
new SpringApplicationBuilder(Job3Application.class)
.web(WebApplicationType.NONE)
.bannerMode(Banner.Mode.OFF)
.run(args);
}
#Override
public void run(String... strings) throws Exception {
logger.info("Loading data...");
logger.info("wasHostService: " + wasHostService);
List<WasHost> hostList = wasHostService.findAll();
if (!hostList.isEmpty()) {
for (WasHost host : hostList) {
logger.info("hostname: " + host.getHostname());
}
} else {
logger.info("No hosts found in database. Aborting data collection.");
exit(0);
}
}
}
The error I get in the log is:
""2018-07-07 12:56:09 [http-nio-9191-exec-1] INFO o.a.c.c.C.[Tomcat].[localhost].[/] - Initializing Spring FrameworkServlet 'dispatcherServlet'
""2018-07-07 12:56:09 [http-nio-9191-exec-1] INFO c.e.b.job3.Job3Controller - app: ca.example.batch.job3.Job3Application#472d7ac
""2018-07-07 12:56:09 [http-nio-9191-exec-1] INFO c.e.b.job3.Job3Application - Loading data...
""2018-07-07 12:56:09 [http-nio-9191-exec-1] INFO c.e.b.job3.Job3Application - wasHostService: null
""2018-07-07 12:56:09 [http-nio-9191-exec-1] ERROR o.a.c.c.C.[.[.[.[dispatcherServlet] - Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed; nested exception is java.lang.NullPointerException] with root cause
"java.lang.NullPointerException: null
at ca.example.batch.job3.Job3Application.run(Job3Application.java:47)
at ca.example.batch.job3.Job3Controller.handle(Job3Controller.java:21)
... when I launch the controller request.
If this isn't the right way, please advise.
Basically, what I am trying to do is launch a main() from within a controller but use the MainBatchApplication runtime to run it (if that makes sense?). When the program is done, it send the return code back to the controller and shows in the browser.
Thank you,
Joey
ca.example.batch.MainBatchApplication is the main appliction you start.
So it scans all Components in package ca.example.batch. That means it should detect ca.example.batch.job3.Job3Application so you should be able to #Autowireit in the Job3Controller
this should work:
#Controller
public class Job3Controller {
private final Job3Application job3Application;
public Job3Controller (Job3Application job3Application){
this.job3Application = job3Application;
}
#RequestMapping("/batch/startJob3")
public String handle() throws Exception {
String[] args = ...
this.job3Application.run(args);
return "COMPLETE";
}
....
}

Custom error pages in Spring Boot 1.4 not picked up when using JSP resolver

I am trying to use my own custom error pages in my Spring Boot 1.4 application. According to documentation, it should be sufficient to place my error pages in src/main/resources/public/error directory (for, example 404.html).
However, I am also using JSP pages in my application and have a resolver for them:
#Override
public void configureViewResolvers(final ViewResolverRegistry registry) {
final UrlBasedViewResolverRegistration resolver = registry.jsp("/WEB-INF/jsp/", ".jsp");
final Map<String, Object> attributes = new HashMap<>();
attributes.put("HASH", hashReader.getHashValue());
attributes.put("Hoker", hookerReader.getHooker());
resolver.attributes(attributes);
}
Whenever I experience an 4xx error, instead of using the custom error page I put in the resources/public/error directory, it tries to load /WEB-INF/jsp/error.jsp.
Is there a way how to force Spring Boot to use its default behavior instead of trying to resolve the error pages to the JSP directory?
here is an example, https://github.com/lenicliu/eg-spring/tree/master/eg-spring-boot/eg-spring-boot-webmvc
i guess u could fix it like this:
package com.lenicliu.spring.boot;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.context.embedded.ConfigurableEmbeddedServletContainer;
import org.springframework.boot.context.embedded.EmbeddedServletContainerCustomizer;
import org.springframework.boot.web.servlet.ErrorPage;
import org.springframework.context.annotation.Bean;
import org.springframework.http.HttpStatus;
#SpringBootApplication
public class Application {
#Bean
public EmbeddedServletContainerCustomizer customizeContainerr() {
return new CustomizedContainer();
}
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
private static class CustomizedContainer implements EmbeddedServletContainerCustomizer {
#Override
public void customize(ConfigurableEmbeddedServletContainer container) {
container.addErrorPages(new ErrorPage(HttpStatus.NOT_FOUND, "/404.html"));
container.addErrorPages(new ErrorPage(HttpStatus.INTERNAL_SERVER_ERROR, "/500.html"));
}
}
}
and u could put 404.html and 500.html into following folders:
src/main/resource/static/500.html
src/main/resource/static/404.html
OR like this:
container.addErrorPages(new ErrorPage(HttpStatus.NOT_FOUND, "/error/404.html"));
container.addErrorPages(new ErrorPage(HttpStatus.INTERNAL_SERVER_ERROR, "/error/500.html"));
and then put them into
src/main/resource/static/error/500.html
src/main/resource/static/error/404.html
reference to http://docs.spring.io/spring-boot/docs/1.4.2.RELEASE/reference/htmlsingle/#boot-features-spring-mvc-static-content
/static or /public or /resources or /META-INF/resources, them are same.
hope to help u :)

JUnit dependencies not being loaded in

I have the following JUnit Test Suite, when I try to load my tests, the dependencies I have autowired in the classes I am testing do not seem to get loaded in, and I get the following error message:
package com.uk.jacob.service;
import static org.junit.Assert.*;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.boot.test.SpringApplicationConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.web.WebAppConfiguration;
import com.uk.jacob.model.Ping;
#RunWith(SpringJUnit4ClassRunner.class)
#SpringApplicationConfiguration(classes = PingerService.class)
#WebAppConfiguration
public class PingerServiceTests {
#Test
public void testPingerServiceReturnsOkWhenServiceIsUp(){
PingerService pingerService = new PingerService();
Ping ping = pingerService.ping("http://devnews.today");
assertEquals(true, ping.ok);
}
#Test
public void testPingerServiceReturnsOkWhenServiceIsDown(){
PingerService pingerService = new PingerService();
Ping ping = pingerService.ping("https://jacob.uk.comz");
assertEquals(false, ping.ok);
}
}
Caused by: org.springframework.beans.factory.BeanCreationException: Could not autowire method: public void com.uk.jacob.service.PingerService.setHttpAdapter(com.uk.jacob.adapter.HttpAdapter); nested exception is org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type [com.uk.jacob.adapter.HttpAdapter] found for dependency: expected at least 1 bean which qualifies as autowire candidate for this dependency. Dependency annotations: {}
PingerService:
package com.uk.jacob.service;
import java.io.IOException;
import java.net.HttpURLConnection;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import com.uk.jacob.adapter.HttpAdapter;
import com.uk.jacob.model.Ping;
#Component
public class PingerService {
HttpAdapter httpAdapter;
public Ping ping(String urlToPing) {
Ping ping = new Ping();
try {
HttpURLConnection connection = httpAdapter.createHttpURLConnection(urlToPing);
if(connectionIsOk(connection)){
ping.ok = true;
}
} catch (Exception e) {
ping.ok = false;
}
return ping;
}
private boolean connectionIsOk(HttpURLConnection connection) throws IOException {
return connection.getResponseCode() == 200;
}
#Autowired
public void setHttpAdapter(HttpAdapter httpAdapter){
this.httpAdapter = httpAdapter;
}
}
HttpAdapter:
package com.uk.jacob.adapter;
import java.io.IOException;
import java.net.HttpURLConnection;
import java.net.URL;
import org.springframework.stereotype.Component;
#Component
public class HttpAdapter {
public HttpURLConnection createHttpURLConnection(String urlToPing) throws IOException{
URL url = new URL(urlToPing);
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
connection.setRequestMethod("GET");
connection.connect();
return connection;
}
}
You are creating your pingerService like
PingerService pingerService = new PingerService();
in the testclass, so its not a spring bean, so spring will not inject nothing there, it wont work.
Instead add the PingerService to your spring configuration :
Annotate it with #Component and put it somewhere where it can be found in the classpath or create it with a #Bean annotated method in a your spring configuration class.
This leads to the second problem :
#SpringApplicationConfiguration(classes = PingerService.class)
You have to provide here a configuration class, and not a single service.
The configuration class must instantiate the spring beans, in your case at least PingerService and HttpAdapter.
Have a look at Spring java config (older version)
regarding your comment : For the config class, would an annotated #SpringBootApplication class be sufficient?
Yes that would be sufficient, if the PingerService and HttpAdapter are located in subpackages of that SpringBootApplication annotated class, so they can be found by the ComponentScan.
A ComponentScan is configured automatically if you use #SpringBootApplication

Log4j in Unit Testing Environment

I am performing JUnit Testing and receiving log4j:WARN No appenders could be found for logger (class .. ) error when I run test class(s).
Log4j properties file is present inside my folder root directory.
This code seems to work but why doesn't log4j picked up automatically.
package com.folio3.automation;
import org.apache.log4j.BasicConfigurator;
import org.apache.log4j.Logger;
import org.junit.Test;
import junit.framework.Assert;
public class TestClass {
static {
BasicConfigurator.configure();
}
private static final Logger LOG = Logger.getLogger(TestClass.class);
#Test
public void test1(){
LOG.info("test 1 called ");
Assert.assertEquals(true, false);
}
}
Do I have to call BasicConfigurator.configure(); in every class or Base class ?
Is there any way to achieve?
Try adding your application root directory to your VM start command and your problem will disappear.

Categories

Resources