0

I upgraded spring from version 2.1.1 to 2.2.0 . Since then I'm facing the following error when I start my app : Caused by: org.springframework.beans.factory.NoUniqueBeanDefinitionException: No qualifying bean of type 'ParentService' available: expected single matching bean but found 2: MasterService,SlaveService .

ParentService is an interface :

public interface ParentService{
..
}

MasterService :

@Service
@MasterProfile
public class MasterService implements ParentService{
.....
}

SlaveService :

@Service
@SlaveProfile
public class SlaveService implements ParentService{
.....
}

MasterProfile annotation :

@Profile("MASTER")
public @interface MasterProfile {

}

Slave Profile :

@Profile("SLAVE")
public @interface SlaveProfile{

}

I'm passing to my app the profile with the following flag :

-Dspring.profiles.include=MASTER

According to Spring 2.2 release notes, they have done some changes and forks are enabled by default in maven. As a result the only way to pass params is with the parameter -Dspring-boot.run.jvmArguments . I used -Dspring-boot.run.jvmArguments=-Dspring.profiles.include=MASTER but it still fails..

JeyJ
  • 3,582
  • 4
  • 35
  • 83
  • but I have the @Profile annotation that should help spring determine which bean to autowrite. As I mentioned, in version 2.1.1 it worked. – JeyJ Jan 06 '20 at 16:25
  • They are my own annotations , just simple interfaces with @profile annotation above them – JeyJ Jan 06 '20 at 16:26
  • same as MasterProfile, just a different name. Added it to the main post – JeyJ Jan 06 '20 at 16:29
  • "The following profiles are active: test,native." Which means it doesnt get the profile(master or slave). Something related to the way the profile is passed to spring – JeyJ Jan 06 '20 at 16:34
  • but if I'll want to have multiple profiles activated ? – JeyJ Jan 06 '20 at 16:42
  • for example : -Dspring.profiles.active=profile_a,profile_b,profile_c ? – JeyJ Jan 06 '20 at 16:42
  • Tried that also, but still I'm getting the same exception. – JeyJ Jan 06 '20 at 16:44
  • same thing : "The following profiles are active: test,native." – JeyJ Jan 06 '20 at 16:46
  • In fact, why are you using `-D` property for this in the first place? The plugin already has configuration for profiles https://docs.spring.io/spring-boot/docs/current/maven-plugin/start-mojo.html#profiles – Michael Jan 06 '20 at 16:48

2 Answers2

1

Passing a profile as a parameter depends on how you run your app. Be careful, the doc you mentioned is referring to the maven spring-boot plugin.

  • With maven plugin : mvn spring-boot:run -Dspring-boot.run.jvmArguments=-Dspring.profiles.include=MASTER
  • Classic java app : java -Dspring.profiles.include=MASTER -jar ./myapp.jar

In both cmd line, you can pass more than one parameter, if separated by a ,. See the documentation: https://docs.spring.io/spring-boot/docs/current/reference/html/spring-boot-features.html#boot-features-external-config-profile-specific-properties

Since the upgrade, you now have to define your custom profile annotation like this :

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME) // Only this one is really needed
@Profile("SLAVE")
public @interface SlaveProfile {
}

Explaination:
In java, an annotation has a RetentionPolicy, which is similar to a scope. (See this: https://docs.oracle.com/javase/7/docs/api/java/lang/annotation/RetentionPolicy.html).
Without any RetentionPolicy set, the default behavior is an annotation not visible for the JVM (i.e at runtime).

When you want to run your application, you first compile it, which implies converting your .java files into .class file. Your class is only a bunch of byte code, converting your human readable file into a computer language.

Then, when Spring is loading the ApplicationContext, what it does under the hood, among many other things, is reading your .class files. During this process (see class name: org.springframework.asm.ClassReader) Spring loads the annotations that you declare. With what I've said above, during the Runtime, you end up with "two kinds" of annotations :

  • InvisibleAnnotation: @Retention(RetentionPolicy.COMPILE)
  • VisibleAnnotation: @Retention(RetentionPolicy.RUNTIME)

To conclude and understand why it was working before:

  • Spring-boot 2.1.0uses spring-core-5.1.2, which interprets at runtime the visible and invisible annotations, which explain why your @SlaveProfile and @MasterProfile have the expected behaviour.
  • Spring-boot 2.2.0uses spring-core-5.2.0, which interprets at runtime ONLY the visible annotations, which explain why your @SlaveProfile and @MasterProfile haven't the expected behaviour.

Let's say that Spring "silently" fixed a bug that was reading Invisible Annotation when they shouldn't, but didn't mention it.

Hope it helps!

RUARO Thibault
  • 2,672
  • 1
  • 9
  • 14
  • I run the app as u did in your second example. Still, it seems that it doesnt pass the param. Please notice, It happens only after I change the springboot version in my pom.xml. It happened because of his upgrade.. – JeyJ Jan 06 '20 at 16:53
  • Please provide your exact command line. What are you seeing when you run your app ? Do you see something like : `The following profiles are active: MASTER` at the begging of your console ? – RUARO Thibault Jan 06 '20 at 17:10
  • I see this message but with other profiles (test,native) I dont see the master profile in that line. I cant share the whole command but I'm using the parameter I mentioned in my comment. It must be something related to the upgrade of spring version – JeyJ Jan 06 '20 at 17:15
  • so I need to pass the profile in a different command and use the annotation @Retention(RetentionPolicy.RUNTIME) ? or just the annotation is enough ? Isnt the problem is that I dont see the profile in the msg "the following profiles are active..." – JeyJ Jan 06 '20 at 17:23
  • If you only add @Retention(RetentionPolicy.RUNTIME), then your `profiles` will be activated. You said you were using the "second example", which is `java -D...`, but the release notes mentioned **Spring Boot applications that are ran by the Maven plugin are now forked by default**. Which, you are not apparently. – RUARO Thibault Jan 06 '20 at 17:28
  • can u explain how did u think that this annotation will solve it ? – JeyJ Jan 07 '20 at 09:24
  • @JeyJ I added the explanation. If you have any questions, let me know – RUARO Thibault Jan 07 '20 at 13:42
  • Amazing. Thank you for your help and for the gr8 explanation ! – JeyJ Jan 07 '20 at 14:06
-3

Adding @Profile will not stop the bean from being instantiated. This is causing the exception. Add @Primary to any beans that the application should not default to.

Ex, add @Primary to the MasterProfile bean.

mad_fox
  • 3,030
  • 5
  • 31
  • 43