Reputation: 6189
I've had this issue that i didn't know how to resolve. I made my Restful API using Spring Boot, and i am implementing the DTO-Domain-Entity pattern, so on this particular case i have this controller's method
@RequestMapping(method = RequestMethod.POST)
@ResponseBody
public ResponseEntity<UserResponseDTO> createUser(@RequestBody UserRequestDTO data) {
UserDomain user = this.mapper.map(data, UserDomain.class);
UserDomain createdUser = this.service.createUser(user);
UserResponseDTO createdUserDTO = this.mapper.map(createdUser, UserResponseDTO.class);
return new ResponseEntity<UserResponseDTO>(createdUserDTO, HttpStatus.CREATED);
}
public class UserDomain {
private Long id;
private Date createdDate;
private Date updatedDate;
private String username;
private String password;
@Value("${default.user.enabled:true}") // I have default-values.properties being loaded in another configuration file
private Boolean enabled;
}
I am transforming UserRequestDTO object to UserDomain. As i understand, UserRequestDTO is a bean that is being injected. Then i am transforming this to UserDomain, the problem here is that UserDomain object is not a component, so enabled attribute will not take the default value.
In the case i wouldn't want to handle UserDomain as a bean, how could i make spring to load default values (just enabled attribute in this case)?
It's not the same answer, since my goal is get it done using @Value annotations.
Anyways, Would it be a better way doing something like this instead Constantine suggested?
public class UserDomain {
@Autowired
private Environment environment;
private Boolean enabled;
UserDomain(){
this.enabled = environment.getProperty("default.user.enabled");
// and all the other ones
}
}
Upvotes: 19
Views: 25024
Reputation: 3257
If your mapper has a method that takes already prepared instance instead of Class
, then you can add the prototype-scoped UserDomain
bean and call context.getBean()
from the controller method.
Controller
...
@Autowired
private WebApplicationContext context;
@RequestMapping(method = RequestMethod.POST)
@ResponseBody
public ResponseEntity<UserResponseDTO> createUser(@RequestBody UserRequestDTO data) {
UserDomain user = this.mapper.map(data, getUserDomain());
UserDomain createdUser = this.service.createUser(user);
UserResponseDTO createdUserDTO = this.mapper.map(createdUser, UserResponseDTO.class);
return new ResponseEntity<UserResponseDTO>(createdUserDTO, HttpStatus.CREATED);
}
private UserDomain getUserDomain() {
return context.getBean(UserDomain.class);
}
...
Spring configuration
@Configuration
public class Config {
@Bean
public static PropertySourcesPlaceholderConfigurer properties() {
PropertySourcesPlaceholderConfigurer propConfigurer = new PropertySourcesPlaceholderConfigurer();
propConfigurer.setLocation(new ClassPathResource("application.properties"));
return propConfigurer;
}
@Bean
@Scope("prototype")
public UserDomain userDomain() {
return new UserDomain();
}
...
}
Otherwise, you can use @Configurable
and AspectJ compile-time weaving. But you have to decide if it is worth to introduce weaving in your project, since you have other ways to handle the situation.
pom.xml
...
<!-- additional dependencies -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>4.2.0.RELEASE</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjrt</artifactId>
<version>1.8.6</version>
</dependency>
...
<!-- enable compile-time weaving with aspectj-maven-plugin -->
<build>
<plugins>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>aspectj-maven-plugin</artifactId>
<version>1.7</version>
<configuration>
<complianceLevel>1.8</complianceLevel>
<encoding>UTF-8</encoding>
<aspectLibraries>
<aspectLibrary>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
</aspectLibrary>
</aspectLibraries>
<Xlint>warning</Xlint>
</configuration>
<executions>
<execution>
<goals>
<goal>compile</goal>
<goal>test-compile</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
...
UserDomain.java
@Configurable
public class UserDomain {
private Long id;
private Date createdDate;
private Date updatedDate;
private String username;
private String password;
@Value("${default.user.enabled:true}")
private Boolean enabled;
...
}
Spring configuration
@EnableSpringConfigured is the same as <context:spring-configured>
.
@Configuration
@EnableSpringConfigured
public class Config {
@Bean
public static PropertySourcesPlaceholderConfigurer properties() {
PropertySourcesPlaceholderConfigurer propConfigurer = new PropertySourcesPlaceholderConfigurer();
propConfigurer.setLocation(new ClassPathResource("application.properties"));
return propConfigurer;
}
...
}
Please consult Spring documentation for more information on AspectJ and @Configurable.
Regarding your edit.
Please note that you use @Autowired
there. It means that UserDomain
instances have to be managed by the Spring container. The container is not aware about instances created outside of it, so @Autowired
(exactly as @Value
) will not be resolved for such instances, e.g. UserDomain userDomain = new UserDomain()
or UserDomain.class.newInstance()
. Thus, you still have to add a prototype-scoped UserDomain
bean to your context. Effectively, it means that the proposed approach is similar to the @Value
-associated approach, except that it ties your UserDomain
to Spring Environment
. Therefore, it is bad.
It is still possible to craft a better solution using Environment
and ApplicationContextAware
without tying your domain objects to Spring.
ApplicationContextProvider.java
public class ApplicationContextProvider implements ApplicationContextAware {
private static ApplicationContext applicationContext;
public static <T> T getEnvironmentProperty(String key, Class<T> targetClass, T defaultValue) {
if (key == null || targetClass == null) {
throw new NullPointerException();
}
T value = null;
if (applicationContext != null) {
System.out.println(applicationContext.getEnvironment().getProperty(key));
value = applicationContext.getEnvironment().getProperty(key, targetClass, defaultValue);
}
return value;
}
public void setApplicationContext(ApplicationContext applicationContext) {
this.applicationContext = applicationContext;
}
}
UserDomain.java
public class UserDomain {
private Boolean enabled;
public UserDomain() {
this.enabled = ApplicationContextProvider.getEnvironmentProperty("default.user.enabled", Boolean.class, false);
}
...
}
Spring configuration
@Configuration
@PropertySource("classpath:application.properties")
public class Config {
@Bean
public ApplicationContextProvider applicationContextProvider() {
return new ApplicationContextProvider();
}
...
}
However, I do not like the additional complexity and sloppiness of this approach. I think it is not justified at all.
Upvotes: 7
Reputation: 11223
Don't you have a service layer? Preferences, parameters, default values and so on should be injected into service classes which are the ones centralizing business logic and they should be managed by Spring.
If you don't have a UserService
, then load the default value into the controller.
I just notice the conversion from DTO to the domain class is taking place in the controller.
Define
@Value("${default.user.enabled:true}")
private Boolean defaultUserEnabled;
inside the controller and then
if (user.isEnabled() == null)
user.setEnabled(defaultUserEnabled);
But, as I already said, both the declaration and the setting of the default value belong to a Spring-managed service class.
Upvotes: 1