I have the following bean with parameterized constructor:
#Component
public class AuthHelper{
#Autowired
private AuthClient gmailClient;
#Autowired
private AuthClient yahooClient;
private AuthClient client;
public AuthHelper client(String option) {
if(option.equals("gmail")) this.client=gmailClient;
if(option.equals("yahoo")) this.client=yahooClient;
return this;
}
public boolean authLogic(String uid, String pass) {
return client.authorize(uid,pass);
}
}
Could you please help to autowire the above bean:
I am stuck while I call the above bean in the below service,
#Service
public class AuthService{
#Autowired
public AuthHelper authHelper;
public boolean authenticate(String uid, String pass){
return authHelper.client("gmail").authLogic(uid, pass);
}
}
Please suggest... I want the helper class should use the bean based on the parameter that I pass from the service.
After Modification:
The above example is working fine. Please suggest if there is any issue in this implementation...
IMO, the better approach would be to have a AuthHelperFactory which should provide the AuthHelper bean with appropriate client as per input.
public class AuthHelper{
private AuthClient client;
public AuthHelper (AuthClient client) {
this.client = client;
}
public boolean authLogic(String uid, String pass) {
return this.client.authorize(uid,pass);
}
}
#Component
public class AuthHelperFactory {
#Autowired
private AuthClient gmailClient;
#Autowired
private AuthClient yahooClient;
public AuthHelper getAuthHelper(String option) {
if(option.equals("gmail")){
return new AuthHelper(gmailClient);
} else if (option.equals("yahoo")){
return new AuthHelper(yahooClient);
}
}
}
In the AuthService, you need to call the factory method in authenticate method.
return authHelperFactory.getAuthHelper("gmail").authLogic(uid, pass);
Related
Hi I have this simple code for my Spring Boot Project:
#Component
public class UserRowMapper implements RowMapper<User> {
#Value("${bug.value}")
private String id;
#Value("${wrong.value}")
private String userName;
#Override
public User mapRow(ResultSet rs, int rowNum) throws SQLException {
return User.builder()
.id(rs.getInt(id))
.userName(rs.getString(userName)).build();
}
}
what I want is to create a simple Mockito Test that will check #Value strings like so:
#ExtendWith(MockitoExtension.class)
class UserRowMapperTest {
#Mock
Environment environment;
#Mock
ResultSet resultSet;
#InjectMocks
UserRowMapper userRowMapper;
#Test
void testMapRow() {
when(environment.getProperty("user.id")).thenReturn("id");
when(environment.getProperty("user.userName")).thenReturn("userName");
try {
final User user = userRowMapper.mapRow(resultSet, anyInt());
// check if its ok
} catch (SQLException throwables) {
throwables.printStackTrace();
}
}
}
But I can't find a simple way to check if the value I injected is what I expect.
any ideas?
Unfortunately, there is no mocking mechanism for Spring's #Value. However, you can use a simple workaround using ReflectionUtils that serves for this purpose according to the JavaDoc:
ReflectionTestUtils is a collection of reflection-based utility methods for use in unit and integration testing scenarios.
There are often times when it would be beneficial to be able to set a non-public field, invoke a non-public setter method, or invoke a non-public configuration or lifecycle callback method when testing code involving
ReflectionTestUtils.setField(userRowMapper, "id", "my-id-value");
ReflectionTestUtils.setField(userRowMapper, "userName", "my-userName-value");
JavaDoc for ReflectionTestUtils#setField(Object, String, Object).
Add getter methods for id and userName fields instead of mocking Environment class.
#Component
public class UserRowMapper implements RowMapper<User> {
#Value("${bug.value}")
private String id;
#Value("${wrong.value}")
private String userName;
#Override
public User mapRow(ResultSet rs, int rowNum) throws SQLException {
return User.builder()
.id(rs.getInt(getId()))
.userName(rs.getString(getUserName())).build();
}
public String getId() {
return id;
}
public String getUserName() {
return userName;
}
}
While mocking:
Mockito.when(userRowMapper.getId()).thenReturn("id");
Mockito.when(userRowMapper.getUserName()).thenReturn("userName");
Also, you can use TestPropertySource annotation to provide altogether different properties file:
#SpringBootTest
#TestPropertySource(locations = "/application2.properties")
public class TestClassTest {
#Autowired
TestClass testClass;
#Test
public void test() {
assertEquals("id", testClass.getId());
}
}
I would rather suggest to you to do not use inline #Value annotation on the consumer class. As you have seen, the class testability decreases.
You can solve your problem simply creating a #Configuration bean and injecting it to the UserRowMapper class. In this way, using DI you can easily mock the configuration in your tests.
See below a naïve implementation.
#Configuration
public class UserRowMapperConfiguration {
#Value("${bug.value}")
private String id;
#Value("${wrong.value}")
private String userName;
public String getId() {
return id;
}
public String getUserName() {
return userName;
}
}
#Component
public class UserRowMapper implements RowMapper<User> {
private UserRowMapperConfiguration configuration;
public UserRowMapper (UserRowMapperConfiguration configuration) {
this.configuration = configuration;
}
#Override
public User mapRow(ResultSet rs, int rowNum) throws SQLException {
return User.builder()
.id(rs.getInt(this.configuration.getId()))
.userName(rs.getString(this.configuration.getUserName())).build();
}
}
#ExtendWith(MockitoExtension.class)
class UserRowMapperTest {
#Mock
UserRowMapperConfiguration configuration;
#Mock
ResultSet resultSet;
#InjectMocks
UserRowMapper userRowMapper;
#Test
void testMapRow() {
when(configuration.getId()).thenReturn("id");
when(configuration.getUserName()).thenReturn("userName");
try {
final User user = userRowMapper.mapRow(resultSet, anyInt());
// check if its ok
} catch (SQLException throwables) {
throwables.printStackTrace();
}
}
}
As thepaoloboi suggested use a configuration class to hold all your configs.
Now to test that your config is pointing to the right #Value key, you create an integration test by simply loading that object using spring without loading the whole context. That way it'll be as fast as a unit test.
Here's an example:
#ExtendWith(SpringExtension.class)
#Import(UserRowMapperConfiguration.class)
#TestPropertySource(properties = { "user.id=id" , "user.userName=userName"})
class UserRowMapperConfigurationTest {
#Autowired
UserRowMapperConfiguration userRowMapperConfiguration;
#Test
void test() {
assertEquals("id",userRowMapperConfiguration.getId());
assertEquals("userName",userRowMapperConfiguration.getUserName());
}
}
and Configuration class:
#Configuration
public class UserRowMapperConfiguration {
#Value("${bug.value}")
private String id;
#Value("${wrong.value}")
private String userName;
public String getId() {
return id;
}
public String getUserName() {
return userName;
}
}
I want to create Clazz, where I can create two Beans with the same class, but with the different configuration.
public class Clazz {
//same class : Client, inside has the different configuration
//inicilized by methods
#Bean(name="Bean1")
public Client1 (){}
#Bean(name = "Bean2")
public Clien2t (){}
}
Then I want to inject them in other classes
public class ClassForInjectBean1{
#Autowired
#Qualifier("Bean1")
#NotNull
Client client
....
}
public class ClassForInjectBean2{
#Autowired
#Qualifier("Bean2")
#NotNull
Client client
....
}
I have tried this construction in classes ClassForInjectBean1 and ClassForInjectBean2
#Resource(name = "Bean2")
#NotNull
Client client
and
#Autowired
#Qualifier("Bean2")
But spring does not understand
Ошибка :
Parameter 1 of constructor in ClassForInjectBean1 required a single bean, but 2 were found:
- Bean1: defined by method 'Client1' in class path resource...
- Bean2: defined by method ''Client2' in class path resource...
Why I can't do that?
I know that there is this way https://www.baeldung.com/spring-qualifier-annotation, but I don't to create many classes and interfaces.
Try to use #Configuration.
Indicates that a class declares one or more #Bean methods and may be
processed by the Spring container to generate bean definitions and
service requests for those beans at runtime
I provided some example for you.
#Configuration
public class Cfg {
#Bean("client1")
public Client client1() {
return new Client("client1");
}
#Bean("client2")
public Client client2() {
return new Client("client2");
}
}
public class Client {
private String name;
public Client(String name) {
this.name = name;
}
#Override
public String toString() {
return "Client{" +
"name='" + name + '\'' +
'}';
}
}
#Component
public class InjectionTest {
#Component
public class ClassForInjectBean1 {
private final Client client;
public ClassForInjectBean1(#Qualifier("client1") Client client) {
this.client = client;
}
#PostConstruct
public void testInit() {
System.out.println(client.toString());
}
}
#Component
public class ClassForInjectBean2 {
private final Client client;
public ClassForInjectBean2(#Qualifier("client2") Client client) {
this.client = client;
}
#PostConstruct
public void testInit() {
System.out.println(client.toString());
}
}
}
Output would be:
Client{name='client2'}
Client{name='client1'}
I have my service class which does a post call. I would like to instantiate that bean/ autowire it to create a object in another class which is not a component or configuration class.
#Service
public class SavePayload {
// Rest Post Call implementation
}
public class PayloadRecord
implements Record {
private String payload;
PayloadProcessor payloadProcessor = new PayloadProcessor();
public PayloadRecord(String payload) {
this.payload = payload;
}
#SneakyThrows
#Override
public boolean isValid() throws ValidationException {
payloadProcessor.savePayload(payload);
return true;
}
#Override
public byte[] getBytes(Charset charset) {
return payload.getBytes(StandardCharsets.UTF_8);
}
#Override
public String getID() {
return payload;
}
#Override
public String toString() {
return payload;
}
private static class PayloadProcessor {
#Autowired
private SavePayload savePayload;
}
}
I'm using a template which will do the record processing. As soon as I got message received I'm assigning it to Payload in Payload Record which is non component class. I would like to initialize the SavePayload service. Save payload service is returning null.
Create an application context aware class so you can get the current context, something like:
#Component
public class ContextAwareClass implements ApplicationContextAware {
private static ApplicationContext ctx;
public static ApplicationContext getApplicationContext() {
return ctx;
}
#Override
public void setApplicationContext(ApplicationContext applicationContext) {
ctx = applicationContext;
}
}
Then, just get the context and get the bean like:
public class YourRegularNoSpringComponentClass {
public void doSomething() {
System.out.println(ContextAwareClass
.getApplicationContext()
.getBean("savePayload")
);
}
}
Above will print the bean if it exist in your context. In your case you would simple use it rather than print it.
Hope this helps!
You will have to create an instance of ApplicationContext
You can explore
AnnotationConfigApplicationContext applicationContext= new AnnotationConfigApplicationContext();
and then use.
SavePayload savePayload = applicationContext.getBean("savePayload");
How do you create a prototype-scoped #Bean with runtime arguments? With getBean(String name, Object... args)?
My question is a consequence of this question.
Why is this approach not used or mentioned in the Spring IoC documentation?
Is this a normal approach? Is there a more correct approach for create a prototype #Bean with runtime arguments?
If it is not normal approach, so could you explain why?
Pay attention, what i need set my arguments through constructor, not through setters.
#Autowired
private ApplicationContext appCtx;
public void onRequest(Request request) {
//request is already validated
String name = request.getParameter("name");
Thing thing = appCtx.getBean(Thing.class, name);
//System.out.println(thing.getName()); //prints name
}
-
public class Thing {
private final String name;
#Autowired
private SomeComponent someComponent;
#Autowired
private AnotherComponent anotherComponent;
public Thing(String name) {
this.name = name;
}
public String getName() {
return this.name;
}
}
In terms of constructor injection, no. However, you can give Thing an init method and use an ObjectFactory:
#Autowired
private ObjectFactory<Thing> thingFactory;
public void onRequest(Request request) {
//request is already validated
Thing thing = thingFactory.getObject();
thing.init("name");
//System.out.println(thing.getName()); //prints name
}
With thing being:
#Component
#Scope("prototype")
public class Thing {
private String name;
#Autowired
private SomeComponent someComponent;
#Autowired
private AnotherComponent anotherComponent;
public init(String name) {
this.name = name;
}
}
Unfortunately, the name can't be final since it's not via the constructor. Would love to see if there are better ways to do this with constructor injection.
It is possible with the help of a bean factory:
#Configuration
public class ThingProvider {
#Bean
#Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public Thing create(String name) {
return new Thing(name);
}
}
Usage:
#Component
public class SomeBean {
#Autowired
private ThingProvider thingProvider;
public void onRequest(Request request) {
String name = request.getParameter("name");
Thing thing = myProvider.create(name);
}
}
What is often brought forward as an argument against appCtx.getBean(Thing.class, name) is that it requires to hard-wire Spring specific classes. Plus, there is no compile time check in case the Thing constructor changes.
In that case, your Thing class might mix constructor dependencies and runtime arguments, something like this:
public class Thing {
private final SomeComponent someComponent;
private final AnotherComponent anotherComponent;
private final String name;
#Autowired
public Thing(SomeComponent someComponent,
AnotherComponent anotherComponent,
String name) {
this.someComponent = someComponent;
this.anotherComponent = anotherComponent;
this.name = name;
}
public String getName() {
return this.name;
}
}
Then you can wrap them in configuration:
#Configuration
public class BeanConfig {
#Autowired
private SomeComponent someComponent;
#Autowired
private final AnotherComponent anotherComponent;
#Bean
#Scope(BeanDefinition.SCOPE_PROTOTYPE)
public Thing createThing(String name) {
return new Thing(someComponent, anotherComponent, name);
}
}
Usage:
#Autowired
private BeanFactory beanFactory;
public void onRequest(Request request) {
String name = request.getParameter("name");
Thing thing = beanFactory.getBean(Thing.class, name);
}
It supposed to work, not sure if this is a good practice though.
#Component
#Qualifier("SUCCESS")
public class RandomServiceSuccess implements RandomService{
public String doStuff(){
return "success";
}
}
#Component
#Qualifier("ERROR")
public class RandomServiceError implements RandomService{
public String doStuff(){
throw new Exception();
}
}
the calling code
#Controller
public class RandomConroller {
#Autowired
private RandomService service;
public String do(){
service.doStuff();
}
}
What I need to do here is to have them swapped based on a value can be retrieved from some custom http header from a http request. Thank you!
I'm totally agree with Sotirios Delimanolis that you need to inject all the implementations and choose one of them at runtime.
If you have many implementations of RandomService and don't want to clutter RandomController with selection logic, then you can make RandomService implementations responsible for selection, as follows:
public interface RandomService{
public boolean supports(String headerValue);
public String doStuff();
}
#Controller
public class RandomConroller {
#Autowired List<RandomService> services;
public String do(#RequestHeader("someHeader") String headerValue){
for (RandomService service: services) {
if (service.supports(headerValue)) {
return service.doStuff();
}
}
throw new IllegalArgumentException("No suitable implementation");
}
}
If you want to define priorities for different implementations, you may use Ordered and put the injected implementations into a TreeSet with OrderComparator.
Qualifier should be used to specify which instance of the interface you want injected in the field after specifying different IDs for each one. Following #Soritios' advice you could do something like:
#Component("SUCCESS")
public class RandomServiceSuccess implements RandomService{
public String doStuff(){
return "success";
}
}
#Component("ERROR")
public class RandomServiceError implements RandomService{
public String doStuff(){
throw new Exception();
}
}
#Component
public class MyBean{
#Autowired
#Qualifier("SUCCESS")
private RandomService successService;
#Autowired
#Qualifier("ERROR")
private RandomService successService;
....
if(...)
}
...or you could obtain just the instance you want from the application context based on your parameter:
#Controller
public class RandomConroller {
#Autowired
private ApplicationContext applicationContext;
public String do(){
String myService = decideWhatSericeToInvokeBasedOnHttpParameter();
// at this point myService should be either "ERROR" or "SUCCESS"
RandomService myService = applicationContext.getBean(myService);
service.doStuff();
}
}
You can just inject both and use the one you need.
#Inject
private RandomServiceSuccess success;
#Inject
private RandomServiceError error;
...
String value = request.getHeader("some header");
if (value == null || !value.equals("expected")) {
error.doStuff();
} else {
success.doStuff();
}