I am using a #Configuration to configure the creation of a RestTemplate bean, that needs some information such as API-key and host etc.
The thing is, I need to be able to make a number of beans, matching a number of API-keys, fetched from a database.
My code right now, looks like this:
#Configuration
public class DandomainProperties {
private String apiKey;
private String host;
private String ordersPath;
private String orderPath;
private String manufacturerPath;
private DanDomainRestTemplate danDomainRestTemplate;
#Bean
DanDomainRestTemplate danDomainRestTemplate(RestTemplateBuilder restTemplateBuilder) {
return restTemplateBuilder
.basicAuthentication("", this.apiKey)
.build(DanDomainRestTemplate.class);
}
So basically, I need to change the configuration and then create a matching RestTemplate bean, any number of times.
As far I as know from your question, you want to create a restTemplate for each individual situation, maybe you could replace an #Configuration bean provider with a Service that provides different kinds of apis?
For example:
#Service
public class DandomainApiProvider {
#Autowired
private ApiRepository apiRepository;
#Autowired
private DanDomainRestTemplate danDomainRestTemplate;
DanDomainRestTemplate restTemplateOf(String queryId) {
// Fetch apikeys from DB by repository
String apiKey = apiRepository.queryApiKey(queryId);
return restTemplateBuilder
.basicAuthentication("", apiKey)
.build(DanDomainRestTemplate.class);
}
With DanDomainRestTemplate your own implementation, and ApiRepository some definition like:
public interfadce ApiRepository {
// Query apiKey by ID
String queryApiKey(String queryId);
}
Related
Let's say I have this code structure:
public class NotificationService {
public void send(Notification notification) {
// call other services and send the notification
}
}
public class OrderNotification implements Notification {
#Autowired
public TranslationService translationService;
private String orderNumber;
public OrderNotification(String orderNumber) {
this.orderNumber = orderNumber;
}
public String getMessage() {
return translationService.trans('notification.order', new Object[]{orderNumber});
}
}
So, my goal is to use the NotificationService in this way:
notificationService.send(new OrderNotification(orderNumber));
But I know that code above won't work, because of the translationService won't be resolved.
My goal is to pass custom parameters to my Notification classes and being able to use services inside that class. What is the best way to do it in the Spring?
I know that below is not the correct answer to your question. It is however a bad design pattern to combine Entities and Services. An Entity should only contain information about the object and not business logic. A Service contains all the business logic.
You need to separate your Service from your Entity.
OrderNotification looks like a regular entity. The entity should not contain business logic. You need a specific service for the business logic.
public class OrderNotification implements Notification {
private String orderNumber;
public OrderNotification(String orderNumber) {
this.orderNumber = orderNumber;
}
public String getMessage() {
return "Order number: " + orderNumber;
}
//Getter & Setters
...
}
#Service
public class NotificationService {
#Autowired
public TranslationService translationService;
public void send(Notification notification) {
//I do not know what trans accepts, so I assume it can accept Notification
translationService.trans(notification.getMessage());
}
}
If you really need to combine the entity and service - Then I recommend this approach:
#Service
public class Master{
#Autowired
NotificationService notificationService
public void testMethod(){
Notification notification = notificationService.createOrder("order1");
notificationService.send(notification);
}
}
#Service
public class NotificationService {
#Autowired
public TranslationService translationService;
public Notification createOrder(String orderNumber){
return new OrderNotification(orderNumber, translationService);
}
public void send(Notification notification) {
// call other services and send the notification
notification.getMessage();
}
}
public class OrderNotification implements Notification {
private TranslationService translationService;
private String orderNumber;
//I have changed this constructor to accept TranslationService.
public OrderNotification(String orderNumber, TranslationService translationService) {
this.orderNumber = orderNumber;
this.translationService = translationService;
}
public String getMessage() {
return translationService.trans('notification.order', new Object[]{orderNumber});
}
}
You have few options available:
Configure AOP and load time weaving to process Spring annotations on objects created with new keyword. This is explained in the docs 5.8.1. Using AspectJ to dependency inject domain objects with Spring.
Declare OrderNotification as a prototype scoped bean and obtain each instance from the context using BeanFactory.getBean(Class<T> requiredType, Object... args) method.
String orderNumber = "123";
OrderNotificaton = factory.getBean(OrderNotificaton.class, orderNumber);
Drop the #Autowired and use plain constructor injection.
public OrderNotification(TranslationService translationService, String orderNumber) {
this.translationService = Objects.requireNonNull(translationService);
this.orderNumber = Objects.requireNonNull(orderNumber);
}
If you only require simple #Autowired I'd go with option 3. It's the simplest approach and makes writing unit tests easier as you don't have to depend on Spring.
I have a little SpringBoot Application, which can execute different functions via OpenLdap.
getUser
createUser
deleteUser
etc.
That works fine. Now i want to create an application.Yml, where i can manage different environments with different credentials. I read some tutorials, but i still have some understanding problems. Actually my code looks like that:
UserController:
...
protected static String serverURL = "xxxxx:90xx";
protected static String LdapBindDn = "cn=admin, xxxxx";
protected static String LdapPassword = "xxxx";
...
#RequestMapping(value = "/{userid:.+}",method = RequestMethod.GET,consumes="application/json",produces = "application/json")
public UserData getUser(#PathVariable String userid) {
DirContext context = connectToLdap();
//some operations...
context.close();
return user;
}
... // same for the other functions
My plan is now, that i want to specify the credentials in an extra application.yml instead of at the beginning of the UserController (see above).
Then i have created an application.yml in the src/main/resources:
# Actual environment
spring:
profiles.actives: development
---
# Dev Profile
spring:
profiles: dev
datasource:
serverUrl: ldaps://xxxxxx:90xx
AdminName: xxxx
AdminPassword: xxxxxx
BaseDN: xxxxx
---
# Production Profile
spring:
profiles: prod
datasource:
serverUrl: ldaps://xxxx2:90xx
AdminName: xxxxx2
AdminPassword: xxxxx2
BaseDN: xxxxxx
Now i need to call this configuration. I have read in one tutorial (http://therealdanvega.com/blog/2017/06/26/spring-boot-configuration-using-yaml) that i have to create an extra class "ApplicationProperties" for the properties of the .yml file.
#Component
#ConfigurationProperties("datasource")
public class ApplicationProperties {
private String serverURL;
private String adminName;
private String adminPassword;
private String baseDN;
// Getter-/Setter-Methods
}
Now i need to define my variables from the beginning with the values from the .yml, right? I went back to my UserController and tried something like that:
private String serverURL;
private String adminName;
private String adminPassword;
private String baseDN;
#Autowired
ApplicationProperties appProp;
#RequestMapping(value = "/{userid:.+}",method = RequestMethod.GET,consumes="application/json",produces = "application/json")
public UserData getUser(#PathVariable String userid) {
DirContext context = connectToLdap();
//some operations...
context.close();
return user;
}
... // same for the other functions
private DirContext connectToLdap(){
System.out.prinln(appProp.getServerURL());
System.out.prinln(appProp.getAdminName());
System.out.prinln(appProp.getAdminPassword());
.... // Code for the Ldap connection
}
But the variable "appProp" is still empty. I know, that here is somewhere a big understanding problem. I don't know how to call these properties from the .yml file.
Thanks for every help in advance!
You can get properties from your .yml file by creating a config class:
application.yml
datasource:
serverUrl: ldaps://xxxxxx:90xx
adminName: xxxx
adminPassword: xxxxxx
baseDN: xxxxx
ApplicationConfig.java class :
#Configuration
#EnableConfigurationProperties
#ConfigurationProperties(prefix = "datasource")
public class ApplicationConfig {
private String serverUrl;
private String adminName;
private String adminPassword;
private String baseDN;
//getters setters
}
Then you can call your ApplicationConfig class
#Autowired
public ApplicationConfig app;
public UserData getUser(#PathVariable String userid) {
DirContext context = connectToLdap();
//some operations...
appProp.getServerUrl();
appProp.getAdminName();
context.close();
return user;
}
And I recommend you to create profile based properties as spring boot picks them automatically, like application-{profile}.{properties|yml}
You can create application-production.yml file and set your profile by adding #Profile("production") annotation in your class.
Spring Boot | MyBatis
When I try to declare a mybatis mapper in controller, it gets underlined by IDE, and doesn't compile.
#Controller
#RequestMapping("demo")
#MapperScan("com.sample.mapper")
public class MessageController {
private static final String MESSAGE = "message";
private static final String INDEX = "index";
#Autowired
private MessageMapper messageMapper;
#RequestMapping("printMessage/{message}")
public String printMessage(ModelMap modelMap) {
modelMap.addAttribute(MESSAGE, "M");
return INDEX;
}
#RequestMapping("printHello")
public String printHello(ModelMap modelMap) {
modelMap.addAttribute(MESSAGE, "Hello, ");
return INDEX;
}
I got this class compiled somehow recently, however, when I try to use messageMapper instance, like messageMapper.insert() as it's not assigned any value, it gives me NullPointerException. It seems like Spring is for some reason is not working for me.
According to the documencation, I think the #MapperScan is not the right class, they cannot be autowired because they are not in the context on controller creation time. When it is defined in Mybatis XML config file, it is loaded with an Sql Session Factory Provider, a place that actually makes more sense, then it shall not be different with annotations style.
I have add some config inside my application.yml file and I want to read it from my Java code.
The added node inside the YAML file looks like this:
myConfig:
projectOne:
mantisID: 501
user: username
password: passwd
projectTwo:
mantisID: 502
user: username
password: passwd
What I want is to get a List of Project objects where
Project.mantisID = 501,
Project.user = "username",
Project.password = "passwd",
etc...
I know spring can read this file with some #Value annotation but how can I use this in order to get what I need?
You can use #ConfigurationProperties annotation to map your configuration to a Bean, then you'll be able to inject your Bean anywhere and fetch those properties.
To do so, first create a class which represents the data structure in your configuration. Then annotate it with #ConfigurationProperties and #Configuration annotations.
#Configuration
#ConfigurationProperties
public class MyConfig {
private final Map<String, Project> myConfig = new HashMap<>();
public Map<String, Project> getMyConfig() {
return myConfig;
}
public static class Project {
private String mantisID;
private String password;
private String user;
// Getters and setters...
}
}
Note that getters and setters are required in the Project class. Also keep in mind that naming of getters and setters is important here.
After you have setup this class, you can inject it anywhere in your project and access its properties.
#Service
public class SomeService {
private final Map<String, MyConfig.Project> projects;
#Autowired
public SomeService(MyConfig config) {
this.projects = config.getMyConfig();
projects.get("projectOne").getMantisID();
projects.get("projectTwo").getPassword();
}
}
You can read more about this here.
Just to finish, I answered myself to my second question.
This is what my service looks like now :
#Service
public class MantisProjectService {
private final Map<String, MantisProjectConfiguration.Project> projects;
private List<MantisProjectConfiguration.Project> mantisProjects = new ArrayList<>();
#Autowired
public MantisProjectService(MantisProjectConfiguration mantisProjectConfiguration)
{
this.projects = mantisProjectConfiguration.getMantisProjectConfiguration();
for (Map.Entry<String, MantisProjectConfiguration.Project> project : projects.entrySet())
{
MantisProjectConfiguration.Project mantisProject = project.getValue();
mantisProject.setName(project.getKey());
mantisProjects.add(mantisProject);
}
}
public List<MantisProjectConfiguration.Project> getMantisProjects()
{
return mantisProjects;
}
}
It returns a List of all the projects. And it is awesome! =)
I'm having some trouble using my values from a .properties file.
My my-properties.properties file looks something like this:
email.host=smtp.googlemail.com
email.port=465
Then my Configuration file looks like this:
#Configuration
#PropertySource("classpath:my-properties.properties")
class MyProperties{
#Bean
public static PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer(){
return new PropertySourcesPlaceholderConfigurer();
}
}
And then I'm trying to use it in this Email class:
#Component("MyProperties.class")
public class AutomatedEmail {
private String recipient;
private String fullName;
private String tempPassword;
private Email email;
#Value("email.from")
private String from;
...
public AutomatedEmail(){
}
public AutomatedEmail(final String recipient, final String fullName, final String tempPassword) throws EmailException {
this.recipient = recipient;
this.fullName = fullName;
this.tempPassword = tempPassword;
}
But it is always coming back saying its null. I've also tried an Autowired approach and setting up the entire email object in the MyProperties class, but that is null also after I call my Constructor
You need to surround the name in the properties file with curly brackets and a dollar sign to make a Spring expression.
#Value("${email.from}")
There's more info in this tutorial on spring values
Edit: Note that this will only work if the bean has been instantiated and managed by the Spring container. You won't be able to inject values into the bean if you just call new Email();.
Read through the spring doco on bean IoC to get a better understanding. And a bit more info on how to instantiate beans.