If we have statless bean then it can be injected into Servlet by #EJB annotation. For example:
#Stateless
public class LongTimeService {
public void do() {
//logic
}
}
public class ServletWithBean extends HttpServlet {
#EJB
private LongTimeService bean;
#Override
public void service(ServletRequest arg0, ServletResponse arg1)
throws ServletException, IOException {
bean.do;
...
}
}
In this case we will have only one instance of LongTimeService bean during all lifecycle of Servlet. From ejb container perspective when web container will construct Servlet with bean it ask instance from ejb container and will keep this instance until servlet will be destroed and every servlet request will work only with one instance.
I think this is bad way to use Statless EJB because th don't create for such kind of usage. For this perpes for example useful #Singleton statfull bean.
But if we want use statless bean then we can lookup instance of this bean from the Context every time inside of the method.
public class ServletWithBean extends HttpServlet {
#Override
public void service(ServletRequest arg0, ServletResponse arg1)
throws ServletException, IOException {
Context ctx = new InitialContext();
LongTimeService bean = context.lookup("LongTimeService");
bean.do;
...
}
}
Is it correct and possible to use this approach?
From ejb container perspective when web container will construct
Servlet with bean it ask instance from ejb container and will keep
this instance until servlet will be destroed and every servlet request
will work only with one instance.
Your reasoning is right, however, the instance that is injected into the servlet class member is actually an instance of an object called Stub or Proxy, it is not really an EJB instance.
Basically, every time you invoke an ejb´s method from your servlet, the Stub asks the ejb container a reference to an ejb, the container will get an available ejb from the pool, this ejb will process the request and once the job is done it will come back to the pool.
Therefore, if your servlet processes several requests at the same time, the stub will get a different ejb reference per request.
Notes that the stub object implementation needs to be thread-safe (and for Stateless bean it is).
I think this is bad way to use Statless EJB because th don't create
for such kind of usage.
According to the above points, yes, you can use #EJB injection from a servlet.
About lookup stub approach:
It also can work (you will get an Stub per request), but it is not necessary for stateless and it has an important drawback: the lookup is a time-consuming operation, therefore, you will gain a latency in your service time response without compensation.
I hope this help you.
Related
I have an object which represents user's state with a huge amount of data and this object can be mutated multiple times per one http request, so one of the optimization I want to use is persist this object only on http request destruction.
The problem is when I try to use #PreDestroy annotation on request scope bean I can not use request scope dependencies like http session (e.g. call javax.servlet.http.HttpServletRequest#getSession()), because DispatcherServlet reset all request scope beans in org.springframework.web.servlet.FrameworkServlet#processRequest
resetContextHolders(request, previousLocaleContext, previousAttributes);
if (requestAttributes != null) {
requestAttributes.requestCompleted();
}
The method annotated with PreDestroy is typically used to release resources that it has been holding, however I want to commit transaction using lot of dependencies, that's why it does not work.
Is there any way in spring web to call bean's method after Servlet::doService call and before request scope beans reset?
you can use Global ServletContextListener. which needs to register the callback at bean level, the ServletContextListener registers the callback at context level.
public class MyServletContextListener
implements ServletContextListener {
#Override
public void contextDestroyed(ServletContextEvent event) {
System.out.println(
"Executed callback ContextListener.");
}
#Override
public void contextInitialized(ServletContextEvent event) {
// Triggers when context initializes
}
}
You need to register it to the ServletListenerRegistrationBean in the configuration class:
#Bean
ServletListenerRegistrationBean<ServletContextListener> servletListener() {
ServletListenerRegistrationBean<ServletContextListener> srb
= new ServletListenerRegistrationBean<>();
srb.setListener(new MyServletContextListener());
return srb;
}
I need to get some configuration and connect to external resources/objects/systems somewhere and store it in application scope.
I can see two ways to setup my application:
Overriding the init() in the existing servlets and required code there and keeping all constructed objects inside that same servlet.
Having some kind of an initialisation servlet and using its init() to do the work. Then storing created objects in ServletContext to share it with my other servlets.
Which out of above is better approach? Is there any better way to share objects between servlets? Calling them directly from one another or so...?
None of both is the better approach. Servlets are intended to listen on HTTP events (HTTP requests), not on deployment events (startup/shutdown).
CDI/EJB unavailable? Use ServletContextListener
#WebListener
public class Config implements ServletContextListener {
public void contextInitialized(ServletContextEvent event) {
// Do stuff during webapp's startup.
}
public void contextDestroyed(ServletContextEvent event) {
// Do stuff during webapp's shutdown.
}
}
If you're not on Servlet 3.0 yet and can't upgrade (it would be about time because Servlet 3.0 was introduced more than a decade ago), and thus can't use #WebListener annotation, then you need to manually register it in /WEB-INF/web.xml like below:
<listener>
<listener-class>com.example.Config</listener-class>
</listener>
To store and obtain objects in the application scope (so that all servlets can access them), use ServletContext#setAttribute() and #getAttribute().
Here's an example which lets the listener store itself in the application scope:
public void contextInitialized(ServletContextEvent event) {
event.getServletContext().setAttribute("config", this);
// ...
}
and then obtain it in a servlet:
protected void doGet(HttpServletRequest request, HttpServletResponse response) {
Config config = (Config) getServletContext().getAttribute("config");
// ...
}
It's also available in JSP EL by ${config}. So you could make it a simple bean as well.
CDI available? Use #Observes on ApplicationScoped.class
import jakarta.enterprise.context.ApplicationScoped; // And thus NOT e.g. jakarta.faces.bean.ApplicationScoped
#ApplicationScoped
public class Config {
public void init(#Observes #Initialized(ApplicationScoped.class) ServletContext context) {
// Do stuff during webapp's startup.
}
public void destroy(#Observes #Destroyed(ApplicationScoped.class) ServletContext context) {
// Do stuff during webapp's shutdown.
}
}
This is available in a servlet via #Inject. Make it if necessary also #Named so it's available via #{config} in EL as well.
Noted should be that this is new since CDI 1.1. If you're still on CDI 1.0 and can't upgrade, then pick another approach.
In case you're curious how to install CDI on a non-JEE server such as Tomcat, head to: How to install and use CDI on Tomcat?
EJB available? Consider #Startup#Singleton
#Startup
#Singleton
public class Config {
#PostConstruct
public void init() {
// Do stuff during webapp's startup.
}
#PreDestroy
public void destroy() {
// Do stuff during webapp's shutdown.
}
}
This is available in a servlet via #EJB. The difference with other approaches is that it's by default transactional and in case of #Singleton also read/write locked. So if you would ever need to inject a random EJB (e.g. #Stateless) into a #WebListener or an #ApplicationScoped then you could basically as good merge both into a single #Startup #Singleton.
See also:
How to run a background task in a servlet based web application?
ServletContainerInitializer vs ServletContextListener
How do I force an application-scoped bean to instantiate at application startup?
I need to get some configuration and connect to external resources/objects/systems somewhere and store it in application scope.
I can see two ways to setup my application:
Overriding the init() in the existing servlets and required code there and keeping all constructed objects inside that same servlet.
Having some kind of an initialisation servlet and using its init() to do the work. Then storing created objects in ServletContext to share it with my other servlets.
Which out of above is better approach? Is there any better way to share objects between servlets? Calling them directly from one another or so...?
None of both is the better approach. Servlets are intended to listen on HTTP events (HTTP requests), not on deployment events (startup/shutdown).
CDI/EJB unavailable? Use ServletContextListener
#WebListener
public class Config implements ServletContextListener {
public void contextInitialized(ServletContextEvent event) {
// Do stuff during webapp's startup.
}
public void contextDestroyed(ServletContextEvent event) {
// Do stuff during webapp's shutdown.
}
}
If you're not on Servlet 3.0 yet and can't upgrade (it would be about time because Servlet 3.0 was introduced more than a decade ago), and thus can't use #WebListener annotation, then you need to manually register it in /WEB-INF/web.xml like below:
<listener>
<listener-class>com.example.Config</listener-class>
</listener>
To store and obtain objects in the application scope (so that all servlets can access them), use ServletContext#setAttribute() and #getAttribute().
Here's an example which lets the listener store itself in the application scope:
public void contextInitialized(ServletContextEvent event) {
event.getServletContext().setAttribute("config", this);
// ...
}
and then obtain it in a servlet:
protected void doGet(HttpServletRequest request, HttpServletResponse response) {
Config config = (Config) getServletContext().getAttribute("config");
// ...
}
It's also available in JSP EL by ${config}. So you could make it a simple bean as well.
CDI available? Use #Observes on ApplicationScoped.class
import jakarta.enterprise.context.ApplicationScoped; // And thus NOT e.g. jakarta.faces.bean.ApplicationScoped
#ApplicationScoped
public class Config {
public void init(#Observes #Initialized(ApplicationScoped.class) ServletContext context) {
// Do stuff during webapp's startup.
}
public void destroy(#Observes #Destroyed(ApplicationScoped.class) ServletContext context) {
// Do stuff during webapp's shutdown.
}
}
This is available in a servlet via #Inject. Make it if necessary also #Named so it's available via #{config} in EL as well.
Noted should be that this is new since CDI 1.1. If you're still on CDI 1.0 and can't upgrade, then pick another approach.
In case you're curious how to install CDI on a non-JEE server such as Tomcat, head to: How to install and use CDI on Tomcat?
EJB available? Consider #Startup#Singleton
#Startup
#Singleton
public class Config {
#PostConstruct
public void init() {
// Do stuff during webapp's startup.
}
#PreDestroy
public void destroy() {
// Do stuff during webapp's shutdown.
}
}
This is available in a servlet via #EJB. The difference with other approaches is that it's by default transactional and in case of #Singleton also read/write locked. So if you would ever need to inject a random EJB (e.g. #Stateless) into a #WebListener or an #ApplicationScoped then you could basically as good merge both into a single #Startup #Singleton.
See also:
How to run a background task in a servlet based web application?
ServletContainerInitializer vs ServletContextListener
How do I force an application-scoped bean to instantiate at application startup?
I have a filter where I am dinamically mapping servlet classes:
#Override
public void init( FilterConfig filterConfig ) throws ServletException {
servletContext = filterConfig.getServletContext();
File directory = getConventionDirectory();
FileSystemInspector fileInspector = new FileSystemInspector();
Set<ActionInfoData> actions = fileInspector.getActions( directory );
for ( ActionInfoData action : actions ) {
servletContext
.addServlet( action.getServletName(), action.getClassName() )
.addMapping( action.getServletMapping() );
}
}
Then when I access a given mapping the EJB is not injected.
#EJB
private I18nManager i18nManager;
#Override
protected void doGet( HttpServletRequest request, HttpServletResponse response )
throws ServletException, IOException {
I18nManager i18n = i18nManager; //null
}
If I manually create a mapping in the web.xml the given EJB is working in that servlet.
It makes me wonder if the fact I am registering the servlets at runtime the container does not consider those servlets as managed.
If that is the case what is the proper way to inject the EJBs into my servlets without changing the way they are dinamically registered via filter?
Is via JNDI the only way to inject my EJBs?
EDIT 1:
I have tried to implement a ServletContextListener class as suggested by "Will" using the following code in the web.xml:
<listener>
<listener-class>com.megafone.web.filter.convention.InitServlet</listener-class>
</listener>
And the relevant part of the implementation:
...
#Override
public void contextInitialized( ServletContextEvent sce ) {
ServletContext servletContext = sce.getServletContext();
FileSystemInspector fileInspector = new FileSystemInspector();
Set<ActionInfoData> actions = fileInspector.getActions( getConventionDirectory() );
for ( ActionInfoData action : actions ) {
servletContext
.addServlet( action.getServletName(), action.getClassName() )
.addMapping( action.getServletMapping() );
}
}
...
Unfortunately it does not make the container inject the EJBs and the null pointer remains. I am currently making a custom type safe JNDI lookup to the service. Obviously this is far more expensive than using the proper injection (correct me if I am wrong, have done no experiments regarding performance yet).
Using:
Java EE 6
JBoss AS 7.1
The problem seems related to this reported bug which is as yet unresolved. Resource resolution works just fine for Managed Beans as defined by the JSF specification, but not for CDI Managed Beans. Simply annotating your dynamic servlet classes with #javax.faces.bean.ManagedBean should fix the problem (yes, its quite an ugly solution):
#ManagedBean
public class DynServlet extends HttpServlet {
private static final long serialVersionUID = 1L;
#EJB
private LoginService loginService;
public DynServlet() {
super();
}
#Override
protected void doGet(HttpServletRequest request,
HttpServletResponse response) throws ServletException, IOException {
response.getOutputStream().println(
"Request made to: " + getClass().getSimpleName());
response.getOutputStream().println("Login Service: " + loginService);
return;
}
}
#WebListener
public class DynamicServletLoadListener implements ServletContextListener {
public DynamicServletLoadListener() {
super();
}
#Override
public void contextDestroyed(final ServletContextEvent contextEvent) {
return;
}
#Override
public void contextInitialized(final ServletContextEvent contextEvent) {
contextEvent.getServletContext().addServlet("dynservlet", DynServlet.class)
.addMapping("/services/dynservlet");
}
}
Tested with JEE6 (ofc) and both JBoss 7.1.1 and 7.2.0 (EAP 6.1.0 Alpha).
Edit:
The problem with dynamic mapped servlets is actually pretty deep in the base JBoss architecture. They use JBossWeb (a forked version of Tomcat) as the servlet implementation, and in the guts of its context management code it makes a determination wether to instantiate new components via injection or regular new. Afaik as of date, your servlets would need to be annotated in some fashion in order for them to be processed via injection: I had mentioned #ManagedBean in my original answer but it looks like annotating with #WebServlet works as well.
Servlet 3.0 Spec, Sect. 4.4.3.5
Resource injection [e.g. #EJB] on all components (Servlets, Filters and Listeners) added
programmatically or created programmatically, other than the ones added via the
methods that takes an instance, will only be supported when the component is a
Managed Bean. For details about what is a Managed Bean please refer to the
Managed Bean specification defined as part of Java EE 6 and JSR 299.
Managed Bean Declaration
A Java EE 6 managed bean is annotated #javax.annotation.ManagedBean and has a no-arg constructor. A JSR 299 (CDI) managed bean merely needs a no-arg constructor or a constructor annotated #javax.inject.Inject.
Answer
To enable resource injection you need to:
place #ManagedBean annotation on dynamically added servlet
OR
enable CDI & include an empty beans.xml
Edit
Even though you're creating the servlet dynamically, it's important that the container does the creation. Don't think creation within ServletContext will support injection. Servlet doc very vague here.
With CDI try:
servletContext.addServlet("your servlet name", #Inject YourServletClass servlet)
First, in my test, this worked fine using a version of Glassfish V3.
But, second, you may well be running afoul of this clause of the Servlet 3.0 spec.
The following methods are added to ServletContext since Servlet 3.0 to
enable programmatic definition of servlets, filters and the url
pattern that they map to. These methods can only be called during the
initialization of the application either from the contexInitialized
method of a ServletContextListener implementation or from the
onStartup method of a ServletContainerInitializer implementation.
Notably, these methods can NOT be called from a Filter.init() method. I originally tried this in a Servlet.init() method, and the init method failed because the context was already initialized.
So, my experiment did not duplicate your test exactly -- I did not use a Filter.init() method for this, rather I put the code in a ServletContextListener. And when I did that, my #EJB annotation was honored.
Edit:
As un-helpful as it sounds, I would suggest this is a bug in JBoss. When I initially tried your original code with the injection from the Filter, Glassfish threw an exception, since you're not allowed to do the injection save where I mentioned earlier. Now perhaps that's an "added feature" of JBoss, but apparently the #EJB injection processing simply isn't working. According to spec, this should work as advertised.
I'd like to register/add a Managed Bean class programmatically (from within a Servlet init()) into application scope.
How can I do that with JSF 1.2?
It is unlikely that can do do this in a programmatic manner from your application for managed beans of all scopes. BalusC has already pointed out how to do this for application scoped managed beans.
Having taken a look at how managed beans are registered in Mojarra 2.1 (a JSF 2.1 implementation); there isn't a lot of lot of elegant options available for programmatic registration of session and request scoped beans. Simply put, you either have to invoke the implementation specific classes, or you will have to create and destroy i.e. manage the beans yourself instead of relying on the JSF implementation to do this.
Populating the request and session scopes with the beans (the unmanaged way)
Note - This is referred to as the "unmanaged way" because you are constructing the beans, and not the container. Annotations like #PostConstruct and #PreDestroy will not work, unless you process them yourself and invoke the appropriate methods. Even dependency-injection won't work.
EL expressions are always evaluated at runtime, so it gives you enough opportunity to populate the scope with the beans before evaluation (which allows for shooting yourself in the foot, if you have the chance to do so). In Mojarra (and possibly other JSF implementations), the EL resolver will rely on the services of a ScopeHandler (or an equivalent class) to resolve the EL expression values. Mojarra uses the classes ApplicationScopeHandler, RequestScopeHandler and SessionScopeHandler to obtain the values from the different scopes.
You can populate the contents of the Session and Request scopes after after a new session is created, or before a request is processed by the JSF implementation.
Session scope population can be done (ideally using a HttpSessionListener) using:
HttpSession session = request.getSession(false);
session == null ? null : session.setAttribute("<keyname>", new Bean());
The keyname must match the values you are using to reference the bean in EL expressions.
In a similar manner, you can populate the request scope (ideally done in a filter) using:
ServletRequest request = ... // get the reference to the servlet request object
request.setAttribute("<keyname>", new Bean());
If you need to understand how this works, you should take a look at the classes com.sun.faces.context.SessionMap, com.sun.faces.context.RequestMap and com.sun.faces.context.ApplicationMap to see how the context maps are managed internally, and used by the SessionScopeHandler, RequestScopeHandler and ApplicationScopeHandler classes that are static inner classes of the ScopeManager (another static inner) class of the com.sun.faces.mgbean.BeanManager class. The BeanManager class is the one that contains the managed bean registrations, and the next section discusses how to "hack into" the registration process of Mojarra.
Using the Mojarra classes to register the beans
Registration of managed beans in the Mojarra implementation is done by the public void register(ManagedBeanInfo beanInfo) method of the com.sun.faces.mgbean.BeanManager class. It is not trivial to access the BeanManager class using the JSF or Servlet APIs alone. There is however the ApplicationAssociate class of Mojarra that creates the BeanManager instance, and can be accessed using the getCurrentInstance() method. The other answer by Thomas already demonstartes how to register the managed bean programmatically:
ApplicationAssociate.getCurrentInstance().getBeanManager().register(...)
There is a caveat with the above approach. It is unlikely that this approach will work in the init method of Servlet for the simple reason that the getCurrentInstance method relies on a ThreadLocal variable to retrieve the ApplicationAssociate instance. The thread local variable is initialized by the com.sun.faces.application.WebappLifecycleListener class, so you must reproduce the mechanism used by the WebappLifecycleListener class, of invoking the ApplicationAssociate getInstance(ServletContext context) method, to gain access to the ApplicationAssociate instance. The following code therefore, might be (as I have not attempted using it) a better one, if you are willing to use Mojarra specific classes:
ServletContext sc = ... //get the ServletContext reference;
ApplicationAssociate.getInstance(sc).getBeanManager().register(...)
You must still watch out for quirks arising out of this mechanism as it is quite possible that some of the Mojarra classes and instances would not have been loaded or initialized before your Servlet. I would therefore suggest loading attempting to configure your servlet with a load-on-startup value that is higher than the one used by the FacesServlet.
from within a Servlet init()
So, it concerns a non-JSF request. The FacesContext#getCurrentInstance() would return null here, so it's of no use for you here.
It's good to know that JSF application scoped managed beans are basically stored as an attribute of the ServletContext. In the init() method you've the ServletContext at your hands by the inherited getServletContext() method. So, the following should do:
#Override
public void init() {
getServletContext().setAttribute("managedBeanName", new BackingBean());
}
That's it. It'll be available in JSF by #{managedBeanName}.
Try FacesContext.currentInstance().getExternalContext().getApplicationMap().put(name, bean);, i.e. put the managed bean instance into the map using the name you want to use in your expressions.
Edit:
To register the bean, try calling: ApplicationAssociate.getCurrentInstance().getBeanManager().register(...) and pass a ManagedBeanInfo you filled.
The following code registers the managed bean correcly using FacesContext, but it requires the servlet request and response. You could use the code and initialize it lazily using servlet and not during the init.
Usage:
UserBean ub = (UserBean)
Example.getBean(servletRequest, servletResponse, "user", UserBean.class);
Source:
import javax.el.ExpressionFactory;
import javax.el.ValueExpression;
import javax.faces.FactoryFinder;
import javax.faces.component.UIViewRoot;
import javax.faces.context.FacesContext;
import javax.faces.context.FacesContextFactory;
import javax.faces.lifecycle.LifecycleFactory;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
class Example {
public static Object getBean(HttpServletRequest request, HttpServletResponse response, String beanName, Class expectedType){
FacesContext ctx = getFacesContext(request, response);
ValueExpression vex = ctx.getApplication().getExpressionFactory().createValueExpression(ctx.getELContext(), "#{"+beanName+"}", expectedType);
return vex.getValue(ctx.getELContext());
}
private static FacesContext getFacesContext(HttpServletRequest request, HttpServletResponse response) {
FacesContext facesContext = FacesContext.getCurrentInstance();
if (facesContext == null) {
facesContext = ((FacesContextFactory) FactoryFinder.getFactory(FactoryFinder.FACES_CONTEXT_FACTORY)).
getFacesContext(request.getSession().getServletContext(), request, response,
((LifecycleFactory) FactoryFinder.getFactory(FactoryFinder.LIFECYCLE_FACTORY))
.getLifecycle(LifecycleFactory.DEFAULT_LIFECYCLE));
InnerFacesContext.setFacesContextAsCurrentInstance(facesContext);
facesContext.setViewRoot(facesContext.getApplication().getViewHandler().createView(facesContext, ""));
}
return facesContext;
}
private abstract static class InnerFacesContext extends FacesContext {
protected static void setFacesContextAsCurrentInstance(FacesContext facesContext) {
FacesContext.setCurrentInstance(facesContext);
}
}
}
Say I have registered my beans using
ApplicationAssociate.getInstance(sc).getBeanManager().register(...)
Now It is working fine, But then there is a server restart and
my beans are destroyed, How on startup I can get the same bean registered.