Cache population in Spring controller - java

I want to call all the request mapping method(which has #Resource injection) before the server starts. How I can do this?
#Controller
public class ServiceController {
#Resource(name="userService")
private IUserService userService;
#RequestMapping("/getAllCountry")
public String getAllCountry() {
return userService.getAllCountry();
}
#RequestMapping("/getAllStates")
public String getAllStates() {
return userService.getStates();
}
#PostConstruct
public void cacheData(){
cache.put("ALL_COUNTRY_DATA", getAllCountry());
cache.put("ALL_STATE_DATA", getAllStates());
}
}
The above code fails and give me IllegalStateException. What is the best way to call the request mapping methods before the server is up and populate the cache.

Try using ApplicationListener in conjunction with ContextRefreshedEvent:
#Controller
public class ServiceController implements ApplicationListener<ContextRefreshedEvent> {
private static final Map<String, String> cache = new HashMap<>();
#Resource(name = "userService")
private IUserService userService;
#RequestMapping("/getAllCountry")
public String getAllCountry() {
return userService.getAllCountry();
}
#RequestMapping("/getAllStates")
public String getAllStates() {
return userService.getStates();
}
public void cacheData() {
cache.put("ALL_COUNTRY_DATA", getAllCountry());
cache.put("ALL_STATE_DATA", getAllStates());
}
#Override
public void onApplicationEvent(ContextRefreshedEvent event) {
cacheData();
}
}

Related

Factory design patter Spring Boot double bean

#Component
public abstract class CommandBase {
#Autowired
WebServiceProxy nbiService;
#Autowired
OperationCacheRepository cacheRepository;
public CommandBase(
WebServiceProxy nbiService,
OperationCacheRepository cacheRepository) {
this.nbiService = nbiService;
this.cacheRepository = cacheRepository;
}
public abstract void executeSPV(SpeedTestDTO stDTO) throws NBIException;
public abstract long executeGPV(long guid, OperationCache operationCache) throws NBIException;
#Slf4j
public class DownloadDiagnosticsCommand extends CommandBase {
public DownloadDiagnosticsCommand(WebServiceProxy nbiService, OperationCacheRepository cacheRepository) {
super(nbiService, cacheRepository);
}
#Override
public void executeSPV(SpeedTestDTO stDTO) throws NBIException {
// some executable code
}
#Override
public long executeGPV(long guid, OperationCache operationCache) throws NBIException {
// some executable code
}
}
#Slf4j
public class UploadDiagnosticsCommand extends CommandBase {
public UploadDiagnosticsCommand(WebServiceProxy nbiService, OperationCacheRepository cacheRepository) {
super(nbiService, cacheRepository);
}
#Override
public void executeSPV(SpeedTestDTO stDTO) throws NBIException {
// some executable code
}
#Override
public long executeGPV(long guid, OperationCache operationCache) throws NBIException {
//some executable code
}
}
#Component
public class RFACommandFactory {
#Autowired
WebServiceProxy nbiServiceProxy;
#Autowired
OperationCacheRepository cacheRepository;
public final CommandBase createCommand(final String measureType) {
if ("download".equalsIgnoreCase(measureType)) {
return new DownloadDiagnosticsCommand(nbiServiceProxy, cacheRepository);
} else if ("upload".equalsIgnoreCase(measureType)) {
return new UploadDiagnosticsCommand(nbiServiceProxy, cacheRepository);
}
return null;
}
}
Calling method executeSPV from abstract class
#RestController
#RequestMapping("/rfa/speedtest/v1")
#Slf4j
public class Controller {
#Autowired
CommandBase command;
#Autowired
RFACommandFactory rfaCommandFactory;
#PostMapping(value = "{id}", consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE)
#ResponseBody
private ResponseEntity<String> post(
#PathVariable String assetId,
#RequestBody Payload payload) {
log.info("Received new payload:{}", payload);
command = rfaCommandFactory.createCommand(speedTestDTO.getType());
try {
command.executeSPV(speedTestDTO);
} catch (NBIException e) {
log.info("NBIException", e);
return new ResponseEntity(payload, HttpStatus.BAD_REQUEST);
}
return new ResponseEntity(payload, HttpStatus.CREATED);
}
}
If I remove #Componet from Upload and Download classes I receive error I need to add Bean for abstrcat class CommndBase
If I use #Compoment on Upload and Download classes I receive dual Bean is useed...
Field command in .Controller required a single bean, but 2 were found:
You should not use #Component for abstract class, because Spring context will not be able to initialize that bean. You should remove it then.
Another thing is the way you want to implement a factory pattern here - I recommend you the way described here: https://stackoverflow.com/a/39361500/14056755, refactored version https://stackoverflow.com/a/55060326/14056755.

How can I create different spring beans depending on the input

All the class are implemented from the same interface. What is the best way to create beans depending on the input value we are receiving.
If the value is a it need to invoke one class vs different class if the value is b.
You cloud try something like this:
#Component
public class SomeServiceFactory {
#Autowired
private Someservice someserviceA;
#Autowired
private Someservice someserviceB;
#Autowired
private MyServiceThree SomeserviceC;
public SomeService getSomeService(String serviceType) {
if (serviceType.equals("A")) {
return someserviceA;
} else if (serviceType.equals("B")) {
return someserviceB;
} else {
return someserviceC;
}
}
}
First the interface:
public interface MyService {
void doSomething();
}
Then defining two implementation:
#Service
public class MyServiceA implements MyService {
#Override
public void doSomething() {
// do your business A
}
}
#Service
public class MyServiceB implements MyService {
#Override
public void doSomething() {
// do your business B
}
}
The context:
#Service
#RequiredArgsConstructor(onConstructor = #__(#Autowired))
public class MyServiceContext {
private final Map<String, MyService> strategyMap;
public MyService getMyService(String key) {
// the key is the bean name
return strategyMap.get(key);
}
}
Usage
#Autowired
private MyServiceContext context;
...
// your input key must be the bean name.
context.getMyService(yourInputValue).doSmething();

Autowired object populated using ThreadLocalTargetSource does not populate for every class

My Spring Boot application implements the TenantStore example for storing data in ThreadLocalTargetSource detailed in this link
#Bean(destroyMethod = "destroy")
public ThreadLocalTargetSource threadLocalTenantStore() {
ThreadLocalTargetSource result = new ThreadLocalTargetSource();
result.setTargetBeanName("tenantStore");
return result;
}
The working example allows for the TenantStore object to be set and injected by the Spring Framework. My version of the TenantFilter class described in that article sets the properties of the TenantStore object whenever a Servlet request is made
#Autowired
private TenantStore tenantStore;
#Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
try {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
if (authentication != null) {
String token = (String) request.getAttribute(ACCESS_TOKEN_VALUE);
if (token != null) {
OAuth2AccessToken oAuth2AccessToken = tokenStore.readAccessToken(token);
if (oAuth2AccessToken.getAdditionalInformation() != null) {
String tenantName = (String) oAuth2AccessToken.getAdditionalInformation().get("tenant");
storeTenantInThread(tenantName);
}
}
}
chain.doFilter(request, response);
} catch (ResourceNotFoundException e) {
log.error(e.getMessage());
} finally {
clearTenant();
}
}
private void storeTenantInThread(String tenantName) {
tenantStore.setName(tenantName);
}
private void clearTenant() {
tenantStore.clear();
}
I then have a number of services where TenantStore is autowired and in each of these services the TenantStore contains the information that was populated in the doFilter() method. Except for one class. For some reason the properties of the TenantStore in this class are still null. The name of the class affected is MyCacheService and the architecture is as follows:
#RestController
#RequestMapping("/here")
public class MyController {
#Autowired
private MyService myService
#GetMapping
public ResponseEntity myGetMethod(#RequestParam("text") String text) {
myService.myMethod(text);
return new ResponseEntity(Http.OK);
}
}
#Service
public class MyService {
#Autowired
private TenantStore tenantStore;
#Autowired
private MyOtherService myOtherService;
public void myMethod(String text) {
System.out.println(tenantStore.getName()); //works - prints name
myOtherService.myOtherMethod(text);
}
}
#Service
public class MyOtherService {
#Autowired
private TenantStore tenantStore;
#Autowired
private Map<String, MyComponent> myComponents;
public void myOtherMethod(String text) {
System.out.println(tenantStore.getName()); //works - prints name
MyComponent useThisComponent = myComponents.get("componentName");
useThisComponent.myComponentMethod(text);
}
}
#Component("componentName")
public class MyComponent {
#Autowired
private TenantStore tenantStore;
#Autowired
private MyCacheService myCacheService;
public void myComponentMethod(String text) {
System.out.println(tenantStore.getName()); //works - prints name
entityAliasCacheService.myCacheMethod(String text);
}
}
#Service
public class MyCacheService {
#Autowired
private TenantStore tenantStore;
public void myCacheMethod(String text) {
System.out.println(tenantStore.getName()); //DOES NOT WORK - tenantStore object is not null but the name property is
}
}
From what I can guess, for some reason the TenantStore in MyCacheService is being populated in a different thread, though I've no idea why.
I noticed similar behaviour. I fixed the issue by adding a bean dependancy
#Service
#DependsOn("proxiedThreadLocalTargetSource") // asks Spring to first load proxy bean
public class MyCacheService {
where proxiedThreadLocalTargetSource bean is defined like in the OP's example -
#Primary
#Bean(name = "proxiedThreadLocalTargetSource")
public ProxyFactoryBean proxiedThreadLocalTargetSource(ThreadLocalTargetSource threadLocalTargetSource) {
ProxyFactoryBean result = new ProxyFactoryBean();
result.setTargetSource(threadLocalTargetSource);
return result;
}
So, by adding the dependancy, Spring knows that it should load MyCacheService bean after the proxiedThreadLocalTargetSource. Without this dependancy, I noticed that TenantStore got injected instead of the proxy bean.
Getting instance of TenantStore from org.springframework.context.ApplicationContext
First implement ApplicationContextAware like as below
#Component
public class ApplicationContextUtil implements ApplicationContextAware {
private static ApplicationContext context;
#Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
context = applicationContext;
}
public static ApplicationContext context() {
return context;
}
}
And your MyCacheService Will be like this:
public class MyCacheService {
public void myCacheMethod(String text) {
TenantStore tenantStore = ApplicationContextUtil.context().getBean(TenantStore.class);
System.out.println(tenantStore.getName());
}
}

Instance variable in Spring Service

I have following Spring Service
#Service
class FeatureTogglesImpl implements FeatureToggles {
private final FeatureToggleRepository featureToggleRepository;
private Map<String, Feature> featuresCache;
#Autowired
public FeatureTogglesImpl(final FeatureToggleRepository featureToggleRepository) {
this.featureToggleRepository = featureToggleRepository;
this.featuresCache = loadAllFromRepository();
}
#Override
#Transactional
public void enable(Feature feature) {
Feature cachedFeature = loadFromCache(feature);
cachedFeature.enable();
featureToggleRepository.save(cachedFeature);
onFeatureToggled();
}
#Override
public boolean isEnabled(Feature feature) {
return loadFromCache(feature).isEnabled();
}
private Feature loadFromCache(Feature feature) {
checkNotNull(feature);
return featuresCache.get(feature.getKey());
}
private Map<String, Feature> loadAllFromRepository() {
return Maps.uniqueIndex(featureToggleRepository.findAll(), new Function<Feature, String>() {
#Override
public String apply(Feature feature) {
return feature.getKey();
}
});
}
void onFeatureToggled() {
featuresCache = loadAllFromRepository();
}
}
As you can see,I store loaded features into featuresCache, so that when client calls isEnabled() it is loading according feature from the cache.
There is a managed bean, who manages toggling the feature,
#Component
#ManagedBean
#Scope("view")
public class FeatureTogglesManager {
#Autowired
private FeatureToggles featureToggles;
#Secured({"ROLE_FEATURE_TOGGLES_EDIT"})
public String enable(Feature feature) {
featureToggles.enable(feature);
return null;
}
}
When I call enable() from AdminFeatureTogglesManager , I can see proper feature toggled, and cache pre-populated.
I have another service, which actually uses FeatureToggles.isEnabled() service
#Service
class ProductServieImpl implements ProductService {
#Autowired
private FeatureToggles featureToggles;
#Override
#Transactional
public void loadProducts() {
if (featureToggles.isEnabled(NewProducts.insance())) {
loadNewProducts();
return;
}
loadOldProducts();
}
}
The problem is that featureToggles.isEnabled() from this service always returns old instance from the cache, and when I debug the FeatureTogglesImpl, I do not see my pre-populated cache, although after toggle I could see correct/updated cache.
Isn't FeatureTogglesImpl supposed to be a singletong, so that if I change instance variable, it changes everywhere? Any help is appreciated.

How to initialize web-service

I'm trying to create a simple web-page, which is displaying the data, received from web-service
#Service
#Transactional
public class TopicService {
#Autowired
private TopicRepository topicRepository;
public int saveTopic(Topic topic){
return topicRepository.save(topic).getId();
}
public Iterable<Topic> findAllTopics(){
return topicRepository.findAll();
}
public Topic findTopicByID(Long id){
return topicRepository.findOne(id);
}
public List<Topic> findTopicsByTag(Tag tag){
return topicRepository.findAllByTopicTag(tag);
}
}
topic repository extends CRUD-repository
#Repository
public interface TopicRepository extends CrudRepository<Topic, Long>
{
List<Topic> findAllByTopicTag(Tag currentTag);
}
the controller invokes a service in the following way
#Controller
public class HomeController {
private TopicService service;
private static final Logger logger = LoggerFactory.getLogger(HomeController.class);
#RequestMapping(value = "/", method = RequestMethod.GET)
public ModelAndView home(Locale locale, Model model)
{
Iterable<Topic> listTopic = service.findAllTopics();
return new ModelAndView("home", "model", listTopic);
}
}
this string
Iterable listTopic = service.findAllTopics();
throws null-pointer exception. I guess, because the service isn't initialized. How could I perform correct initialization of the service?
You need to autowire the TopicService in HomeController:
#Controller
public class HomeController {
#Aurowired
private TopicService service;

Categories

Resources