As I see, I can use autosearch annotation #Service to create singleton to use that via #Inject. Like:
#Service
class MyService {
//.....
}
#Service
class MyOtherService {
#Inject MyService myService;
//.....
}
But would like to create Service using options that depends on environment.
I could do that using AbstractBinder like:
final ResourceConfig resourceConfig = new ResourceConfig()
.register(new AbstractBinder() {
#Override
protected void configure() {
String someOption = "optionOne";
String anotherOption = "optionTwo";
MyService myService = new MyService.create(someOption, anotherOption);
bind(MyService).to(MyService.class).in(Singleton.class);
}
})
But how can I do just the same but using annotation autoconfig style? Without creating AbstractBinder object.
Related
How to override #Configuation which is present under src/main/java with #TestConfiguration during unit tests?
#Configuration
public class AppConfig {
#Bean
public EmployeeService employeeService(){
return new EmployeeService();
}
}
#Component
public class ServerStartSetup implements CommandLineRunner {
#Autowired
private EmployeeService employeeService;
public void run(String... args) {
// do something with employee service
}
}
I would like to override the above bean with some below custom bean for testing purposes.
#TestConfiguration
public class TestAppConfig {
#Bean
public EmployeeService employeeService(){
return new FakeEmployeeService();
}
}
#SpringBootTest
#Import(TestAppConfig.class)
public class UnitTest {
}
However AppConfig does not seem to be skipped. That is , it throws an error saying that there is a bean with same name employeeService. If I rename bean method name in the TestAppConfig, it injects the bean created via AppConfig.
How to fix this.?
Note: One possible solution is using #Profile. I am looking for anything other than using Profiles.
I tested locally and found that changing the method name or #Bean to #Bean("fakeEmployeeService") and adding the #Primary annotation works.
#SpringBootTest
class DemoApplicationTests {
#Autowired
private EmployeeService employeeService;
#TestConfiguration
static class TestConfig {
//#Bean("fakeEmployeeService")
#Bean
#Primary
public EmployeeService employeeServiceTest() {
return new EmployeeService() {
#Override
public void doSomething() {
System.out.println("Do something from test...");
}
};
}
}
...
}
If we want to override a bean definition in #TestConfiguration, we need:
To use the same name as the overridden bean. (Otherwise it would be an "additional" bean and we could get conflict/'d have to qualify/primary)
Since spring-boot:2.1: spring.main.allow-bean-definition-overriding=true (set this in tests ONLY!plz!)
#ref
Then, with:
#TestConfiguration
public class TestAppConfig {
#Bean // when same name, no #Primary needed
public EmployeeService employeeService(){ // same name as main bean!
return new FakeEmployeeService();
}
}
We can do that:
#Import(TestAppConfig.class)
#SpringBootTest(properties = "spring.main.allow-bean-definition-overriding=true")
public class UnitTest {
... // EmployeeService will be "fake", the rest is from "main config"
You can mock the AppConfig bean in your test like this:
#MockBean
private AppConfig config;
Or, like you said, just use profiles.
I have a main SpringBootApplication class here:
package com.example.springproj;
#SpringBootApplication
public class App {
public static void main(String[] args) {
SpringApplication.run(App.class, args);
}
}
#RestController class here:
package com.example.springproj.controller;
#RestController
#Api("Sample")
public class RefDataController {
#Autowired
#Qualifier("RefDataServiceImpl")
private RefDataService refDataService;
#GetMapping(path = {"/refdata"}, produces = {"application/json"})
public ResponseEntity<Configuration> getRefData() {
// etc
}
}
The controller autowires this interface:
package com.example.springproj.service;
public interface RefDataService {
Configuration getConfiguration(String param);
}
Which is implemented by this class:
package com.example.springproj.services;
#Service
public class RefDataServiceImpl implements RefDataService {
#Autowired
private ConfigRepository config;
#Value("${ENV}")
private String environment;
#Override
public Configuration getConfiguration(String param) {
// etc
}
}
But when I run the App.java file, I get this
***************************
APPLICATION FAILED TO START
***************************
Description:
Field refDataService in com.citi.icrm.risk.springproj.controller.RefDataController required a bean of type 'com.citi.icrm.risk.springproj.service.RefDataService' that could not be found.
The injection point has the following annotations:
- #org.springframework.beans.factory.annotation.Autowired(required=true)
- #org.springframework.beans.factory.annotation.Qualifier(value=RefDataServiceImpl)
Action:
Consider defining a bean of type 'com.citi.icrm.risk.springproj.service.RefDataService' in your configuration.
I'm reasonably sure that this auto-wiring should work, and I'm not sure how to go about configuring this bean in a Spring boot app. What am I doing wrong?
EDIT: Things I've already tried include:
Removing all of the #Qualifier annotations
#RestController
#Api("Sample")
public class RefDataController {
#Autowired
private RefDataServiceImpl refDataService;
#GetMapping(path = {"/refdata"}, produces = {"application/json"})
public ResponseEntity<Configuration> getRefData() {
System.err.println("testing.");
return new ResponseEntity<Configuration>(refDataService.getConfiguration("EEMS_USER_DETAIL_URL"), HttpStatus.OK);
}
}
public class RefDataServiceImpl implements RefDataService {
#Autowired
private ConfigRepository config;
#Value("${ENV}")
private String environment;
#Override
public Configuration getConfiguration(String param) {
try {
return config.getConfiguration(param, environment);
} catch (Exception e) {
e.printStackTrace();
throw (RuntimeException) new RuntimeException().initCause(e);
}
}
}
Changing the bean names to match convention
#RestController
#Api("Sample")
public class RefDataController {
#Autowired
#Qualifier("refDataServiceImpl")
private RefDataService refDataService;
#GetMapping(path = {"/refdata"}, produces = {"application/json"})
public ResponseEntity<Configuration> getRefData() {
System.err.println("testing.");
return new ResponseEntity<Configuration>(refDataService.getConfiguration("EEMS_USER_DETAIL_URL"), HttpStatus.OK);
}
}
#Service("refDataServiceImpl")
public class RefDataServiceImpl implements RefDataService {
#Autowired
private ConfigRepository config;
#Value("${ENV}")
private String environment;
#Override
public Configuration getConfiguration(String param) {
try {
return config.getConfiguration(param, environment);
} catch (Exception e) {
e.printStackTrace();
throw (RuntimeException) new RuntimeException().initCause(e);
}
}
}
For reference, the files fall into the app's package structure like so:
com.example.springproj
-> com.example.springproj.controller
--> RefDataController
-> com.example.springproj.services
--> RefDataService
-> com.exampple.springproj.services.impl
---> RefDataServiceImpl
Here's the folder structure, since some people have asked:
Firstly, you don't need #Qualifier("RefDataServiceImpl") if you have only one implementation of the RefDataService interface.
You just need
#Autowired
private RefDataService refDataService;
Secondly, the name of a bean generated on a class name but starts with a lowercase letter. In your example, the name of bean will look like refDataServiceImpl.
So, you can autowired this bean like below
#Autowired
#Qualifier("refDataServiceImpl")
private RefDataService refDataService;
Thirdly, you can specify the name of bean
#Service("youBeanName")
public class RefDataServiceImpl implements RefDataService
and then autowired this bean by the name in you controller, for example
#RestController
#Api("Sample")
public class RefDataController {
#Autowired
#Qualifier("youBeanName")
private RefDataService refDataService;
//....
}
Change the #Service annotation on the RefDataServiceImpl class as follows:
#Service("RefDataServiceImpl")
public class RefDataServiceImpl implements RefDataService
The #Qualifier name in the autowired service does not match a bean in your spring configuration.
The default naming convention is the full path of the class.
Because of this,
the name that Spring is probably using in your config for the RefDataServiceImpl service is this: "com.example.springproj.services.RefDataServiceImpl".
Added:
This page might be a good read: https://www.baeldung.com/spring-qualifier-annotation.
Attempt Two:
Try this
#Service
#Qualifier("RefDataServiceImpl")
#Service("RefDataServiceImpl")
public class RefDataServiceImpl implements RefDataService
I fixed the issue by putting RefDataServiceImpl in the same package as RefDataService.Before this I was keeping it in a sub-folder to the main services package. I'm still certain that I should be able to make this work with an implementation sub-folder, but this works as a solution for now.
I ran into this same issue while trying to implement a class with a database query. Adding #Repository to the top of the implemented class solved my problem.
I'm trying to autowire a service in my rest controller like these:
rest controller:
#ApplicationPath("/greetings")
#Component(immediate = true, service = Application.class)
public class RestControllerApplication extends Application {
#Autowired
private MyService myService;
public Set<Object> getSingletons() {
return Collections.<Object>singleton(this);
}
#POST
#Path("/getUploadType")
#Produces("application/json")
public JsonObject getUploadType() {
...
myService.findUploadTypes();
...
}
}
service:
#Component
public class UploadService {
private static final Logger log = Logger.getLogger(UploadService.class);
#Autowired
private OneDAO oneDAO;
#Autowired
private TwoDAO twoDAO;
...
}
but in my rest controller, uploade service is null. Why?
Spring uses its own set of annotations. Instead of #Path plus #[HTTP method] you should use #RequestMapping.
You can find an example here
There is also an extended example here
I have got access to my bean, with these few line of code:
WebApplicationContext context = ContextLoader.getCurrentWebApplicationContext();
MyService myService = context.getBean(MyService.class);
You are declaring a UploadService as #Component but trying to autowire a MyService instance in your controller...
There are two options: you can declare correct service type in your controller or you can make UploadService inheriting from MyService.
I am trying to create my own #EnableXxx-style annotation (namely #EnableCustomizedPropertySources). For this the annotation imports the class CustomizedPropertySourcesConfiguration which in turn implements ImportAware, in order to have access to attributes of the #EnableCustomizedPropertySources annotation.
Annotation:
#Target(ElementType.TYPE)
#Retention(RetentionPolicy.RUNTIME)
#Import(CustomizedPropertySourcesConfiguration.class)
public #interface EnableCustomizedPropertySources {
String externalFolderName();
String propertiesFileName();
(...)
}
Imported configuration class:
#Configuration
public class CustomizedPropertySourcesConfiguration implements ImportAware {
protected AnnotationAttributes enableCustomizedPropertySourcesAttributes;
#Override
public void setImportMetadata(AnnotationMetadata importMetadata) {
Map<String, Object> attributes = importMetadata.getAnnotationAttributes(EnableCustomizedPropertySources.class.getName(), false);
this.enableCustomizedPropertySourcesAttributes = AnnotationAttributes.fromMap(attributes);
}
#Bean
public PropertySourcesPlaceholderConfigurer propertySource() {
return (...);
}
}
The problem is, the method setImportMetadata is not invoked by Spring when I annotate some #Configuration class with my #EnableCustomizedPropertySources annotation, so I cannot access the annotations attributes.
The ImportAware class (CustomizedPropertySourcesConfiguration) needs both:
#Configuration
#Component
I have some Jpa repositories and several Entity class. I need to create a helper object for one of my Entity. Inside that helper I use #Autowire to access the Jpa repositories.
#Entity
class A {
#Transient
Helper helper;
...
}
class Helper {
A a;
#Autowired
CRepository repo;
public Helper(A a) {
this.a = a;
}
}
However, the repo is always null. I've tried using SpringBeanAutowiringSupport.processInjectionBasedOnCurrentContext(this) and #Configurable, but both of them failed. Can anybody provide some hint for me?
BTW, A is instantiated inside a rest controller.
Thanks!.
You can use a BeanUtil class to get any bean that created in Springl
#Service
public class BeanUtil implements ApplicationContextAware {
private static ApplicationContext context;
#Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
context = applicationContext;
}
public static <T> T getBean(Class<T> beanClass) {
return context.getBean(beanClass);
}
}
Then you can get the bean.
MyBean obj = BeanUtil.getBean(MyBean.class);
Use constructor injection instead of field injection; this is a best practice all the time anyway. Then it's trivial to inject your A into the controller and pass it as a constructor argument.
#Configurable annotation works fine, but you need to use #EnableSpringConfigured annotation in any configuration class in order to make it work. Read my answer in other stackoverflow post: spring autowiring not working from a non-spring managed class
Entity class should not contain any helpers, even if transient. For a clean design you need to separate concerns, so the entity should not be aware of your business logic. I cannot help you more since I don't know which is the goal of that helper, but here you have other alternatives:
ALTERNATIVE 1 (based on your description seems that helper is an stateful bean, so it is not candidate to be a #Service, which I personally think it should be)
#Controller
public MyController {
#RequestMapping(...)
public void processRequest() {
A a = new A();
...
Helper helper = new Helper(a); // CRepository is successfully autowired
}
}
#Configurable(autowire = Autowire.BY_TYPE)
public class Helper {
A a;
#Autowired
CRepository repo;
}
#Configuration
#EnableSpringConfigured
public Application {
...
}
ALTERNATIVE 2 (make your Helper class stateless so that spring is aware of your beans without the need of extra stuff like #Confgurable/#EnableSpringConfigured)
#Controller
public MyController {
#Autowired Helper helper; // CRepository is correctly autowired
#RequestMapping(...)
public void processRequest() {
A a = new A();
...
helper.doSomething(a);
}
}
#Service
public class Helper {
// A a; remove dependency to A to make it stateless
#Autowired
CRepository repo;
public Helper() {
}
public void doSomething(A a) {
...
repo.save(a);
}
}
You cannot autowire nothing in your Helper class because it isn't managed by Spring.
You can use this approach:
public class HelperManager {
#Autowired
private ApplicationContext context;
public Helper getHelper(A a) {
return context.getBean(Helper.class, a);
}
Configure Helper to be a prototype bean:
#Configuration
public class MyConfiguration {
#Bean
public HelperManager helperManager() {
return new HelperManager();
}
#Bean
#Scope("prototype")
public Helper helper(A a) {
return new Helper(a);
}
}
And finally in your controller:
#Controller
public class MyController {
#Autowired
private HelperManager helperManager;
public someMethodWhereToInstanceYourHelper(A a) {
...
Helper helper = helperManager.getHelper(a);
...
}
}