I'm trying some stuff with Spring Framework and I would like to know how Spring can inject singleton dependency with method call when using java configuration?
Example :
#Configuration
public class AppConfiguration {
#Bean
public BlogRepository blogRepository() {
return new BlogRepositoryImpl();
}
#Bean
#Scope("prototype")
public BlogService blogService() {
return new BlogServiceImpl(blogRepository());
}
#Bean
public AuthorService authorService() {
return new AuthorServiceImpl(blogRepository());
}
}
I know that this class is also a bean and it is proxied by Spring but, how can Spring always get the existing BlogRepository singleton since I call blogRepository() from within the class and so proxy can't handle the call?
When you annotate class with #Configuration, methods annotated with #Bean are proxy wrapped by CGLIB.
If it’s the first call of this method, then the original method’s body will be executed and the resulting object will be stored in the Spring context. All subsequent calls just return the bean retrieved from the context.
What makes you think proxy cannot handle the call?
Spring could generate a proxy similar to this subclass:
class AppConfigurationProxy extends AppConfiguration {
private BlogRepository blogRepository;
#Override
public BlogRepository blogRepository() {
if (blogRepository == null)
blogRepository = super.blogRepository();
return blogRepository;
}
// same for the other two #Bean methods
}
Now, no matter how many times a method inside AppConfiguration calls it's own blogRepository() method, it will always get the same object.
UPDATE: Proof that above would work.
Simple Bean interfaces
public interface BlogRepository {
}
public interface BlogService {
}
public interface AuthorService {
}
Simple Bean classes
They don't have any actual logic, just a toString() implementation that shows the "identity" of the object, similar to the default toString() implementation in class Object.
public class BlogRepositoryImpl implements BlogRepository {
#Override
public String toString() {
return "BlogRepositoryImpl#" + Integer.toHexString(hashCode());
}
}
public class BlogServiceImpl implements BlogService {
private BlogRepository blogRepository;
public BlogServiceImpl(BlogRepository blogRepository) {
this.blogRepository = blogRepository;
}
#Override
public String toString() {
return "BlogServiceImpl#" + Integer.toHexString(hashCode()) + "[blogRepository=" + this.blogRepository + "]";
}
}
public class AuthorServiceImpl implements AuthorService {
private BlogRepository blogRepository;
public AuthorServiceImpl(BlogRepository blogRepository) {
this.blogRepository = blogRepository;
}
#Override
public String toString() {
return "AuthorServiceImpl#" + Integer.toHexString(hashCode()) + "[blogRepository=" + this.blogRepository + "]";
}
}
Configuration class
As defined in the question.
public class AppConfiguration {
public BlogRepository blogRepository() {
return new BlogRepositoryImpl();
}
public BlogService blogService() {
return new BlogServiceImpl(blogRepository());
}
public AuthorService authorService() {
return new AuthorServiceImpl(blogRepository());
}
}
Proxy class as String could have implemented it
Same as at top of the answer, just completed with all the methods.
public class AppConfigurationProxy extends AppConfiguration {
private BlogRepository blogRepository;
private BlogService blogService;
private AuthorService authorService;
#Override
public BlogRepository blogRepository() {
if (this.blogRepository == null)
this.blogRepository = super.blogRepository();
return this.blogRepository;
}
#Override
public BlogService blogService() {
if (this.blogService == null)
this.blogService = super.blogService();
return this.blogService;
}
#Override
public AuthorService authorService() {
if (this.authorService == null)
this.authorService = super.authorService();
return this.authorService;
}
}
Test
public class Test {
public static void main(String[] args) {
// Show result without proxy
AppConfiguration config = new AppConfiguration();
System.out.println(config.blogRepository());
System.out.println(config.blogService());
System.out.println(config.authorService());
// Show how only one BlogRepository is craeted when proxy is used
config = new AppConfigurationProxy();
System.out.println(config.blogRepository());
System.out.println(config.blogService());
System.out.println(config.authorService());
}
}
Output
BlogRepositoryImpl#1e81f4dc
BlogServiceImpl#7960847b[blogRepository=BlogRepositoryImpl#6a6824be]
AuthorServiceImpl#2c13da15[blogRepository=BlogRepositoryImpl#77556fd]
BlogRepositoryImpl#9e89d68
BlogServiceImpl#3b192d32[blogRepository=BlogRepositoryImpl#9e89d68]
AuthorServiceImpl#16f65612[blogRepository=BlogRepositoryImpl#9e89d68]
As can be seen, the first part, which didn't use the proxy, ends up with 3 different instances of BlogRepositoryImpl.
With the use of the Proxy, only one instance of BlogRepositoryImpl is created, and shared even though blogService() calls blogRepository() "directly".
Related
I have code as follows
public class A {
#Inject
private Supplier<Set<String>> set;
.
.
}
I am writing test cases for this class, where my Unit test looks like :
#Test(groups = "MyApp.unit")
public class ATest extends someOtherClass {
#Inject
private A a;
& my unit config looks like
#Configuration
#Import({someClass.class})
public class UnitTestConfig {
...
#Bean
public Supplier<Set<String>> set() {
Supplier<Set<String>> items = () -> Sets.newHashSet("100");
return items;
}
#Bean
public A a() {
return new A();
}
}
I am unable to inject Supplier bean into my class A. Had put debug points and tried testing, it enters the bean function but Supplier class doesnt get created. It gives the message "Class has no fields"
All other beans seem fine. Does anyone have any insights on this?
If you're already using the #Configuration to configure the bean, why won't you use constructor injection?
I suggest you to rewrite the A class as follows:
public class A {
private final Supplier<Set<String>> sup;
public A(Supplier<Set<String>> sup) {
this.sup = sup;
}
}
Now you can use one of the following options:
#Configuration
public class SampleConfig {
#Bean
public Supplier<Set<String>> set () {
return () -> Set.of("a","b", "c");
}
#Bean
public A a (Supplier<Set<String>> sup) {
return new A(sup);
}
}
Or another way:
#Configuration
public class SampleConfig {
#Bean
public Supplier<Set<String>> set () {
return () -> Set.of("a","b", "c");
}
#Bean
public A a () {
return new A(set());
}
}
In the second option I call set() as if its a regular method. Spring handles classes annotated with #Configuration, so its a "special" call used for injection.
Since a supplier is a singleton, multiple calls to this set method (say, from different beans) will return the same instance of the Supplier.
The first method is more flexible though, because it allows to keep the definition of the supplier and class A in different configuration files, the second way assumes that they're both defined in the same #Configuration.
Update (based on Op's Comments)
With field injection it will work just the same:
public class A {
#Autowired
private Supplier<Set<String>> sup;
public Set<String> getSet() {
return this.sup.get();
}
}
#Configuration
public class SampleConfig {
#Bean
public Supplier<Set<String>> set () {
return () -> Set.of("a","b", "c");
}
#Bean
public A a () {
return new A();
}
// this is called right before the application gets started (after all the injections are done - this is just for the sake of illustration)
#EventListener(ApplicationReadyEvent.class)
public void onReady(ApplicationReadyEvent evt) {
A a = evt.getApplicationContext().getBean(A.class);
System.out.println(a.getSet()); // this will return your set
}
}
I am new to spring boot. So I am working on a simple application where I have 2 services EnglishLanguageService and SpanishLanguageService. In the end, I would like to print Hello and Hola when the app runs but I am getting the error
Parameter 0 of constructor in controller.ConstructorInjectedController required a single bean, but 2 were found:
- profileEnglishLanguageService: defined in file [/Users/user/Downloads/depdency-injection-example/target/classes/services/EnglishLanguageService.class]
- profileSpanishLanguageService: defined in file [/Users/user/Downloads/depdency-injection-example/target/classes/services/SpanishLanguageService.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
I am not sure where I am going wrong. I will paste my code down below.
MainApplication
#SpringBootApplication
#ComponentScan(basePackages= {"controller" , "services"})
public class DepdencyInjectionExampleApplication {
public static void main(String[] args) {
// returns application context
ApplicationContext ctx = SpringApplication.run(DepdencyInjectionExampleApplication.class, args);
System.out.println(ctx.getBean(PropertyInjectedController.class).getGreeting());
System.out.println(ctx.getBean(ConstructorInjectedController.class).getGreetings());
}
ConstructorInjectedController class
#Component
public class ConstructorInjectedController {
private final GreetingService greetingService;
public ConstructorInjectedController(GreetingService greetingService) {
this.greetingService = greetingService;
}
public String getGreetings() {
return greetingService.sayGreetings();
}
PropertyInjectedControllerClass
#Controller
public class PropertyInjectedController {
public GreetingService greetingService;
public String getGreeting() {
return greetingService.sayGreetings();
}
}
GreetingService interface
public interface GreetingService {
String sayGreetings();
}
EnglishLanguageService class
#Service
public class EnglishLanguageService implements GreetingService {
private GreetingService greetingService;
public EnglishLanguageService(#Qualifier("english")
GreetingService greetingService){
this.greetingService = greetingService;
}
#Override
public String sayGreetings() {
return greetingService.sayGreetings();
}
EnglishServiceImpl
#Profile("english")
public class EnglishServiceImpl implements GreetingService {
#Override
public String sayGreetings() {
return "Hello";
}
SpanishLanguageService
#Service
public class SpanishLanguageService implements GreetingService {
private GreetingService greetingService;
public SpanishLanguageService(#Qualifier("spanish")
GreetingService greetingService){
this.greetingService = greetingService;
}
#Override
public String sayGreetings() {
return greetingService.sayGreetings();
}
SpanishLanguageServiceImpl
#Profile("spanish")
public class SpanishServiceImpl implements GreetingService {
#Override
public String sayGreetings() {
return "Hola";
}
}
You are using the annotations wrong. #Profile is used for something completely other than you think so it should be removed.
You are basically saying to spring "please inject a specific greeting service in class X please".
Right now spring doesn't know which bean you want since you have two different ones implementing the same interface. So we use the #Qualifier annotation to tag a bean, and also to target it during injection.
Fist your implementation should have either a #Service or a #Component so that spring finds it during the component scan, and instantiates it as a spring managed bean. This combined with a #Qualifier so that we tag it, so we later can pick this tag during injection:
#Component
#Qualifier("english")
public class EnglishServiceImpl implements GreetingService {
#Override
public String sayGreetings() {
return "Hello";
}
}
Here we inject the greeting service and use our qualifier to tell spring which bean we want to inject of the available ones.
#Service
public class EnglishLanguageService implements GreetingService {
private GreetingService greetingService;
public SpanishLanguageService(#Qualifier("english") GreetingService greetingService){
this.greetingService = greetingService;
}
#Override
public String sayGreetings() {
return greetingService.sayGreetings();
}
}
I want to use #Qualifier to dynamically specifying parameters? how to do it ?
#Qualifier("two") 'two' as a parameter ,can be 'one' 'three' or other.
Can i use aop dynamically design 'two'?
means I want to change the name of service with a #Qualifier by parameters.
the parameter from the url 'Token'.
case: url: http://localhost:8080/insert/order, token has a parameter: companyId = one
#RestController
public class ApiWebService {
#Autowired
#Qualifier("two")
//#Qualifier("one")
private BaseService baseService;
#GetMapping("insert/order")
public void test() {
baseService.insertOrder();
}
}
#Service("one")
public class CompanyOneService extends BaseService {
#Override
public void insertOrder() {
System.out.println("conpanyOne");
System.out.println("baseInsertOrder");
}
}
#Service("two")
public class CompanyTwoService extends BaseService {
#Override
public void insertOrder(){
System.out.println("companyTwo");
System.out.println("baseInsertOrder");
}
}
three
four
...
#Service
public class BaseService {
public void insertOrder(){
System.out.println("baseInsertOrder");
}
}
你好 !
No you cannot , mostly because the attribute in Java annotation does not allow to assign with variables.
Actually you want to choose an implementation to use based on some runtime conditions(i.e.companyId in your case). You can achieve it using factory pattern with #Configuration and #Bean which is much more elegant and easier to understand than your ugly AOP solution:
First define a factory:
#Configuration
public class ServiceFactory{
#Bean
public BaseService companyOneService(){
return new CompanyOneService();
}
#Bean
public BaseService companyTwoService(){
return new CompanyTwoService();
}
public BaseService getService(Integer companyId){
if(companyId == 1){
return companyOneService();
}else if(company==2){
return companyTwoService();
}else{
//blablablab
}
}
}
In the controller , inject the ServiceFactory to get the related Service based on the the company Id
#RestController
public class ApiWebService {
#Autowired
private ServiceFactory serviceFactory;
#GetMapping("insert/order")
public void test() {
Integer companyId = getCompanyIdFromToken(httpServletRequest);
BaseService service = serviceFactory.getService(companyId);
service.blablabla();
}
}
Inject (autowire) ApplicationContext into your class and use one of getBeans* method to find the exact bean you need.
aspect
#Aspect
#Component
public class ApiAspect {
#Pointcut("execution(* com.example.demo.control.ApiWebService.*(..))")
public void apiInputWebService() {
}
#Before("apiInputWebService()")
public void apiInputAuth(JoinPoint joinPoint) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder
.getRequestAttributes())
.getRequest();
String token = request.getHeader("Authorization");
//compangId can be from token
String compangId = "one";
Object target = joinPoint.getTarget();
Method method = target.getClass().getMethod("before", String.class);
method.invoke(target, compangId);
}
}
control
#RestController
public class ApiWebService {
private ApiService baseService;
#Autowired
private ApplicationContext applicationContext;
public void before(String company) {
baseService = (ApiService) applicationContext.getBean(company);
}
#GetMapping("insert/order")
public void test() {
baseService.insertOrder();
}
}
service
#Service
public class ApiService {
public void insertOrder(){
System.out.println("baseInsertOrder");
}
}
#Service("one")
public class CompanyOneService extends ApiService {
#Override
public void insertOrder() {
System.out.println("conpanyOne");
System.out.println("baseInsertOrder");
}
}
#Service("two")
public class CompanyTwoService extends ApiService {
#Override
public void insertOrder(){
System.out.println("companyTwo");
System.out.println("baseInsertOrder");
}
}
So far, I had a very simple bean definition that looked like this:
#Bean
#Conditional(value=ConditionClass.class)
SomeInterface myMethodImpl(){
return new ImplementationOne();
}
However, I now have situation where additional implementation class has been added, let's call it ImplementationTwo, which needs to be used instead of ImplementationOne when the option is enabled in configuration file.
So what I need is something like this:
#Bean
#Conditional(value=ConditionClass.class)
SomeInterface myMethodImpl(){
return context.getEnvironment().getProperty("optionEnabled") ? new
ImplementationOne() : new ImplementationTwo();
}
Basically a way to instantiate correct implementation at bean definition time based on the configuration value. Is this possible and can anyone please provide an example? Thanks
It is possible to implement this without using #Conditional.
Assuming you have a Interface SomeInterface and two implementations ImplOne ImplTwo:
SomeInterface.java
public interface SomeInterface {
void someMethod();
}
ImplOne.java
public class ImplOne implements SomeInterface{
#Override
public void someMethod() {
// do something
}
}
ImplTwo.java
public class ImplTwo implements SomeInterface{
#Override
public void someMethod() {
// do something else
}
}
Then you can control which implementation is used in a configuration class like this:
MyConfig.java
#Configuration
public class MyConfig {
#Autowired
private ApplicationContext context;
#Bean
public SomeInterface someInterface() {
if (this.context.getEnvironment().getProperty("implementation") != null) {
return new ImplementationOne();
} else {
return new ImplementationTwo();
}
}
}
Make sure that the component scan of spring finds MyConfig. Then you can use #Autowired to inject the right implementation anywhere else in your code.
I think you are doing it wrong.
You should use #Conditional() on your implementation and not on your Interface.
Here is how I would do it :
The interface you will use on your code.
MyInterface.java
public interface MyInterface {
void myMethod();
}
The first implementation :
MyInterfaceImplOne.java
#Bean
#Conditional(MyInterfaceImplOneCondition.class)
public class MyInterfaceImplOne implements MyInterface {
void myMethod(){
// dosmthg
}
}
MyInterfaceImplOneCondition.java
public class MyInterfaceImplOneCondition implements Condition {
#Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
return context.getEnvironment().getProperty("optionEnabled")
}
}
And for the 2nd implementation :
MyInterfaceImplTwo.java
#Bean
#Conditional(MyInterfaceImplTwoCondition.class)
public class MyInterfaceImplTwo implements MyInterface {
void myMethod(){
// dosmthg 2
}
}
MyInterfaceImplTwoCondition.java
public class MyInterfaceImplTwoCondition implements Condition {
#Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
return !context.getEnvironment().getProperty("optionEnabled")
}
}
In that case, you now just have to call the interface, and Spring will inject the bean corresponding to the right condition.
Hope it is what you are looking for, and I was clear enough!
I am attempting to figure out how to use a custom annotation and HK2 to inject something into a Resource method. Because I'm in a Spring webapp environment, I just piled on the existing helloworld-spring-webapp Jersey 2 example. My problem is, is the Resource method is called twice. The first time, the injection happens successfully, the second time, it does not.
InjectionResolver.resolve() method
#Override
public Object resolve(Injectee injectee, ServiceHandle<?> root) {
return "THIS HAS BEEN INJECTED APPROPRIATELY";
}
Binder.configure() method
#Override
protected void configure() {
bind(SampleInjectionResolver.class).to(new TypeLiteral<InjectionResolver<SampleParam>>() {}).in(Singleton.class);
}
ResourceConfig registering of binder
public MyApplication () {
register(new SampleInjectionResolver.Binder());
...
JerseyResource.getHello()
#GET
#Produces(MediaType.TEXT_PLAIN)
public String getHello(#SampleParam String inject) {
System.err.println("EXECUTING!");
System.err.println("*******************************INJECTED: " + inject);
return inject;
}
Server output from a SINGLE call
EXECUTING!
*******************************INJECTED: THIS HAS BEEN INJECTED APPROPRIATELY
EXECUTING!
*******************************INJECTED:
Have I missed a configuration somewhere? I can't figure out why it's being called twice. I'm assuming if I fix that, the issue of the InjectionResolver not working on the 2nd call will be a non-issue.
I faced with the exactly same issue - Twice call of the annotated resource method.
After deep investigation, I have found the way, how to use custom annotation in the Jersey 2.x.
Custom annotation class (TestMessage.java):
#Documented
#Retention(RetentionPolicy.RUNTIME)
#Target({ ElementType.PARAMETER })
public #interface TestMessage {}
Custom annotation resolver (TestMessageResolver.java):
public class TestMessageResolver {
// InjectionResolver implementation
public static class TestMessageInjectionResolver extends ParamInjectionResolver<TestMessage> {
public TestMessageInjectionResolver() {
super(TestMessageValueFactoryProvider.class);
}
}
// ValueFactoryProvider implementation
#Singleton
public static class TestMessageValueFactoryProvider extends AbstractValueFactoryProvider {
#Inject
public TestMessageValueFactoryProvider(MultivaluedParameterExtractorProvider mpep, ServiceLocator injector) {
super(mpep, injector, Parameter.Source.UNKNOWN);
}
#Override
protected Factory<?> createValueFactory(Parameter parameter) {
Class<?> classType = parameter.getRawType();
if (classType == null || (!classType.equals(String.class))) {
return null;
}
return new AbstractContainerRequestValueFactory<String>() {
#Override
public String provide() {
return "testString";
}
};
}
}
// Binder implementation
public static class Binder extends AbstractBinder {
#Override
protected void configure() {
bind(TestMessageValueFactoryProvider.class).
to(ValueFactoryProvider.class).
in(Singleton.class);
bind(TestMessageInjectionResolver.class).
to(new TypeLiteral<InjectionResolver<TestMessage>>(){}).
in(Singleton.class);
}
}
}
Custom annotation usage (JerseyResource.java):
#Path("jersey")
public class JerseyResource {
#GET
#Produces(MediaType.TEXT_PLAIN)
public String getMethod(#TestMessage String message) {
return "getMethod(), message=" + message;
}
}
Resolver registration(SpringJerseyApplication.java):
public class SpringJerseyApplication extends ResourceConfig {
public SpringJerseyApplication() {
register(JerseyResource.class);
register(new TestMessageResolver.Binder());
}
}
Hope it will be helpful :)