Suppose I have the following Spring MVC controller
#RestController
#RequestMapping("/books")
public class BooksController {
#Autowired
private final BooksRepository booksRepository;
#RequestMapping(value="/search", method=RequestMethod.POST, consumes="application/json")
public Collection<Book> doSearch(#RequestBody final SearchCriteria criteria) {
return booksRepository.find(criteria);
}
}
and the following repository
#Service
public class BooksRepository {
#Autowired
private final JdbcTemplate jdbcTemplate;
#Autowired
private final SearchQueryBuilder searchQueryBuilder;
public Collection<BookLite> find(final SearchCriteria criteria) {
// TODO: will this cause race conditions?
searchQueryBuilder.clear().addKeywords(criteria.getKeywords());
final String query = searchQueryBuilder.buildQuery();
final Object[] params = searchQueryBuilder.buildParams();
return jdbcTemplate.query(query, params, new BookExtractor());
}
}
with the following SearchQueryBuilder implementation
#Component
public class SearchQueryBuilder {
private final List<String> keywords = new LinkedList<String>();
public SearchQueryBuilder clear() {
keywords.clear();
return this;
}
public SearchQueryBuilder addKeywords(final List<String> keywords) {
for (String keyword : keywords) {
add(keyword);
}
return this;
}
private SearchQueryBuilder add(final String keyword) {
keywords.add(keyword);
return this;
}
public String buildQuery() {
...
}
public Object[] buildParams() {
...
}
}
My concerns are the following. Since the SearchQueryBuilder class is not thread safe, injecting it this way will probably cause race conditions. What is a good way to handle this? Is it enough (and a good practice) to change the bean scope to e.g. request?
I would use SearchQueryBuilderFactory as a Spring bean, and create the SearchQueryBuilder instances on the fly.
I would avoid creating Spring beans that change state during the execution.
Your reliance on having them request-scoped makes your solution more fragile and error-prone, since the problem would reappear if you try to use it as Spring bean outside the web context.
Related
I implemented a validation using the chain of responsibility pattern. The request payload to validate can have different parameters. The logic is: if the payload has some parameters, validate it and continue to validate other, else throw an exception. In a level of the validation chain I need to call other services, and here comes into play the Dependency Injection.
The validation structure is like a tree, starting from top to bottom.
So, the class where I need to start the Validation
#Service
public class ServiceImpl implements Service {
private final .....;
private final Validator validator;
public ServiceImpl(
#Qualifier("lastLevelValidator") Validator validator, .....) {
this.validator = validator;
this...........=............;
}
/...../
private void validateContext(RequestContex rc) {
Validator validation = new FirstLevelValidator(validator);
validation.validate(rc);
}
}
So the Validator Interface
public interface Validator<T> {
void validate(T object);
}
The validation classes that implements Validator
#Component
public class FirstLevelValidator implements Validator<RequestContext>{
private final Validator<RequestContext> validator;
#Autowired
public FirstLevelValidator(#Qualifier("lastLevelValidator") Validator<RequestContext> validator) {
this.validator = validator;
}
#Override
public void validate(RequestContext requestContext) {
if ( requestContext.getData() == null ) {
LOGGER.error(REQUEST_ERROR_MSG);
throw new BadRequestException(REQUEST_ERROR_MSG, INVALID_CODE);
}
if (requestContex.getData() == "Some Data") {
Validator validator = new SecondLevelValidator(this.validator);
validator.validate(requestContext);
} else {/* other */ }
}
#Component
public class SecondLevelValidator implements Validator<RequestContext>{
private final Validator<RequestContext> validator;
#Autowired
public SecondLevelValidator(#Qualifier("lastLevelValidator") Validator<RequestContext> validator) {
this.validator = validator;
}
#Override
public void validate(RequestContext requestContext) {
if ( requestContext.getOption() == null ) {
LOGGER.error(REQUEST_ERROR_MSG);
throw new BadRequestException(REQUEST_ERROR_MSG, INVALID_CODE);
}
if ( requestContext.getOption() == " SOME " ) {
validator.validate(requestContext); //HERE WHERE I CALL THE Qualifier
}
}
#Component
public class LastLevelValidator implements Validator<RequestContext>{
private final ClientService1 client1;
private final ClientService2 client2;
public LastLevelValidator(ClientService1 client1, ClientService2 client2) {
this.client1 = client1;
this.client2 = client2;
}
#Override
public void validate(RequestContext requestContext) {
Integer userId = client2.getId()
List<ClientService1Response> list = client1.call(requestContext.id(), userId);
boolean isIdListValid = list
.stream()
.map(clientService1Response -> clientService1Response.getId())
.collect(Collectors.toSet()).containsAll(requestContext.getListId());
if (!isIdListValid) {
LOGGER.error(NOT_FOUND);
throw new BadRequestException(NOT_FOUND, INVALID_CODE);
} else { LOGGER.info("Context List validated"); }
}
}
In the LastLevelValidator I need to call other services to make the validation, for that I inject into each validator class (First.., Second..) the #Qualifier("lastLevelValidator") object, so when I need to instantiate the LastLevelValidation class I can call it like validator.validate(requestContext); instance of validator.validate(ClientService1, ClientService2 ) that it would force me to propagate the ClientServices objects through all the chain from the ServiceImpl class.
Is it this a good solution ?
Is there any concern I didn't evaluate?
I tried also declaring the services I need to call for the validation as static in the LastLevelValidation, in the way that I can call it like LastLevelValidation.methodvalidar(), but look like not a good practice declares static objects.
I tried to pass the objects I need propagating it for each Validation class, but seems to me that if I need another object for the validation I have to pass it through all the validation chain.
I am working within an environment that changes credentials every several minutes. In order for beans that implement clients who depend on these credentials to work, the beans need to be refreshed. I decided that a good approach for that would be implementing a custom scope for it.
After looking around a bit on the documentation I found that the main method for a scope to be implemented is the get method:
public class CyberArkScope implements Scope {
private Map<String, Pair<LocalDateTime, Object>> scopedObjects = new ConcurrentHashMap<>();
private Map<String, Runnable> destructionCallbacks = new ConcurrentHashMap<>();
private Integer scopeRefresh;
public CyberArkScope(Integer scopeRefresh) {
this.scopeRefresh = scopeRefresh;
}
#Override
public Object get(String name, ObjectFactory<?> objectFactory) {
if (!scopedObjects.containsKey(name) || scopedObjects.get(name).getKey()
.isBefore(LocalDateTime.now().minusMinutes(scopeRefresh))) {
scopedObjects.put(name, Pair.of(LocalDateTime.now(), objectFactory.getObject()));
}
return scopedObjects.get(name).getValue();
}
#Override
public Object remove(String name) {
destructionCallbacks.remove(name);
return scopedObjects.remove(name);
}
#Override
public void registerDestructionCallback(String name, Runnable runnable) {
destructionCallbacks.put(name, runnable);
}
#Override
public Object resolveContextualObject(String name) {
return null;
}
#Override
public String getConversationId() {
return "CyberArk";
}
}
#Configuration
#Import(CyberArkScopeConfig.class)
public class TestConfig {
#Bean
#Scope(scopeName = "CyberArk")
public String dateString(){
return LocalDateTime.now().toString();
}
}
#RestController
public class HelloWorld {
#Autowired
private String dateString;
#RequestMapping("/")
public String index() {
return dateString;
}
}
When I debug this implemetation with a simple String scope autowired in a controller I see that the get method is only called once in the startup and never again. So this means that the bean is never again refreshed. Is there something wrong in this behaviour or is that how the get method is supposed to work?
It seems you need to also define the proxyMode which injects an AOP proxy instead of a static reference to a string. Note that the bean class cant be final. This solved it:
#Configuration
#Import(CyberArkScopeConfig.class)
public class TestConfig {
#Bean
#Scope(scopeName = "CyberArk", proxyMode=ScopedProxyMode.TARGET_CLASS)
public NonFinalString dateString(){
return new NonFinalString(LocalDateTime.now());
}
}
I would like to make use of prepared statements when executing CQL in my application. This functionality looks to be provided by the ReactiveCqlTemplate class, which I have passed into the ReactiveCassandraTemplate in my Cassandra configuration here:
#Configuration
#EnableReactiveCassandraRepositories(
basePackages = "com.my.app",
includeFilters = {
#ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE, classes = {ScyllaPersonRepository.class})
})
public class CassandraConfiguration extends AbstractReactiveCassandraConfiguration {
#Value("${cassandra.host}")
private String cassandraHost;
#Value("${cassandra.connections}")
private Integer cassandraConnections;
#Override
public CassandraClusterFactoryBean cluster() {
PoolingOptions poolingOptions = new PoolingOptions()
.setCoreConnectionsPerHost(HostDistance.LOCAL, cassandraConnections)
.setMaxConnectionsPerHost(HostDistance.LOCAL, cassandraConnections*2);
CassandraClusterFactoryBean bean = super.cluster();
bean.setJmxReportingEnabled(false);
bean.setPoolingOptions(poolingOptions);
bean.setLoadBalancingPolicy(new TokenAwarePolicy(new RoundRobinPolicy()));
return bean;
}
#Override
public ReactiveCassandraTemplate reactiveCassandraTemplate() {
return new ReactiveCassandraTemplate(reactiveCqlTemplate(), cassandraConverter());
}
#Bean
public CassandraEntityInformation getCassandraEntityInformation(CassandraOperations cassandraTemplate) {
CassandraPersistentEntity<Person> entity =
(CassandraPersistentEntity<Person>)
cassandraTemplate
.getConverter()
.getMappingContext()
.getRequiredPersistentEntity(Person.class);
return new MappingCassandraEntityInformation<>(entity, cassandraTemplate.getConverter());
}
#Override
public SchemaAction getSchemaAction() {
return SchemaAction.CREATE_IF_NOT_EXISTS;
}
public String getContactPoints() {
return cassandraHost;
}
public String getKeyspaceName() {
return "mykeyspace";
}
}
This is the ScyllaPersonRepository referenced in my Cassandra configuration filters.
public interface ScyllaPersonRepository extends ReactiveCassandraRepository<Person, PersonKey> {
#Query("select id, name from persons where id = ?0")
Flux<Object> findPersonById(#Param("id") String id);
}
After executing a few queries, the CQL Non-Prepared statements metric in my Scylla Monitoring Dashboard showed that I'm not using prepared statements at all.
I was able to use prepared statements after followed the documentation here which walked me through creating the CQL myself.
public class ScyllaPersonRepository extends SimpleReactiveCassandraRepository<Person, PersonKey> {
private final Session session;
private final CassandraEntityInformation<Person, PersonKey> entityInformation;
private final ReactiveCassandraTemplate cassandraTemplate;
private final PreparedStatementCache cache = PreparedStatementCache.create();
public ScyllaPersonRepository(
Session session,
CassandraEntityInformation<Person, PersonKey> entityInformation,
ReactiveCassandraTemplate cassandraTemplate
) {
super(entityInformation, cassandraTemplate);
this.session = session;
this.entityInformation = entityInformation;
this.cassandraTemplate = cassandraTemplate;
}
public Flux<ScyllaUser> findSegmentsById(String id) {
return cassandraTemplate
.getReactiveCqlOperations()
.query(
findPersonByIdQuery(id),
(row, rowNum) -> convert(row)
);
}
private BoundStatement findPersonByIdQuery(String id) {
return CachedPreparedStatementCreator.of(
cache,
QueryBuilder.select()
.column("id")
.column("name")
.from("persons")
.where(QueryBuilder.eq("id", QueryBuilder.bindMarker("id"))))
.createPreparedStatement(session)
.bind()
.setString("id", id);
}
private Person convert(Row row) {
return new Person(
row.getString("id"),
row.getString("name"));
}
}
But, I would really like the ORM to handle that all for me. Is it possible to configure this behaviour out of the box, so that I don't need to manually write the CQL myself but instead just enable it as an option in my Cassandra Configuration and get the ORM to orchestrate it all behind the scenes?
Frankly, I think this is a bug(request for enhancement) and it should be filed in Springs Jira.
It seems the repository simply doesn't support this out of box(nor did I find any config option how to flip it, but I might have missed it).
Actually, my theory was correct:
https://jira.spring.io/projects/DATACASS/issues/DATACASS-578?filter=allopenissues
so just add yourself and try to ask them for resolution.
I have a configuration object that is managed by Spring. Let's call that object 'ConfigurationObject'. The configuration contained by that object I also want to make accessible, through delegation, in objects which I instantiate with the 'new' operator. Let's call these objects 'UserObject'.
Would it then be acceptable to pass the configurationObject as an argument to the constructor of the UserObject and then assign it to a regular private field that is not managed by Spring? So that I can then use the ConfigurationObject to return configuration form the UserObject. See below for the story in code.
#Configuration
public class ConfigurationObject {
private final String configItem;
public ConfigurationObject(#Value("${config.item}") final String configItem){
this.configItem = configItem;
}
public String getConfigItem() {
return configItem;
}
}
public final class UserObject {
private ConfigurationObject configurationObject;
/* other properties */
public UserObject(final ConfigurationObject configurationObject) {
this.configurationObject = configurationObject;
}
public String getConfigItem(){
return configurationObject.getConfigItem();
}
}
Best regards,
Henk
You can get Spring class using context from ApplicationInitializer:
ApplicationInitializer.getAppContext().getBean(ConfigurationObject.class);
Or create class to get Spring context using ApplicationContextAware:
#Component
public class SpringContext implements ApplicationContextAware {
private static ApplicationContext context;
public static UserService getUserService() {
return (UserService)context.getBean("userService");
}
#Override
public void setApplicationContext(ApplicationContext context) throws BeansException {
// store ApplicationContext reference to access required beans later on
SpringContext.context = context;
}
}
Yes. It is a very valid use-case. I often do it when I need to create an object which some of its properties are determined during the runtime which do not know upfront.
I would suggest creating a factory method on ConfigurationObject for creating an UserObject:
#Configuration
public class ConfigurationObject {
private final String configItem;
public ConfigurationObject(#Value("${config.item}") final String configItem){
this.configItem = configItem;
}
public String getConfigItem() {
return configItem;
}
public UserObject createUserObject(){
return new UserObject(this);
}
}
I had already tried solutions mentioned in Why is my Spring #Autowired field null? yet the problem persists. I have tried annotating the class DevicePojo(code below) with #Configurable #Service.
Here are my beans
DistributionConfig.java
#Component
#Configuration
public class DistributionConfig {
#Qualifier("exponentialDistribution")
#Bean
#Scope("prototype")
public DistributionService exponentialDistribution() {
return new ExponentiallyDistribute();
}
#Qualifier("normalDistribution")
#Bean
#Scope("prototype")
public DistributionService normalDistribution() {
return new NormallyDistribute();
}
#Qualifier("uniformDistribution")
#Bean
#Scope("prototype")
public DistributionService uniformDistribution() {
return new UniformlyDistribute();
}
}
JsonFileConfig.java
#Configuration
public class JsonFileConfig {
private static ObjectMapper mapper=new ObjectMapper();
#Qualifier("devicesPojo")
#Bean
public DevicesPojo[] devicesPojo() throws Exception {
DevicesPojo[] devicePojo=mapper.readValue(new File(getClass().getClassLoader().getResource("Topo/esnet-devices.json").getFile()),DevicesPojo[].class);
return devicePojo;
}
#Qualifier("linksPojo")
#Bean
public LinksPojo[] linksPojo() throws Exception {
LinksPojo[] linksPojo=mapper.readValue(new File(getClass().getClassLoader().getResource("Topo/esnet-adjcies.json").getFile()),LinksPojo[].class);
return linksPojo;
}
}
Here is my DevicePojo where i get the null pointer exception.
#JsonDeserialize(using = DeviceDeserializer.class)
#Component
public class DevicesPojo {
private String device;
private List<String> ports;
private List<Integer> bandwidth;
#Autowired
#Qualifier("uniformDistribution")
private DistributionService uniformDistribution; // Here uniformDistribution is null
public DevicesPojo(String device, List<String> port, List<Integer> bandwidth) {
this.device = device;
this.ports= port;
this.bandwidth=bandwidth;
this.uniformDistribution.createUniformDistribution(1000,0,ports.size());
}
public String getDevice(){
return device;
}
public String getRandomPortForDevice()
{
return ports.get((int)uniformDistribution.getSample());
}
public List<String> getAllPorts(){
return ports;
}
public int getBandwidthForPort(String port){
return bandwidth.get(ports.indexOf(port));
}
}
However, if i replace private DistributionService uniformDistribution;with private DistributionService uniformDistribution=new UniformDistribution() the code is working fine.
There is a mix of problems here.
1. You create your DevicesPojo objects using JSON deserializer. Spring has no chance to interfere and inject the DistributionService.
2. Even if it could interfere, it would fail, since you are trying to use the 'distributionService' object in the constructor. Field injection would work only after an object is constructed.
Now regarding fixing the problems.
Long answer short - don't expect auto-injection in your POJOs.
Normally, dependencies like 'distributionService' in objects that are created on the fly, like your DevicesPojo are avoided altogether.
If you insist on having them, inject them manually at construction time:
class DevicesPojoFactory {
#Autowired #Qualifier("uniformDistribution")
private DistributionService uniformDistribution;
ObjectMapper mapper = new ObjectMapper();
DevicesPojo[] readFromFile(String path) {
DevicesPojo[] devicePojoArr = mapper.readValue(...);
for (DevicesPojo dp: devicePojoArr) {
dp.setDistribution(uniformDistribution);
}
}
}