EmbeddedServletContainerCustomizer in spring boot 2.0 - java

I try to migrate my app from spring boot 1.5 to 2.0
The problem is that I cannot find EmbeddedServletContainerCustomizer.
Any ideas how to make it through?
#Bean
public EmbeddedServletContainerCustomizer customizer() {
return container -> container.addErrorPages(new ErrorPage(HttpStatus.UNAUTHORIZED, "/unauthenticated"));
}
Update:
I found it as ServletWebServerFactoryCustomizer in org.springframework.boot.autoconfigure.web.servlet package.
#Bean
public ServletWebServerFactoryCustomizer customizer() {
return container -> container.addErrorPages(new ErrorPage(HttpStatus.UNAUTHORIZED, "/unauthenticated"));
}
But there is an error:
Cannot resolve method
'addErrorPages(org.springframework.boot.web.server.ErrorPage)'
I also had to change import of new Error Page from org.springframework.boot.web.servlet to org.springframework.boot.web.server

I think you need ConfigurableServletWebServerFactory, instead of ServletWebServerFactoryCustomizer.
You can find the code snippet below:
import org.springframework.boot.web.embedded.undertow.UndertowServletWebServerFactory;
import org.springframework.boot.web.server.ErrorPage;
import org.springframework.boot.web.servlet.server.ConfigurableServletWebServerFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpStatus;
#Configuration
public class ServerConfig {
#Bean
public ConfigurableServletWebServerFactory webServerFactory() {
UndertowServletWebServerFactory factory = new UndertowServletWebServerFactory();
factory.addErrorPages(new ErrorPage(HttpStatus.UNAUTHORIZED, "/unauthenticated"));
return factory;
}
}
The above code is for Undertow. For tomcat, you need to replace UndertowServletWebServerFactory with TomcatServletWebServerFactory.
You will need to add the following dependency to your project (in case of Maven):
<dependency>
<groupId>io.undertow</groupId>
<artifactId>undertow-servlet</artifactId>
<version>2.0.26.Final</version>
</dependency>

You don't need the container customizer to register your error pages. Simple bean definition implemented as a lambda does the trick:
#Bean
public ErrorPageRegistrar errorPageRegistrar() {
return registry -> {
registry.addErrorPages(
new ErrorPage(HttpStatus.UNAUTHORIZED, "/unauthenticated"),
);
};
}

From Spring Boot 2 on the WebServerFactoryCustomizer has replaced the EmbeddedServletContainerCustomizer:
#Bean
public WebServerFactoryCustomizer<ConfigurableServletWebServerFactory> webServerFactoryCustomizer() {
return (factory) -> factory.addErrorPages(new ErrorPage(HttpStatus.UNAUTHORIZED, "/401.html"));
}
Alternatively you might add a view controller like
#Override
public void addViewControllers(ViewControllerRegistry registry) {
registry.addViewController("/unauthorized").setViewName("forward:/401.html");
}
and then your WebServerFactory should point to /unauthorized instead:
#Bean
public WebServerFactoryCustomizer<ConfigurableServletWebServerFactory> webServerFactoryCustomizer() {
return (factory) -> factory.addErrorPages(new ErrorPage(HttpStatus.UNAUTHORIZED, "/unauthorized"));
}

You can find the code snippet below:
#Bean
public ErrorPageRegistrar errorPageRegistrar(){
return new MyErrorPageRegistrar();
}
// ...
private static class MyErrorPageRegistrar implements ErrorPageRegistrar {
#Override
public void registerErrorPages(ErrorPageRegistry registry) {
registry.addErrorPages(new ErrorPage(HttpStatus.BAD_REQUEST, "/400"));
}
}

Related

Request to Spring Controller returns 404 not found

I am trying to set up a Spring MVC app but every time I call the http://localhost:9001/tasks API from postman I get the following error:
Here is my code:
#SpringBootApplication(exclude = {SecurityAutoConfiguration.class})
public class TaskManagerApplication {
public static void main(String[] args) {
SpringApplication.run(TaskManagerApplication.class, args);
}
#Bean
public WebMvcConfigurer corsConfigurer() {
return new WebMvcConfigurerAdapter() {
#Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**").allowedOrigins("http://localhost:4200");
}
};
}
}
TaskRepository:
#Path("tasks")
#ApiIgnore
#Component
#AllArgsConstructor
public class TaskResource {
private final TaskService taskService;
#GET
#Produces(APPLICATION_JSON)
public List<Task> getAllTasks() {
return taskService.getTasks();
}
TaskService:
#Service
#RequiredArgsConstructor
public class TaskService {
private final TaskRepository taskRepository;
public List<Task> getTasks() {
return taskRepository.findAll();
}
Project Structure:
You are using JAX-RS in spring boot. Spring handles rest in its own way, if you want to use JAX-RS instead of Springs Rest Annotations, you need to do some extra configurations.
First, you need to add a JAX-RS dependency in your build.gradle or pom.xml file. I guess you have already done that. Jersey is one of the JAX-RS implementation, if you want to add this, you need to do the following.
build.gradle
implementation "org.springframework.boot:spring-boot-starter-jersey"
pom.xml
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jersey</artifactId>
</dependency>
After that, you need to register the JAX-RS endpoints with Spring. I guess you missed this step.
import org.glassfish.jersey.server.ResourceConfig;
import org.springframework.context.annotation.Configuration;
#Configuration
public class JaxrsConfig extends ResourceConfig {
public JaxrsConfig() {
register(TaskResource.class);
}
}
After this, your JAX-RS endpoints will be registered with spring.
But I will suggest you to follow spring annotations if you are using spring. If you use spring annotations your code will look like this.
#RestController
#RequestMapping(path = "tasks")
public class TaskResource {
#GetMapping(path = "", produces = MediaType.APPLICATION_JSON_VALUE)
public List<String> getAllTasks() {
return Arrays.asList("a","b");
}
}
Also you will need to remove JAX-RS from spring to use this Spring MVC annoatations to work.

Disable Hystrix for a single Feign client

My SpringBoot app has Hystrix enabled with fallback defined for some of the Feign clients and undefined for the rest them.
Now, I wanted to disable Hystrix for the ones that did not have a fallback defined as yet. So I followed the steps listed in [paragraph 7.4] https://cloud.spring.io/spring-cloud-netflix/multi/multi_spring-cloud-feign.html which is to create a separate Feign configuration with a vanilla Feign.Builder. However adding the new #Bean Feign.Builder disables my Hystrix functionality across all Feign clients which I don't want. If I remove the #Bean Feign.Builder, Hystrix fallback kicks in like usual in myhystrixclient. A similar SO question here How to disable hystrix in one of multiple feign clients is still open. What am I doing wrong?
public class MyFeignClientConfiguration {
#Bean
public FeignErrorDecoder feignErrorDecoder() {
return new FeignErrorDecoder();
}
#Bean
#Scope("prototype")
public Feign.Builder feignBuilder() {
return Feign.builder();
}
}
My Feign Client looks like below:
#FeignClient(name = "myregularclient", configuration = MyFeignClientConfiguration.class)
public interface MyRegularClient {
//my APIs here
}
My Hystrix Feign Configuration looks like the below:
public class MyFeignClientHystrixConfiguration {
#Bean
public FeignErrorDecoder feignErrorDecoder() {
return new FeignErrorDecoder();
}
}
And here is my Feign client where Hystrix fallback is implemented
#FeignClient(name = "myhystrixclient", configuration = MyFeignClientHystrixConfiguration.class, fallback = MyFallbackService.class)
public interface MyHystrixClient {
//my APIs here
}
UPDATE
Adding my Application.java for further review of the component scan aspects.
#ComponentScan(basePackages ="com.demo.xyz")
#EnableAutoConfiguration(exclude = {DataSourceAutoConfiguration.class,
MetricFilterAutoConfiguration.class,
MetricRepositoryAutoConfiguration.class})
#EnableDiscoveryClient
#EnableFeignClients
#EnableCircuitBreaker
public class MyApplication {
/** Start the app **/
}
I managed to reproduce problem on Spring Cloud vDalston.SR5 and it seems I found a solution. It doesn't brake other feign clients which use hystrix. Tested it manually and with integration tests.
Create feign client without hystrix. Notice that configuration class is annotated with #Configuration annotation.
#FeignClient(name = "withoutHystrix",
url = "conrad.fake",
fallback = FeignClientWithoutHystrix.FallbackThatShouldNotOccur.class,
configuration = FeignClientWithoutHystrixConfig.class)
public interface FeignClientWithoutHystrix {
#RequestMapping(method = RequestMethod.GET, value = "/fake/url")
String getFromFakeUrl();
#Component
class FallbackThatShouldNotOccur implements FeignClientWithoutHystrix {
private final Logger log = LoggerFactory.getLogger(this.getClass());
#Override
public String getFromFakeUrl() {
log.error("This fallback shouldn't occur");
return "Fallback";
}
}
}
#Configuration
public class FeignClientWithoutHystrixConfig {
#Bean
public Feign.Builder feignBuilder() {
return Feign.builder();
}
}
Exclude feign client configuration from #ComponentScan with excludeFilters:
#EnableFeignClients
#SpringBootApplication
#ComponentScan(basePackages = "konrad",
excludeFilters = {#ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE, value = FeignClientWithoutHystrixConfig.class)})
public class CloudClient {
public static void main(String[] args) {
SpringApplication.run(CloudClient.class, args);
}
}
Enable hystrix
feign:
hystrix:
enabled: true
If you want to check anything or run tests, this is my repository https://github.com/MrJavaNoHome/spring-cloud-client
please try this configuration:
import feign.Feign;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Scope;
#Configuration
public class MyFeignClientConfiguration {
#Bean
#Scope("prototype")
public Feign.Builder feignBuilder() {
return Feign.builder();
}
}
And:
import feign.hystrix.HystrixFeign;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Scope;
#Configuration
public class MyFeignClientHystrixConfiguration {
#Bean
#Scope("prototype")
public HystrixFeign.Builder feignBuilder() {
return HystrixFeign.builder();
}
}
feign-hystrix was a part of spring-cloud-starter-openfeign with version less than 3.0.0.
Starting from 3.0.0 , feign-hystrix no longer included

Understanding Primary annotation in Profile

I am trying to understand the behaviour of #Primary in #Profile from this video
Dependency Injection using profile.
The active profile in file application.properties is english and running it gives error
expected single matching bean but found 2: helloWorldServiceEnglish,helloWorldServiceSpanish
Adding #Primary annotation in helloConfig.java resolves the error:
#Bean
#Profile("english")
#Primary
public HelloWorldService helloWorldServiceEnglish(HelloWorldFactory factory) {
return factory.createHelloWorldService("en");
}
When I am Autowiring using Profile and there is only one single Profile named english then why it is searching for other beans which do not have #Profile annotation? And how adding #Primary is changing this behaviour?
Does Spring internally first scans for Autowire by type and completely ignore #Profile because of which it throws error expected single matching bean but found 2.
helloConfig.java
package com.spring.config;
import com.spring.services.HelloWorldFactory;
import com.spring.services.HelloWorldService;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.context.annotation.Profile;
#Configuration
public class HelloConfig {
#Bean
public HelloWorldFactory helloWorldFactory() {
return new HelloWorldFactory();
}
#Bean
#Profile("english")
#Primary
public HelloWorldService helloWorldServiceEnglish(HelloWorldFactory factory) {
return factory.createHelloWorldService("en");
}
#Bean
#Qualifier("spanish")
public HelloWorldService helloWorldServiceSpanish(HelloWorldFactory factory) {
return factory.createHelloWorldService("es");
}
#Bean
#Profile("french")
public HelloWorldService helloWorldServiceFrench(HelloWorldFactory factory) {
return factory.createHelloWorldService("fr");
}
#Bean
#Profile("german")
public HelloWorldService helloWorldServiceGerman(HelloWorldFactory factory) {
return factory.createHelloWorldService("de");
}
#Bean
#Profile("polish")
public HelloWorldService helloWorldServicePolish(HelloWorldFactory factory) {
return factory.createHelloWorldService("pl");
}
#Bean
#Profile("russian")
public HelloWorldService helloWorldServiceRussian(HelloWorldFactory factory) {
return factory.createHelloWorldService("ru");
}
}
DependencyInjectionApplication.java
package com.spring.componentScan;
import com.spring.controllers.GreetingController;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.ComponentScan;
#SpringBootApplication
#ComponentScan("com.spring")
public class DependencyInjectionApplication {
public static void main(String[] args) {
ApplicationContext ctx = SpringApplication.run(DependencyInjectionApplication.class, args);
GreetingController controller = (GreetingController) ctx.getBean("greetingController");
controller.sayHello();
}
}
GreetingController.java
package com.spring.controllers;
import com.spring.services.HelloWorldService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Controller;
#Controller
public class GreetingController {
private HelloWorldService helloWorldService;
private HelloWorldService helloWorldServiceSpanish;
#Autowired
public void setHelloWorldService(HelloWorldService helloWorldService) {
this.helloWorldService = helloWorldService;
}
#Autowired
#Qualifier("spanish")
public void setHelloWorldServiceFrench(HelloWorldService helloWorldServiceSpanish) {
this.helloWorldServiceSpanish = helloWorldServiceSpanish;
}
public String sayHello() {
String greeting = helloWorldService.getGreeting();
System.out.println(helloWorldServiceSpanish.getGreeting());
System.out.println(greeting);
return greeting;
}
}
Application.properties
spring.profiles.active=english
Complete Source code:
If you consider this source code
#Bean(name = "french")
public HelloWorldService helloWorldServiceFrench(HelloWorldFactory factory) {
return factory.createHelloWorldService("fr");
}
#Bean
public HelloWorldService helloWorldServiceGerman(HelloWorldFactory factory) {
return factory.createHelloWorldService("de");
}
#Bean
public HelloWorldService helloWorldServicePolish(HelloWorldFactory factory) {
return factory.createHelloWorldService("pl");
}
#Bean
public HelloWorldService helloWorldServiceRussian(HelloWorldFactory factory) {
return factory.createHelloWorldService("ru");
}
here is no #Profile annotation, and thats why Spring creating multiple beans of same type, if you want them to recognized differently, try giving them well qualified explicit name by #Bean(name="polish") (or Spring anyway assign them by looking at #Bean method name) and then autowire using #Qualifier("polish")
Okay, so your example is not updated with the latest code. But I assume that you want to create multiple instances of the same bean type and use them for different languages. It's easy to achieve and you don't need to have #Profile and #Primary for that.
What you need is just assign qualifier for the bean instance (or use the one that spring assigns by default). And the inject bean by this qualifier.
#Bean
public HelloWorldService helloWorldServiceFrench(HelloWorldFactory factory) {
return factory.createHelloWorldService("fr");
}
#Bean
public HelloWorldService helloWorldServiceGerman(HelloWorldFactory factory) {
return factory.createHelloWorldService("de");
}
#Bean
public HelloWorldService helloWorldServicePolish(HelloWorldFactory factory) {
return factory.createHelloWorldService("pl");
}
#Bean
public HelloWorldService helloWorldServiceRussian(HelloWorldFactory factory) {
return factory.createHelloWorldService("ru");
}
Controller:
#Controller
public class GreetingController {
#Qualifier("helloWorldServiceGerman")
#Autowired
private HelloWorldService helloWorldServiceGerman;
#Qualifier("helloWorldServiceFrench")
#Autowired
private HelloWorldService helloWorldServiceFrench;
#Qualifier("helloWorldServicePolish")
#Autowired
private HelloWorldService helloWorldServicePolish;
#Qualifier("helloWorldServiceRussian")
#Autowired
private HelloWorldService helloWorldServiceRussian;
. . .
}
Update
You usually mark a bean as #Primary when you want to have one bean instance as a priority option when there are multiple injection candidates. Official doc with good example.
#Profile just narrows bean search, but still if you have multiple beans of the same type in the same profile - #Primary to the rescue (if you autowire by type, autowire by qualifier still works fine though).
Developers usually do this to avoid NoUniqueBeanDefinitionException that you had initially.

Spring Webflux, How to forward to index.html to serve static content

spring-boot-starter-webflux (Spring Boot v2.0.0.M2) is already configured like in a spring-boot-starter-web to serve static content in the static folder in resources. But it doesn't forward to index.html. In Spring MVC it is possible to configure like this:
#Override
public void addViewControllers(ViewControllerRegistry registry) {
registry.addViewController("/").setViewName("forward:/index.html");
}
How to do it in Spring Webflux?
Do it in WebFilter:
#Component
public class CustomWebFilter implements WebFilter {
#Override
public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) {
if (exchange.getRequest().getURI().getPath().equals("/")) {
return chain.filter(exchange.mutate().request(exchange.getRequest().mutate().path("/index.html").build()).build());
}
return chain.filter(exchange);
}
}
import static org.springframework.web.reactive.function.server.RequestPredicates.GET;
import static org.springframework.web.reactive.function.server.RouterFunctions.route;
import static org.springframework.web.reactive.function.server.ServerResponse.ok;
#Bean
public RouterFunction<ServerResponse> indexRouter(#Value("classpath:/static/index.html") final Resource indexHtml) {
return route(GET("/"), request -> ok().contentType(MediaType.TEXT_HTML).bodyValue(indexHtml));
}
There's a ticket in the Spring Boot tracker for this.
The same by using WebFlux Kotlin DSL:
#Bean
open fun indexRouter(): RouterFunction<ServerResponse> {
val redirectToIndex =
ServerResponse
.temporaryRedirect(URI("/index.html"))
.build()
return router {
GET("/") {
redirectToIndex // also you can create request here
}
}
}

How to use multiple spring configuration files

I have a java spring configuration defined like so,
#Configuration
public class FirstConfiguration {
#Bean
FirstController firstController() {
return new FirstController(firstService());
}
#Bean
FirstService firstService() {
return new FirstServiceImpl(secondService());
}
}
Now the beans in this configuration depend on SecondConfiguration defined like so,
#Configuration
public class SecondConfiguration {
#Bean
SecondController SecondController() {
return new SecondController(SecondService());
}
#Bean
SecondService secondService() {
return new SecondServiceImpl();
}
}
How can I make use of the secondService() bean in my FirstConfiguration?
Since the SecondService is a bean, you could inject it into the firstService method to configure another bean:
#Bean
FirstService firstService(#Autowired SecondService secondService) {
return new FirstServiceImpl(secondService);
}
You can inject the firstService like this :
#Autowired
SecondService secondService
You can refer the method secondService() directly when you import the configuration.
#Configuration
#Import(SecondConfiguration.class)
public class FirstConfiguration {
#Bean
FirstController firstController() {
return new FirstController(firstService());
}
#Bean
SomeController someController() {
return new SomeController(secondService());
}
}
Refer the Spring config import

Categories