Note: this is not cross-posting (although it's related to my other question shared objects between webapps of the same tomcat)
I have 2 webapps running at two contexts: c1, c2 (both immediately after the root). I put a startupListener in c1 to share a variable, and another one in c2 to retrieve it. The problem is if I share an object of built-in datatypes (like HashMap, Integer,...) it is ok, but custom datatype can't be cast. For example, if I have a custom class named User, and pass an object of that type around, ClassCastError happened.
My startuplistener in c1 is:
public void contextInitialized(ServletContextEvent sce) {
User user = new user("name");
Integer exampleInt = 1;
ServletContext context = sce.getServletContext().getContext("/c1");
if (context!=null)
{
context.setAttribute("user", user);
context.setAttribute("id", exampleInt);
}
}
In c2 app, it is like this:
public void contextInitialized(ServletContextEvent sce) {
ServletContext context = sce.getServletContext().getContext("/c1");
Integer integer = (Integer) context.getAttribute("id");//this line is OK
Object object = context.getAttribute("user");
User userObject = (User) object; //this line triggered error
User user = (User) context.getAttribute("user");// also trigger error
}
Why so? (a class complain about casting to itself?). Any workaround: I want to share my objects between contexts of the same jvm.
Tks.
The class of the object in the first webapp is loaded by the first webapp classloader. A class with the same name is loaded in the second webapp, by the second webapp classloader. So, even if both apps have access to a class with the same name, they're two different classes, because they're not loaded by the same classloader.
I wouldn't try to share objects between apps this way. It won't work in a clustered environment anyway, and a more secure container could return null when asked for another servlet context. Consider sharing a database between the two applications, and storing the shared data in the shared database.
If you still want to go this route, then make sure the shared classes are in the container's classpath, and not in each webapp classpath.
Related
I have just started learning Spring. I am confused with some simple concept of concurrency.
I have a class containing only 1 field uid:
#Service("Class1")
#Data
public class Class1 {
private String uid;
}
And I have request mapper with Class1 Autowired in it.
#RestController
#RequestMapping("/web")
#Scope("session")
public class Web {
#Autowired
Class1 class1;
#GetMapping("/hello")
public String sayHello(#RequestParam(value = "myName", defaultValue = "World") String name) {
class1.setUid(name);
try{
Thread.sleep(5000);
}
catch (InterruptedException e){
}
return String.format("Hello %s and uid %s!", name,class1.getUid());
}
}
Now if I hit two request at the same time with two different parameters say, myName=name1 and myName=name2, the uid is shown same in both the requests. But i think it should be equal to that particular name which is being passed.
I want to assign uid equal to the name which is being passed. How cannot be there 2 different instances of Class1 in both the request? Why is this concurrency happening?
I know Spring handle 2 different sessions by generating 2 different threads. But my concern is that both the threads use same bean. But i want different instance for different sessions. What is the proper way to do so?
I'm not sure if this is the issue, but at first glance I could relate this to the fact that the default(implicitly used) scope is Singleton, which means there is only one instance of bean (in your case the service bean). Usually, to handle state inside of the beans it is better to use #RequestScope for the service class (if you want to store the state there), which will create bean instance exactly for each particular request.
No sure but u are using #Service at the class level so its shared between all sessions. so the final result depends on which request is served later.
So
R1 -> "string1"
R2 -> "string2"
at the time when u call
return String.format("Hello %s and uid %s!", name,class1.getUid());
it will fetch the latest value which was set
as per my understanding the data class should not be a shared component(#Component or its derivatives) as by default scope is singleton u need to change the scope(prototype or other). so that it get instantiated at every request for more detail u can read https://www.geeksforgeeks.org/singleton-and-prototype-bean-scopes-in-java-spring.
class A
if(userAuthenticated)
ArrayList accounts = getAccountForUser(customerId);
UserInfo userinfo = new UserInfo();
userinfo.getUserInfo(accounts);
}
==============
class UserInfo
{
public ArrayList<String> getUserInfo(ArrayList list) {
ArrayList useraccounts = list;
return useraccounts;
}
}
===============
Now inside Class C i will have a String and i need to check , if that String Value exists then do something else do soemthing
The Problem is as this being a WebApplication , i cannot use instance varaibe in the Application .
class C
{
public String makeDBCALL(String account)
{
Here i need to get that ArrayList of UserInfo and check if taht list.contains(account))
could anybody please tell me how can do this .
}
}
Context
You need context.
Servlet classes need to be given that context.
DB or memory
You could have a reference to a DB (a Datasource) and query every time the DB, or have something in memory (that list by example).
Any way if C is a servlet class that knows it's inside a servlet environment you could ask for the app context. If it's a business class, something more agnostic then it's better to receive by parameter or property the context it needs.
Setting a context
If you use Spring configuration (Spring IOC) you could set what the classes needs writing the correct xml.
If you don't, then, at some place you'll need to look for DB connection, or initialize that list and give other classes a reference.
At last, a concrete solution
Write a context listener that generates this needed context at initialization (onContextCreated) and hangs it from the application context:
class MyListener implements ServletContextListener
{
contextInitialized(ServletContextEvent event) {
// Create theObjWEhatYouNeed
event.getServletContext().setAttribute("whatINeed", theObjWhatYouNeed);
}
}
Configure your web.xml to use MyListener
<listener>
<listener-class>MyListener</listener-class>
</listener>
Then use it from your servlet class (there can be a lot of instances of the same servlet class, have that in mind)
doGet(...) {
MyObj myObj = (MyObj) request.getSession().getServletContext().getAttribute("whatINeed");
}
I have to access some application through an mbean so that I can change its application properties. Now i think this can be done in two ways:
First, either I ask the developer of that application to register all the application properties in an arraylist which my mbean will access.
Secondly, if there is any other way, such that the developer will only need to register editable properties and still my mbean can access both readable/editable(r/w) application properties.
Now since I don't know where these application properties are stored in the JVM, is there a way to implement my second point so that the mbean will just need to access that object and it will get all application properties?
Seems like you have some contradicting requirements here.
You want to change minimal code in the application.
You want to be cause to expose all properties for read and/or write.
You may or may not be talking about System.getProperty(...). If not then I guess you are talking about just fields in various objects.
There are (at least) two ways of doing this. Without knowing how you are exporting the mbeans from the source code right now, I can't tailor my answer to your specific config. My answer will instead show how you might use my SimpleJMX package to expose your properties.
If you are talking about System.getProperty(...) then you could write a JMX mbean that could query any property and return the value:
#JmxOperation(description = "Query for property")
public String queryForProperty(String name) {
return System.getProperty(name);
}
If, instead, you need to export of fields from some list of objects then you are going to either have to add annotations to each fields you are exporting or you are going to have to write beans that export the fields through delegation. I see no easy way and I know of no package that will easily export a series of objects for you without some sort of information about what is to be exported and how.
Using SimpleJMX, you can export a field by annotating either the field or the get method:
#JmxAttributeField(description = "Number of hits in the cache")
private int hitCount;
...
// this can also be specified as #JmxAttributeMethod on the getter/setter methods
#JmxAttributeMethod(description = "Number of misses in the cache")
private int getMissCount() {
return missCount;
}
The #JmxAttributeField supports a isWritable = true to allow the value to be set by JMX. You can also annotation the setter with #JmxAttributeMethod to make it writable.
If you don't want to add annotations to each of your classes, then you are going to have to write some sort of JMX container class that exports the values through delegation:
public class JmxPublisher {
private Class1 object1;
private Class2 object2;
...
public JmxPublisher(Class1 object1, Class2 object2) {
this.object1 = object1;
this.object2 = object2;
...
}
#JmxAttributeMethod(description = "Number of hits in the cache")
public int getClass1HitCount() {
return object1.getHitCount();
}
#JmxAttributeMethod(description = "Shutdown the background thread")
public void setClass2Shutdown(boolean shutdown) {
return object2.setShutdown(shutdown);
}
...
}
I also think you should express yourself more clearly.
From what I understood - why not providing a way to query the remote application, and get information on all properties and if they are Read-only, Write-only or RW?
This way the list of properties will not be mentioned at the source code of the client application - maybe you should let the user of the client application see the list of properties, and let him edit the properties he can edit, and prevent him from editing the properties he can't.
To be specific let me illustrate the question with Spring http-remoting example.
Suppose we have such implementation of a simple interface:
public SearchServiceImpl implements SearchService {
public SearchJdo processSearch(SearchJdo search) {
search.name = "a funky name";
return search;
}
}
SearchJdo is itself a simple POJO.
Now when we call the method from a client through http-remoting (Spring's mechanism of calling remote objects much like EJB that uses serialization) we'll get:
public class HTTPClient {
public static void main(final String[] arguments) {
final ApplicationContext context = new ClassPathXmlApplicationContext(
"spring-http-client-config.xml");
final SearchService searchService =
(SearchService) context.getBean("searchService");
SearchJdo search = new SearchJdo();
search.name = "myName";
// this method actually returns the same object it gets as an argument
SearchJdo search2 = searchService.processSearch(search);
System.out.println(search == search2); // prints "false"
}
}
The problem is that the search objects are different because of serializaton although from logical prospective they are the same.
The question is whether there are some technique that allows to support or emulate object identity across VMs.
You said it - object identity is different from logical equality.
object identity is compared with ==
logical equality is compared with .equals(..)
So override the equals() method and all will be fine. Remember to override hashCode() based on the same field(s) as well. Use your IDE to generate these 2 methods for you.
(Teracotta VM clustering allows sharing objects between VMs, but that doesn't fit your case.)
IMHO attempting to preserve object identity equality across VMs is a losing proposition.
To the best of my knowledge the language specification does not require a VM to support that, so you would be limited in where you can pull off if you truly want to be portable.
May I ask why you don't just use some unique ID that you supply yourself? Java GUIDs, while expensive, are serializable.
I did this once, but I'm not quite sure if this is a right approach:
Every user had a username, session id, roles, and a login date attached to a user object. Every time I logged into a VM the system would load a User object into memory; I would also return the user object to the application.
If I needed to execute an action within the application server, then I would send the user object as an argument. If the VM had the User loaded with the same session ID then it would use the object stored in the VM to know the assigned roles. Otherwise, the application would then be capable of changing the roles in the user and it wouldn't be secure.
If the application had to change the application server, then it sends the user object to the new server and the new server wouldn't be able to find the user within its records.
HERE IS THE SECRET: The session ID is created hashing the username, the login date and a secret password shared among all of the servers.
Once the new server finds that the session ID is coherent, then it would load the roles from the database as a reliable source of information.
Sorry if I couldn't write this before, but hope it helps for someone.
I read a properties-file at the webapplication startup phase (contextInitialized()) and I started to think about how to make these settings 'visible' to the servlets. Do I need to loop through the keys and add each and every one to the context, like this
Iterator i = settings.keySet().iterator();
while (i.hasNext()) {
key = (String) i.next();
value = (String) settings.get(key);
context.setAttribute(key, value);
}
or are there better methods?
Thank you!
/Adam
why not store the entire contents in your servlet context?
context.setAttribute("mySettings", settings);
setAttribute's signature is:
public void setAttribute(String name, Object object)
Have you considered the possibility of defining the settings in web.xml?
Also, if that's not possible, use generics if possible:
String key = null;
Iterator<String> i = settings.keySet().iterator();
while (i.hasNext())
context.setAttribute(key = i.next(), settings.get(key));
I've been toying with an idea:
In the context initialized method, I've planned to create just one global object for the settings. Much like toolkit proposed. But instead of setting context attributes for each key/attribute/setting, would it be a terrible idea to add a settings container/wrapper object? I'm thinking this class would be responsible for holding (static?) classes of module settings. This way I can get typed references like so
//ExampleServlet.java
Settings settings = (Settings)context.getAttribute("application.settings");
String color = settings.getModule1().getColor();
String font = settings.getModule1().getFont();
int blogs = settings.getModule2().getActiveBlogCount();
Throughout the code I'll have to remember only one attribute key, the one for the entire settings container. Less risk of typos which could cause rumtime exceptions!
It will also make it easy to rename attributes.
What do you think?
/Adam
What about using the JNDI context. JNDI is a more common way to pass properties to a webapp.
Any Properties may be specified in the META-INF/context.xml for tomcat or any application specific setup.
It's something that I have contemplated, setting the entire properties object as a context attribute.
If I do not go this route, are there any guidelines for how to name these attributes or do you feel that "application.settings" or "myBlog.settings"? How do you group keys? Would this be okay:
application.module1.color=black
application.module1.font=arial
I feel, in a way, that it could become a burden to maintain such an application where the property keys are spread throughout the code? Should another developer rename a property in the properties file, we'll know only when running the application (if/when/what referenced the old key). Right?
I'll have to lookup JNDI.