I am creating a custom annotation NullCheck for method parameter to check value is null or not hello(#NullCheck String text), but I am not able to invoke Aspect around the Annotation.
Main Class
package com.example.demo;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
#SpringBootApplication
#EnableAutoConfiguration
#EnableAspectJAutoProxy
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
}
Controller class, just invoking an aspect for POC, not returning anything
package com.example.demo;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
#RestController
#RequestMapping("/")
class HelloController {
#GetMapping
public void hello() {
hello("hi");
}
private void hello(#NullCheck String text) {
System.out.println(text);
}
}
Annotation
package com.example.demo;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
#Documented
#Retention(RUNTIME)
#Target(ElementType.PARAMETER)
public #interface NullCheck { }
Aspect
package com.example.demo;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component;
#Aspect
#Component
public class NullCheckAspect {
this is working
#Before("#annotation(org.springframework.web.bind.annotation.GetMapping)")
but this is not
#Before("#annotation(com.example.demo.NullCheck)")
public void beforeAdvice(JoinPoint joinPoint) {
System.out.println("Before method:" + joinPoint.getSignature());
}
}
build.gradle
buildscript {
ext {
springBootVersion = '2.1.2.RELEASE'
}
repositories {
mavenCentral()
}
dependencies {
classpath("org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}")
}
}
apply plugin: 'java'
apply plugin: 'idea'
apply plugin: 'eclipse'
apply plugin: 'org.springframework.boot'
apply plugin: 'io.spring.dependency-management'
group = 'com.example'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = '1.8'
idea {
module {
// if you hate browsing Javadoc
downloadJavadoc = true
// and love reading sources :)
downloadSources = true
}
}
bootJar {
launchScript()
}
repositories {
mavenCentral()
jcenter()
}
bootJar {
launchScript()
}
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-actuator'
implementation 'org.springframework.boot:spring-boot-starter-aop'
implementation 'org.springframework.boot:spring-boot-starter-web'
runtimeOnly 'org.springframework.boot:spring-boot-devtools'
}
what am I missing?
As per my understanding and after doing some search on google, you can get method parameter and its value with particular annotation with following code:
package com.example.demo;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;
#Component
#Aspect
public class NullCheckAspect {
#Around("execution(* com.example.demo.HelloController.nullChecker(String))")
public Object around(ProceedingJoinPoint pJoinPoint) throws Throwable {
Object[] args = pJoinPoint.getArgs();
Method method = MethodSignature.class.cast(pJoinPoint.getSignature()).getMethod();
Annotation[][] parametersAnnotations = method.getParameterAnnotations();
Map<String, Object> annotatedParameters = new HashMap<>();
int i = 0;
for(Annotation[] parameters : parametersAnnotations) {
Object arg = args[i];
String name = method.getParameters()[i++].getDeclaringExecutable().getName();
for(Annotation parameter: parameters) {
if ((parameter instanceof NullCheck)) {
System.out.println("Found the null checker annotation with name: " + name);
System.out.println("Found the null checker annotation with arg: " + arg);
annotatedParameters.put(name, arg);
}
}
}
System.out.println(annotatedParameters);
return pJoinPoint.proceed(args);
}
}
And with the interface annotation:
package com.example.demo;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import org.springframework.stereotype.Component;
#Component
#Retention(RUNTIME)
#Target(ElementType.PARAMETER)
public #interface NullCheck {
}
More details about my code you can check it at my github repository, I have created spring boot demo version and pushed it for you at https://github.com/krishnaiitd/learningJava/blob/master/springBoot/gs-spring-boot/src/main/java/com/example/demo/HelloController.java
This also include other type of aspects like tracking the time of a particular methods.
Hope this will help you to get the basic understanding of #Aspect in Spring boot.
I don't know why it is as it is but I had the same issue. This is can be solved easily by using Pointcuts, for example:
#Before("nulllCheckAnnotation()")
public void beforeAdvice(JoinPoint joinPoint) {
System.out.println("Before method:" + joinPoint.getSignature());
}
}
#Pointcut("#annotation(com.example.demo.NullCheck)")
private void nulllCheckAnnotation() { }
Read more about pointcuts here if you interested: https://www.baeldung.com/spring-aop-pointcut-tutorial
I am used to adding annotations on my REST controllers for Swagger-UI to use. However, I would prefer to point Swagger-UI at a YAML file which describes my REST controller. An example of what I want to do is shown below. (Springfox/Swagger2)
DemoApplication.java
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
#SpringBootApplication
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
}
SwaggerConfig.java
Note that I am trying to tell Swagger to build a Docket based on a YAML file rather than a REST controller.
import com.google.common.base.Predicate;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spring.web.plugins.Docket;
import springfox.documentation.swagger2.annotations.EnableSwagger2;
import static springfox.documentation.builders.PathSelectors.regex;
#Configuration
#EnableSwagger2
public class SwaggerConfig {
#Bean
public Docket productApi() {
return new Docket(DocumentationType.SWAGGER_2).useDefaultResponseMessages(false)
.select()
.paths(paths())
.build();
}
private Predicate<String> paths() {
return regex("/swagger.yml");
}
}
swagger.yml
This is a sample YAML file describing what my REST controller looks like, and this is what I want Swagger-UI to use.
swagger: "2.0"
paths:
/animals:
post:
summary: Creates an animal.
responses:
'201':
description: Created.
build.gradle
plugins {
id 'org.springframework.boot' version '2.1.3.RELEASE'
id 'java'
}
apply plugin: 'io.spring.dependency-management'
group = 'com.example'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = '1.8'
repositories {
mavenCentral()
}
dependencies {
implementation 'org.springframework.boot:spring-boot-starter'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
implementation group: 'io.springfox', name: 'springfox-swagger2', version: '2.8.0'
implementation group: 'io.springfox', name: 'springfox-swagger-ui', version: '2.8.0'
}
If this is not possible with a YAML file but it is possible using some other format (like JSON), feel free to answer with that solution instead.
You need to inject your YAML definition via SwaggerResourceProvider.
If you need to preserve annotation-based swagger:
#Configuration
#EnableSwagger2
public class SwaggerConfig {
#Bean
public Docket api() {
return new Docket(DocumentationType.SWAGGER_2)
.select()
.apis(RequestHandlerSelectors.any())
.paths(PathSelectors.any())
.build();
}
#Primary
#Bean
public SwaggerResourcesProvider swaggerResourcesProvider(InMemorySwaggerResourcesProvider defaultResourcesProvider) {
return () -> {
SwaggerResource wsResource = new SwaggerResource();
wsResource.setName("Documentation");
wsResource.setSwaggerVersion("2.0");
wsResource.setLocation("/swagger.yaml");
List<SwaggerResource> resources = new ArrayList<>(defaultResourcesProvider.get());
resources.add(wsResource);
return resources;
};
}
}
if you want to use just swagger based on YAML:
#Configuration
#EnableSwagger2
public class SwaggerConfig {
#Primary
#Bean
public SwaggerResourcesProvider swaggerResourcesProvider() {
return () -> {
SwaggerResource wsResource = new SwaggerResource();
wsResource.setName("Documentation");
wsResource.setSwaggerVersion("2.0");
wsResource.setLocation("/swagger.yaml");
List<SwaggerResource> resources = List.of(wsResource);
return resources;
};
}
}
you need to put your YAML file to src/main/resource/static
Recently I have added Spring Security to my Spring Boot project using the following class:
#EnableWebSecurity
#EnableGlobalMethodSecurity(prePostEnabled = true)
public class MySecurityConfig {
}
as result, by default all my URLs are now protected with authentication and a self-generated password.
The problem is that all tests in a #WebMvcTest class that I used for unit-testing a controller:
#RunWith(SpringRunner.class)
#WebMvcTest(SomeController.class)
public class SomeControllerTest {...}
are now failing everywhere because of lack of authorization.
Question: can I tell #Test methods to ignore authorization so they keep succeeding as before?
How can I prevent the #EnableWebSecurity config class from being picked on a specific #WebMvcTest unit testing class?
I would like the tests already in place to be able to still go through and to test the authentication features separately later on.
So far I have tried to use a nested config class in the testing class in order to exclude security configs:
#RunWith(SpringRunner.class)
#WebMvcTest(SomeController.class)
public class SomeControllerTest {
#Configuration
#EnableAutoConfiguration(exclude = { SecurityAutoConfiguration.class})
static class ContextConfiguration { }
....}
but it seems not to work.
NOTE : I am using Spring Boot 1.5.8
For me in Spring Boot 2.2.4 (JUnit5) the below seems to have worked and bypass the security filter.
#ExtendWith(SpringExtension.class)
#WebMvcTest(SomeController.class)
#AutoConfigureMockMvc(addFilters = false)
public class SomeControllerTest {
...
Note: this simply disables any filters in the SpringSecurity configuration. It won't disable the security completely. In other words it will still bootstrap security without loading any filters.
You can set secure=false in the #WebMvcTest annoation.
It will skip the spring security MockMvc auto configuration in your Test
#WebMvcTest(controllers = SomeController.class, secure = false)
public class SomeControllerTest {
Note by the author:
As of 2021, this answer has been obsolete for a few years and it probably won't work for you.
In Spring Boot 2.2.6, #WebMvcTest is meta annotated with #AutoConfigureWebMvc which auto-configure org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration as you can see in spring.factories of spring-boot-test-autoconfigure.jar
So you just have to exclude SecurityAutoConfiguration in your test to disable Spring Security :
#WebMvcTest(excludeAutoConfiguration = SecurityAutoConfiguration.class)
In Spring Boot 2.4 both secure flags were removed and none of the answers here actually work.
I ended up excluding all the security myself and wrapping it around in a custom annotation.
import org.springframework.boot.autoconfigure.security.oauth2.client.servlet.OAuth2ClientAutoConfiguration;
import org.springframework.boot.autoconfigure.security.oauth2.resource.servlet.OAuth2ResourceServerAutoConfiguration;
import org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration;
import org.springframework.boot.autoconfigure.security.servlet.SecurityFilterAutoConfiguration;
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.FilterType;
import org.springframework.core.annotation.AliasFor;
import org.springframework.security.config.annotation.web.WebSecurityConfigurer;
import java.lang.annotation.*;
#Target({ElementType.TYPE})
#Retention(RetentionPolicy.RUNTIME)
#WebMvcTest(excludeFilters = {#ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE, value = WebSecurityConfigurer.class)},
excludeAutoConfiguration = {SecurityAutoConfiguration.class,
SecurityFilterAutoConfiguration.class,
OAuth2ClientAutoConfiguration.class,
OAuth2ResourceServerAutoConfiguration.class})
public #interface UnsecuredWebMvcTest {
#AliasFor(annotation = WebMvcTest.class, attribute = "controllers")
Class<?>[] value() default {};
#AliasFor(annotation = WebMvcTest.class, attribute = "controllers")
Class<?>[] controllers() default {};
}
With Spring Security 4+, I find #WithMockUser annotation to be very handy. It provides a mock user and password to test spring security methods annotated with #PreAuthorize or #PostAuthorize. All you need to do is annotate the test method with #WithMockUser. The default role for the user is USER. You can override the default username and role too.
//default
#Test
#WithMockUser
public void getProfile() {
//your test here
}
//with username and roles
#Test
#WithMockUser(username = "john", roles={"ADMIN"})
public void getProfile() {
//your test here
}
NOTE: This annotation can be used for classes.
#WithMockUser(username = "john", roles={"ADMIN"})
public class UsersAdminSecurityTest {
}
This worked for me, using spring boot 2.3.1.
#ExtendWith(SpringExtension.class)
#WebMvcTest(SomeController.class)
#AutoConfigureMockMvc(addFilters = false)
public class SomeControllerTest {
}
I understand this is a specific question for Spring Boot 1.5 but seems a bit old. In order to have a successful OAuth2 secured controller unit test running I applied the following steps, kindly notice I used Spring Boot 2.2.6, Gradle 5.x, and JUnit 5. This mechanism should work the same way deprecated ones based on #AutoConfigureMockMvc(secure = false) or #WebMvcTest(controllers = SomeController.class, secure = false)
This is for a REST API project that is secured (OAuth2) using Microsoft's Azure Active Directory, but essentially this testing strategy should work for any OIDC, OAuth2 configuration.
The trick is to have a Controller test file and annotate it with #WebMvcTest annotation, however, the following parameters are required:
#WebMvcTest(
value = YourController.class
// this disables loading up the WebSecurityConfig.java file, otherwise it fails on start up
, useDefaultFilters = false
// this one indicates the specific filter to be used, in this case
// related to the GreetController we want to test
, includeFilters = {
#ComponentScan.Filter(
type = FilterType.ASSIGNABLE_TYPE,
value = YourController.class
)
}
)
Here the configurations that make the test run successfully.
build.gradle
plugins {
id 'org.springframework.boot' version '2.2.6.RELEASE'
id 'io.spring.dependency-management' version '1.0.9.RELEASE'
id 'java'
}
group = 'com.grailscoder'
version = '0.0.1-SNAPSHOT'
configurations {
compileOnly {
extendsFrom annotationProcessor
}
}
repositories {
mavenCentral()
}
ext {
set('azureVersion', "2.2.4")
}
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-security'
implementation 'org.springframework.boot:spring-boot-starter-web'
implementation 'com.microsoft.azure:azure-active-directory-spring-boot-starter'
compileOnly 'org.projectlombok:lombok'
annotationProcessor 'org.projectlombok:lombok'
testImplementation('org.springframework.boot:spring-boot-starter-test') {
exclude group: 'org.junit.vintage', module: 'junit-vintage-engine'
}
testImplementation 'org.springframework.security:spring-security-test'
testImplementation 'org.junit.jupiter:junit-jupiter-api:5.5.2'
testImplementation 'org.junit.jupiter:junit-jupiter-params:5.5.2'
testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.5.2'
}
dependencyManagement {
imports {
mavenBom "com.microsoft.azure:azure-spring-boot-bom:${azureVersion}"
}
}
test {
useJUnitPlatform()
}
GreetController.java
package com.grailscoder.controller;
import lombok.RequiredArgsConstructor;
import org.springframework.http.HttpStatus;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestController;
#RequiredArgsConstructor
#RestController
public class GreetController {
#GetMapping("/greets")
#PreAuthorize("hasRole('ROLE_USER')") // This is validating against Active Directory's User role granted to the
// current user.
#ResponseStatus(HttpStatus.OK)
public String getGreetMessage() {
return "Greets from secret controller";
}
}
WebSecurityConfig.java
package com.grailscoder.config;
import com.microsoft.azure.spring.autoconfigure.aad.AADAppRoleStatelessAuthenticationFilter;
import lombok.RequiredArgsConstructor;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
#RequiredArgsConstructor
#EnableWebSecurity
#EnableGlobalMethodSecurity(prePostEnabled = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
private final AADAppRoleStatelessAuthenticationFilter aadAuthFilter;
#Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable();
http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.NEVER);
http.authorizeRequests()
.antMatchers("/", "/index.html", "/public").permitAll()
.anyRequest().authenticated();
http.addFilterBefore(aadAuthFilter, UsernamePasswordAuthenticationFilter.class);
}
}
application.properties
azure.activedirectory.client-id=xxxxx-AD-client-id-goes-here
azure.activedirectory.session-stateless=true
GreetControllerTest.java
package com.grailscoder.controller;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.FilterType;
import org.springframework.security.test.context.support.WithMockUser;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.ResultActions;
import static org.mockito.Mockito.when;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
#WebMvcTest(
value = GreetController.class
// this disables loading up the WebSecurityConfig.java file, otherwise it fails on start up
, useDefaultFilters = false
// this one indicates the specific filter to be used, in this case
// related to the GreetController we want to test
, includeFilters = {
#ComponentScan.Filter(
type = FilterType.ASSIGNABLE_TYPE,
value = GreetController.class
)
}
)
class GreetControllerTest {
#Autowired
MockMvc mockMvc;
#Autowired
ObjectMapper objectMapper;
#BeforeEach
void setUp() {
// add setup stuff here
}
#Test
#WithMockUser
void testGreet() throws Exception {
ResultActions result = mockMvc.perform(get("/greets"))
.andExpect(status().isOk());
System.out.println(result.andReturn().getResponse().getContentAsString());
}
}
I understand in order to have a similar test JUnit 4 based with a completely different approach, the following test could be used as a reference (but I haven't tried): https://github.com/spring-projects/spring-security/blob/master/samples/boot/oauth2resourceserver/src/test/java/sample/OAuth2ResourceServerControllerTests.java
In my case, for Spring Boot version 2.5.4, I'm able to bypass Jwt security by setting useDefaultFilters = false in #WebMvcTest
#WebMvcTest(controllers = YourController.class, useDefaultFilters = false)
public class YourControllerTest {
// Test cases
}
#AutoConfigureMockMvc(addFilters = false)
Just adding addFilters = false resolved this.
I solved the problem by using following annotations and properties:
#WebMvcTest(controllers =
SomeController.class,
excludeAutoConfiguration = {
MySecurityConfig.class,
ManagementWebSecurityAutoConfiguration.class,
SecurityAutoConfiguration.class
}
)
#ContextConfiguration(classes = SomeController.class)
public class SomeControllerTest {
}
NOTE: I' using spring boot 2.6.6, so secure=false didn't work for me!
#AutoConfigureMockMvc(secure = false) does not work because secure is deprecated
what works:
#AutoConfigureMockMvc(addFilters = false)
does not completely disable spring security just bypass its filter chain.
or
#WebMvcTest(excludeAutoConfiguration = {SecurityAutoConfiguration.class})
(and if you are using Spring Boot actuator:
#WebMvcTest(excludeAutoConfiguration = {SecurityAutoConfiguration.class,
ManagementWebSecurityAutoConfiguration.class})
)
I'm new to Spring and Vaadin. I'm trying to go through tutorial for views navigation.
I'd like to enable #Push in future to update views periodically with data fetched from DB.
The project is very, very simple. I use no configuration class nor any XML - only Java annotations.
Could anyone help me to fix the example code? Because I'm getting following warnings and I'm not sure what can be done with it and what will be the result of ignoring them.
First one is:
onClassPostProcessor : Cannot enhance #Configuration bean definition 'com.vaadin.spring.VaadinConfiguration' since its singleton instance has been created too early.
The typical cause is a non-static #Bean method with a BeanDefinitionRegistryPostProcessor return type:
Consider declaring such methods as 'static'.
And another one is only when I add compile("com.vaadin:vaadin-push") dependancy:
o.a.util.IOUtils : More than one Servlet Mapping defined.
WebSocket may not work org.apache.catalina.core.ApplicationServletRegistration
AppMain
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
#SpringBootApplication
public class AppMain {
public static void main(String[] args) {
SpringApplication.run(AppMain.class, args);
}
}
MyUI
import com.vaadin.annotations.Theme;
import com.vaadin.navigator.View;
import com.vaadin.navigator.ViewDisplay;
import com.vaadin.server.VaadinRequest;
import com.vaadin.spring.annotation.SpringUI;
import com.vaadin.spring.annotation.SpringViewDisplay;
import com.vaadin.ui.*;
import com.vaadin.ui.themes.ValoTheme;
#Theme("valo")
#SpringUI(path = "/ui")
#SpringViewDisplay
public class MyUI extends UI implements ViewDisplay {
private Panel springViewDisplay;
#Override
protected void init(VaadinRequest request) {
final VerticalLayout root = new VerticalLayout();
root.setSizeFull();
setContent(root);
final CssLayout navigationBar = new CssLayout();
navigationBar.addStyleName(ValoTheme.LAYOUT_COMPONENT_GROUP);
root.addComponent(navigationBar);
springViewDisplay = new Panel();
springViewDisplay.setSizeFull();
root.addComponent(springViewDisplay);
root.setExpandRatio(springViewDisplay, 1.0f);
}
#Override
public void showView(View view) {
springViewDisplay.setContent((Component) view);
}
}
DefaultView
import com.vaadin.navigator.View;
import com.vaadin.navigator.ViewChangeListener;
import com.vaadin.spring.annotation.SpringView;
import com.vaadin.ui.Label;
import com.vaadin.ui.VerticalLayout;
import javax.annotation.PostConstruct;
#SpringView(name = DefaultView.VIEW_NAME)
public class DefaultView extends VerticalLayout implements View {
public static final String VIEW_NAME = "";
#PostConstruct
void init() {
addComponent(new Label("This is the default view"));
}
#Override
public void enter(ViewChangeListener.ViewChangeEvent event) {
// This view is constructed in the init() method()
}
}
build.gradle
buildscript {
ext { springBootVersion = '1.5.6.RELEASE' }
repositories { mavenCentral() }
dependencies { classpath("org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}") }
}
apply plugin: 'java'
apply plugin: 'org.springframework.boot'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = 1.8
repositories { mavenCentral() }
ext { vaadinVersion = '8.1.0' }
dependencies {
compile('com.vaadin:vaadin-spring-boot-starter')
compile("com.vaadin:vaadin-push")
compile("org.springframework.boot:spring-boot-starter-logging")
compile('org.springframework.boot:spring-boot-starter-web')
testCompile('org.springframework.boot:spring-boot-starter-test')
}
dependencyManagement {
imports { mavenBom "com.vaadin:vaadin-bom:${vaadinVersion}" }
}
application.properties
server.port=8091
I'm building JPA configuration with multiple persistence units using different in-memory datasources, but the configuration fails resolving the qualified datasource for entity manager factory bean with the following error:
***************************
APPLICATION FAILED TO START
***************************
Description:
Parameter 0 of method emfb in datasources.Application$PersistenceConfiguration required a single bean, but 2 were found:
- ds1: defined by method 'ds1' in class path resource [datasources/Application$PersistenceConfiguration.class]
- ds2: defined by method 'ds2' in class path resource [datasources/Application$PersistenceConfiguration.class]
Action:
Consider marking one of the beans as #Primary, updating the consumer to accept multiple beans, or using #Qualifier to identify the bean that should be consumed
Here is the sample application
package datasources;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import javax.persistence.PersistenceContextType;
import javax.sql.DataSource;
import javax.ws.rs.ApplicationPath;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import org.apache.log4j.Logger;
import org.glassfish.jersey.server.ResourceConfig;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.transaction.jta.JtaAutoConfiguration;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.boot.orm.jpa.EntityManagerFactoryBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseBuilder;
import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
import org.springframework.stereotype.Component;
#Configuration
#EnableAutoConfiguration(exclude = {
// HibernateJpaAutoConfiguration.class,
// DataSourceAutoConfiguration.class
JtaAutoConfiguration.class
})
#ComponentScan
public class Application {
public static void main(String[] args) {
new SpringApplicationBuilder(Application.class)
.build()
.run(args);
}
#Component
#Path("/ds")
public static class DsApi {
private final static Logger logger = Logger.getLogger(DsApi.class);
#Autowired(required = false)
#Qualifier("ds1")
private DataSource ds;
#GET
public String ds() {
logger.info("ds");
return ds.toString();
}
}
#Component
#Path("/em")
public static class EmApi {
private final static Logger logger = Logger.getLogger(EmApi.class);
#PersistenceContext(unitName = "ds2", type = PersistenceContextType.TRANSACTION)
private EntityManager em;
#GET
public String em() {
logger.info("em");
return em.toString();
}
}
#Configuration
#ApplicationPath("/jersey")
public static class JerseyConfig extends ResourceConfig {
public JerseyConfig() {
register(DsApi.class);
register(EmApi.class);
}
}
#Configuration
public static class PersistenceConfiguration {
#Bean
#Qualifier("ds1")
public DataSource ds1() {
return new EmbeddedDatabaseBuilder().build();
}
#Bean
#Qualifier("ds2")
public DataSource ds2() {
return new EmbeddedDatabaseBuilder().build();
}
#Bean
#Primary
#Autowired
public LocalContainerEntityManagerFactoryBean emfb(#Qualifier("ds1") DataSource ds, EntityManagerFactoryBuilder emfb) {
return emfb.dataSource(ds)
.packages(Application.class)
.persistenceUnit("ds1")
.build();
}
#Bean
#Autowired
public LocalContainerEntityManagerFactoryBean emfb2(#Qualifier("ds2") DataSource ds, EntityManagerFactoryBuilder emfb) {
return emfb.dataSource(ds)
.packages(Application.class)
.persistenceUnit("ds2")
.build();
}
}
}
The error is indicating that at some point in the application, a bean is being injected by the type DataSource and not being qualified by name at that point.
It does not matter that you have added #Qualifier in one location. The injection is failing in some other location that has not been qualified. It's not your fault though because that location is in Spring Boot's DataSourceAutoConfiguration which you should be able to see in your stack trace, below the piece that you have posted.
I would recommend excluding DataSourceAutoConfiguration i.e. #SpringBootApplication(exclude = DataSourceAutoConfiguration.class). Otherwise, this configuration is only being applied to the bean you have made #Primary. Unless you know exactly what that is, it is likely to result in subtle and unexpected differences in behaviour between your DataSources.
Declare one of your DataSource as #Primary.
Also you have 2 beans of same type - LocalContainerEntityManagerFactoryBean, declare one of them #Primary as well, as follows:
#Configuration
public static class PersistenceConfiguration {
#Bean
#Primary
public DataSource ds1() {
return new EmbeddedDatabaseBuilder().build();
}
#Bean
public DataSource ds2() {
return new EmbeddedDatabaseBuilder().build();
}
#Bean
#Primary
#Autowired
public LocalContainerEntityManagerFactoryBean emfb(#Qualifier("ds1") DataSource ds, EntityManagerFactoryBuilder emfb) {
return emfb.dataSource(ds)
.packages(DemoApplication.class)
.persistenceUnit("ds1")
.build();
}
#Bean
#Autowired
public LocalContainerEntityManagerFactoryBean emfb2(#Qualifier("ds2") DataSource ds, EntityManagerFactoryBuilder emfb) {
return emfb.dataSource(ds)
.packages(DemoApplication.class)
.persistenceUnit("ds2")
.build();
}
}
Try declaring the datasource beans outside the static class . I.e directly in Application.java