As I am new to spring MVC, I am stuck on this problem.
I am trying to use session Scope , where I want a list to be shared across multiple controller in a http session. But I do not want this session to be shared by different http sessions. Each http session should have its own list(storage) which is shared among its controllers.
The problem I am facing is , this list is shared among all the request which are running in parallel.
For eg . If I hit the controller on my machine and some other request comes
from a different machine ,they both are using the same list(session storage)
This is MySession class which holds a List which needs to be shared across multiple controllers and each controller can do operation on this list.
#Component
#Scope("session")
public class MySession
{
public List<String> mylist;
}
Here is my controller which is using the MySession class object, Similarly there are other Controllers which can update the List in the session.
#Controller
#Scope("request")
public class SessionController
{
#Autowired
private MySession mySession;
#RequestMapping("/updateSession")
public ModelAndView updateSession(#RequestParam("id") int id)
{
//Operation on List(add remove items)
}
}
So now what is happening is List in MySession is getting effected my some other requests as I think all concurrent requests are using the same list.
Please help me how can I solve this problem if there is anything that I am missing
Ask for any modification.
Thanks in advance.
Related
In our Angular + Spring boot application application, we have 2 Controllers (2 Services are internally referenced). In first controller, We are sending a File from UI and reading the content of the file , query an external application and retrieve a set of data and return only a sub-set of Data, for entering as recommendation for UI fields. why we are returning only sub-set of data received from the external application? Because, we need only those sub-set data for showing recommendations in UI.
Once the rest of the fields are filled, then, we call another controller to generate a report. But, for generation of files, the second service requires the rest of the data from external application, which is received by the first service. I understand that Autowiring the first service in the second service, will create new instance of the first service and I will not get the first service instance, which is used to query the external application. I also like to avoid calling the external application again to retrieve the same data again in the second service. My question is how to fetch the data received by the first service in the second service?
For example:
First controller (ExternalApplicationController), which delegates loading of loading/importing of data from files
public class Department{
private Metadata metadata; // contains data such as name, id, location, etc.,
private Collection<Employee> employees; // the list of employees working in the department.
}
#RestController
#RequestMapping("/externalApp")
public class ExternalApplicationController{
#Autowired
private ExternalApplicationImportService importService;
#PostMapping("/importDepartmentDataFromFiles")
public Metadata importDepartmentDataFromFiles(#RequestParam("files") final MultipartFile[] files) {
return this.importService.loadDepartmentDetails(FileUtils.getInstance().convertToFiles(files)).getMetadata();
}
}
The first service (ExternalApplicationImportService), which delegates the request to the external application for loading of department data.
#Service
public class ExternalApplicationImportService{
private final ExternalApp app;
public Department loadDepartmentDetails(File file){
return app.loadDepartmentDetails(file);
}
}
The Metadata from the ExternalApplicationController is used to populated UI fields and after doing some operations (filling up some data), user requests to generate a report(which contains details from the employees of that department)
#RestController
#RequestMapping("/reportGenerator")
public class ReportController{
#Autowired
private ReportGenerationService generationService;
#PostMapping("/generateAnnualReports")
public void generateAnnualReports(){
generationService.generateAnnualReports();
}
}
#Service
public class ReportGenerationService{
public void generateAnnualReports(){
//here I need access to the data loaded in the ExternalApplicationImportService.
}
}
So, I would like to access the data loaded in the ExternalApplicationImportService in the ReportGenerationService.
I also see that there would be more services created in the future and might need to access the data loaded in the ExternalApplicationImportService.
How can this be designed and achieved?
I feel that I'm missing something how to have a linking between these services, for a given user session.
Thanks,
Paul
You speak about user session. Maybe you could inject the session of your user directly in your controllers and "play" with it?
Just adding HttpSession as parameter of your controllers' methods and spring will inject it for you. Then you just have to put your data in the session during the first WS call. And recover it from the session at the second WS call.
#RestController
#RequestMapping("/reportGenerator")
public class ReportController{
#PostMapping("/generateAnnualReports")
public void generateAnnualReports(HttpSession session){
generationService.generateAnnualReports();
}
}
Alternatively for the second call you could use:
#RestController
#RequestMapping("/reportGenerator")
public class ReportController{
#PostMapping("/generateAnnualReports")
public void generateAnnualReports(#SessionAttribute("<name of your session attribute>") Object yourdata){
generationService.generateAnnualReports();
}
}
You are starting from a wrong assumption:
I understand that Autowiring the first service in the second service, will create new instance of the first service and I will not get the first service instance, which is used to query the external application.
That is not correct: by default, Spring will create your bean as singleton, a single bean definition to a single object instance for each Spring IoC container.
As a consequence, every bean in which you inject ExternalApplicationImportService will receive the same instance.
To solve your problem, you only need a place in where temporarily store the results of your external app calls.
You have several options for that:
As you are receiving the same bean, you can preserve same state in instance fields of ExternalApplicationImportService.
#Service
public class ExternalApplicationImportService{
private final ExternalApp app;
// Maintain state in instance fields
private Department deparment;
public Department loadDepartmentDetails(File file){
if (department == null) {
department = app.loadDepartmentDetails(file);
}
return department;
}
}
Better, you can use some cache mechanism, the Spring builtin is excellent, and return the cached result. You can choose the information that will be used as the key of the cached data, probably some attribute related to your user in this case.
#Service
public class ExternalApplicationImportService{
private final ExternalApp app;
#Cacheable("department")
public Department loadDepartmentDetails(File file){
// will only be invoked if the file argument changes
return app.loadDepartmentDetails(file);
}
}
You can store the information returned from the external app in an intermediate information system like Redis, if available, or even in the application underlying database.
As suggested by Mohicane, in the Web tier, you can use the http sessions to store the attributes you need to, directly as a result of the operations performed by your controllers, or even try using Spring session scoped beans. For example:
#RestController
#RequestMapping("/externalApp")
public class ExternalApplicationController{
#Autowired
private ExternalApplicationImportService importService;
#PostMapping("/importDepartmentDataFromFiles")
public Metadata importDepartmentDataFromFiles(#RequestParam("files") final MultipartFile[] files, HttpSession session) {
Deparment department = this.importService.loadDepartmentDetails(FileUtils.getInstance().convertToFiles(files));
session.setAttribute("department", department);
return deparment.getMetadata();
}
}
And:
#RestController
#RequestMapping("/reportGenerator")
public class ReportController{
#Autowired
private ReportGenerationService generationService;
#PostMapping("/generateAnnualReports")
public void generateAnnualReports(HttpSession session){
Department department = (Department)session.setAttribute("department");
// Probably you need pass that information to you service
// TODO Handle the case in which the information is not present in the session
generationService.generateAnnualReports(department);
}
}
In my opinion, the second of the proposed approaches is the best one but all are valid mechanisms to share your data between the two operations.
my recommendation for you will be to revisit your design of classes and build a proper relationship between them. I feel you need to introduce the extra logic to manage your temporal data for report generation.
#Mohicane suggested to use HTTP Session in above answer. It might be a possible solution, but it has an issue if your service needs to be distributed in the future (e.g. more than one runnable instance will serve your WEB app).
I strongly advise:
creating a separate service to manage Metadata loading process, where you will have load(key) method
you need to determine by yourself what is going to be a key
both of your other services will utilize it
this service with method load(key) can be marked by #Cacheable annotation
configure your cache implementation. As a simple one you can use In-Memory, if a question becomes to scale your back-end app, you can easily switch it to Redis/DynamoDB or other data storages.
Referances:
Spring Caching
Spring Caching Guide
I have some data objects that are common across a Spring boot application - one is the logged in employee object and other is a category. I have created a #Component class which contains these are static variables. This way I do not even have to autowire them. They can be used directly like CurrentContext.employee in controllers.
#Component
public final class CurrentContext {
public static Category currentCategory;
public static Employee employee;
#Autowired
private CategoryService categoryService;
#Autowired
private EmployeeService employeeService;
#EventListener
public void onApplicationEvent(ContextRefreshedEvent event) {
currentCategory = categoryService.getCategory();
}
#EventListener
public void onLoginSuccess(InteractiveAuthenticationSuccessEvent event) {
employee = employeeService.getEmployeeByUserId(((MyUserDetails) event.getAuthentication().getPrincipal()).getUserId());
}
}
Is this a right way? Please suggest if there is a better way to handle shared data
Edit
Some background - I require the current logged in employee and a category which is common for all employees. So I autowired employeeService and categoryService in my controllers and use them to get the data. They are required in almost all my controller methods, so, I wanted to create a bean of these so that I directly use them in my controller and also save frequent database calls.
Normally, we only put the dependencies related to the cross-cutting concerns (i.e dependencies that are across the whole application such as security , logging , transaction stuff , time provider etc.) in the static field.
By accessing these kind of dependencies in the static way , we don't need to pass them through method parameters /constructors from object to object , which will make the API much cleaner without such noise (BTW. This is called Ambient Context Pattern in the .NET world).
Your Employee object most probably belong to this type , so it is ok to access it in a static way. But as their scope is per session , you cannot simply put it in the static field of a class. If yes, then you always get the same employee for all sessions. Instead, you have to somehow store it in an object which is session scope (e.g HttpSession) . Then at the beginning of handling a web request , you get it from the session and then put it in a ThreadLocal which is encapsulated inside a "ContextHolder" object. You then access that "ContextHolder" in a static way.
Sound very complicated and scary ? Don't worry as Spring Security has already implemented this stuff for you. What you need to do is to customize Authentication#getPrincipal()or extend default Authentication to contain your Employee. Then get it using SecurityContextHolder.getContext().getAuthentication()
For your currentCategory , if they are not the cross-cutting concerns and is the application scope , make a singleton bean to get it values is a much better OOP design.
#Component
public final class CurrentCategoryProvider {
#Autowired
private CategoryService categoryService;
public Category getCurrentCategory(){
//or cache the value to the an internal properties depending on your requirements
return categoryService.getCategory();
}
}
You then inject CurrentCategoryProvider to the bean that need to access currentCategory.
I am writing an API using Spring Boot, and I have a abstract controller to hold the shared logic among several controllers. Now I want to add a warning field:
public abstract class BaseController<T> {
public List<String> warnings;
#RequestMapping(method = POST)
public Response create(HttpServletRequest request,HttpServletResponse response) {
warnings = new ArrayList<>();
if (something bad from T) {
warning.add("bad thing happens");
}
return createRespone(warnings);
}
(createReponse is uesd to create custom reponse)
And I have several different controller inherited from it
#RestController
#RequestMapping("/{area}/blah")
public class BlahController extends BaseController<Blah> {
}
For the warning field, will it be shared several different children controller, or will only one instance alive? If controller A and controller B are both inherited from BaseController and tried to modify warning, is it thread safe?
The warning field is not shared. Your code is equivalent to:
BaseController<blah> blahController = new BlahController();
BaseController<noh> nohController = new NohController();
Having state in a controller is against the REST concepts.
I am trying to do a simple android application that communicates with a Spring server.
I'd like to use Sessions to store data of each logged in User.
My App exchange Json objects with the server and the Request Mapping is like this:
#Controller
public class LoginController {
#Autowired
private IUserDao userDao;
#RequestMapping( value = "/loginJson",method = RequestMethod.POST)
public #ResponseBody loginResponse login(#RequestBody loginModel login) {
loginResponse response=userDao.checkCredentials(login.getUsername(),login.getPassword());
System.out.println("Result="+response.isSuccess());
System.out.println("Received:"+login.getUsername()+" "+login.getPassword());
return response;
}
}
The controller is working fine, but I can't figure out how to store a sessione variable. I found many documents explaining Spring Sessions, but each of them different from the other.
Someone can suggest me some simple way to do this or some kind of good tutorial?
Not sure what you mean by saying Spring session, but you can declare additional HttpSession parameter in your method, and then do whatever you like inside of the method. Is this what you wanted to find out?
I make (thanks with some users on this portal) my application that implements SessionAware.
This is my actual code :
public class UserManager extends ActionSupport implements SessionAware {
private Map<String, Object> session;
#Override
public String execute() throws Exception {
return SUCCESS;
}
public void setSession(Map<String, Object> map) {
this.session=map;
}
public String checkLogin() {
session.put("loggedOn", true);
return SUCCESS;
}
public String checkLogout() {
session.clear();
return SUCCESS;
}
}
And i check these variables on my .jsp :
<s:if test="#session['loggedOn']!=true">
DIV LOGIN
</s:if>
<s:else>
DIV LOGOUT
</s:else>
An easy piece of code.
What i'd like to know is this :
1 - the bean is (as default) request scoped. So when the request is finished it will be destroyed. But i see that, when i put a variable in the Map, it still alive on the server. How is possible? Is a variable of my Bean.
2 - Who call the setSession method? I think the servlet, due to the fact I implements that interface?
3 - I would like to detach about saving object/bean on a Session Object. I'd like to use the Bean session scoped (as for any kind of MVC framework). How can I do it on struts2?
Hope you can make clear these questions :) Cheers
1) Your bean is a struts2 action as such it is action scoped (which is a more restrictive scope than request). I say that action is a lesser scope because you can forward an action to another action in which case the previous action goes out of scope, request scoped objects however will stay in scope until the request is served. When you implement SessionAware you are provided with a reference to the SessionObject you are then putting your object into the Session object who's life span is much longer than your action. Clear your browser cache will remove the session value... you can also remove them by code be implementing SessionAware and removing the value from the map.
2 - The session already exists. You can get the session and add keys but even if you don't put anything in it, it will be there for use.
3 - You have a later question already for this topic see my answer there.