Spring #Scope("request") does not work with Servlet 3.0 - java

I am trying to use a request scoped been inside my servlet 3.0 application.
I am not using the web.xml but an implementation of WebApplicationInitializer. The onStartup method looks like this:
#Override
public void onStartup(ServletContext servletContext) throws ServletException {
AnnotationConfigWebApplicationContext applicationContext =
new AnnotationConfigWebApplicationContext();
applicationContext.setServletContext(servletContext);
applicationContext.scan("package containing proxy scoped bean and other stuff");
applicationContext.refresh();
servletContext.addListener(new ContextLoaderListener(applicationContext));
servletContext.addListener(new RequestContextListener());
}
and the request scoped bean looks like this:
#Component
#Scope(value = "request", proxyMode = ScopedProxyMode.TARGET_CLASS)
public class CallerInformation {
private String clientIp;
public String getClientIp() {
return clientIp;
}
public void setClientIp(String clientIp) {
this.clientIp = clientIp;
}
}
Now the injected "CallerInformation" is not a CGLIB-proxy but behaves like prototype scoped, it is a different instance in every class and it does not hold any information through the request...
Any ideas what I am doing wrong?
EDIT:
I have tried the same scenario with servlet 2.5 and web.xml config and it worked like hell ;)

the behavior is right.
the instance only lives the one request.
see this site for further information.
try to inject the spring-component with the #Autowired annotation

Related

Spring Inject Autowired SessionAttribute into Service Layer

Is there a way to #Inject/#Autowired a SessionAttribute into the #Service layer directly without passing it through a #Controller?
I'm looking for something like this:
#Autowired
#SessionAttribute("userprincipal")
UserPrincipal principal;
Possible Solution:
#Configuration
public class ApplicationConfig {
#Bean
#Scope(value = WebApplicationContext.SCOPE_SESSION, proxyMode = ScopedProxyMode.TARGET_CLASS)
public UserPrincipal sessionUserPrincipal() {
// not sure here the user does not exist at creation of bean
}
}
My solution, hopefully this will save someone else some time.
Caution: The injected dependency is hidden, this will cause problems if used outside a session. Use Optional<T> if this is the case and handle internally. If you share your code your team will not be aware of the required dependency.
Testing: When testing you will be required to provide the session bean for #Autowired functionality.
Session Bean Class:
public class SessionUserPrincipal implements Serializable {
private static final long serialVersionUID = 1L;
private UserPrincipal principal;
public SessionUserPrincipal() {}
// mutator methods omitted
}
return Optional<T> if session attribute is not guarantied to be available
Add Bean to Context:
#Configuration
public class WebServletContextConfiguration implements WebMvcConfigurer {
#Bean
#Scope(value = WebApplicationContext.SCOPE_SESSION, proxyMode = ScopedProxyMode.TARGET_CLASS)
public SessionUserPrincipal sessionUserPrincipal() {
return new SessionUserPrincipal();
}
}
Add RequestContextListener to web.xml
<listener>
<listener-class>org.springframework.web.context.request.RequestContextListener</listener-class>
</listener>
this is a requirement for the code below to work. It exposes state necessary to implement session scope. By default that state is exposed by DispatcherServlet, so it's not available before request enters DispatcherServlet (Spring Security filters). You will get an exception if you try to #Autowire a session bean before its available.
Add session attribute to session #Bean on successful authentication.
public class CustomAuthenticationSuccessHandler implements AuthenticationSuccessHandler {
#Autowired SessionUserPrincipal sessionUserPrincipal;
#Override
public void onAuthenticationSuccess(
HttpServletRequest request,
HttpServletResponse response,
Authentication authentication) throws IOException, ServletException
{
// find/get userprincipal code omitted
sessionUserPrincipal.setPrincipal(userprincipal);
}
}
Use session bean:
#Service
public class DefaultSomeService implements SomeService {
#Autowired private SessionUserPrincipal sessionUserPrincipal;
}

Spring using #Value annotation in Filter

I am currently working on a Spring project and I am making a new filter that checks if a valid JWT has been sent in the request.
I am running into an issue where I can't get a value from my application.yml file using the #Value annotation like so.
#Component
#Order(2)
public class JwtConfiguration implements Filter {
#Value("${jwt.secret}")
private String jwtSecret;
I know this works fine because I have the same thing in my unit test.
I have read somewhere that the filter is not in the application context so it will not have access to configuration and I will not be able to autowire dependencies.
Does anyone know a good technique for getting values from my application.yml to my filter?
I am also not using any XML configuration and would prefer a solution that doesn't use them.
I am using Spring Boot version 1.3.3.
This can be achieved by implementing ServletContextInitializer. See below sample code.
#Configuration
public class WebConfigurer implements ServletContextInitializer {
#Value("${jwt.secret}")
private String jwtSecret;
#Override
public void onStartup(ServletContext servletContext) throws ServletException {
EnumSet<DispatcherType> disps = EnumSet.of(DispatcherType.REQUEST, DispatcherType.FORWARD, DispatcherType.ASYNC);
initFilter(servletContext, disps);
}
private void initFilter(ServletContext servletContext,
EnumSet<DispatcherType> disps) {
FilterRegistration.Dynamic myFilter =
servletContext.addFilter("myFilter",
new MyFilterClass(jwtSecret));
// You can pass null as first parameter to below API calls
myFilter.addMappingForUrlPatterns(disps, true, "/content/*");
myFilter.addMappingForUrlPatterns(disps, true, "/app/*");
myFilter.setAsyncSupported(true);
}
}
Edit/Update:
I suppose there is another way to add filters using Java Config
You can use FilterRegistrationBean to register the filters. Here you can set the order using setOrder method. But think it will create as many ServletContextInitializer as there are filters because FilterRegistrationBean is a ServletContextInitializer
See
org.springframework.web.filter.DelegatingFilterProxy
I will describe the way to do it in Spring boot using DelegatingFilterProxy:
1) add to your application.java this bean:
#Bean
public FilterRegistrationBean securityFilterChainRegistration() {
DelegatingFilterProxy delegatingFilterProxy = new DelegatingFilterProxy();
delegatingFilterProxy.setTargetBeanName(AbstractSecurityWebApplicationInitializer.DEFAULT_FILTER_NAME);
FilterRegistrationBean registrationBean = new FilterRegistrationBean(delegatingFilterProxy);
registrationBean.setName(AbstractSecurityWebApplicationInitializer.DEFAULT_FILTER_NAME);
registrationBean.addUrlPatterns("/*");
return registrationBean;
}
2) create your filterbean:
#Component(AbstractSecurityWebApplicationInitializer.DEFAULT_FILTER_NAME)
public class TokenFilter extends GenericFilterBean {
#Value("${path.to.client.id}") String clientId;
#Autowired
RequestDumperFilter requestDumperFilter;
#Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
// do stuff.
}}
This is basically it. Now you can play around with your favorite way of protecting resources. For example, I added #EnableGlobalMethodSecurity(prePostEnabled = true) annotation to my application class, and protected resources with things like #PreAuthorize("hasRole('ROLE_USER')").

Set a Spring REST Controller welcome-file

I'm building a RESTful API and have a Spring REST Controller (#RestController) and an annotation-based configuration. I'd like to have my project's welcome-file be a .html or .jsp file with the API documentation.
In other web projects I would place a welcome-file-list in my web.xml, but in this particular project I can't seem to get it to work (preferrably using Java and annotations).
This is my WebApplicationInitializer
public class WebAppInitializer implements WebApplicationInitializer {
public void onStartup(ServletContext servletContext) throws ServletException {
AnnotationConfigWebApplicationContext context = new AnnotationConfigWebApplicationContext();
context.register(ApplicationConfig.class);
context.setServletContext(servletContext);
ServletRegistration.Dynamic dynamic = servletContext.addServlet("dispatcher",
new DispatcherServlet(context));
dynamic.addMapping("/");
dynamic.setLoadOnStartup(1);
}
}
This is my WebMvcConfigurerAdapter
#Configuration
#ComponentScan("controller")
#EnableWebMvc
public class ApplicationConfig extends WebMvcConfigurerAdapter {
#Bean
public Application application() {
return new Application("Memory");
}
}
And this is a small part of my REST Controller
#RestController
#RequestMapping("/categories")
public class CategoryRestController {
#Autowired
Application application;
#RequestMapping(method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<Map<Integer, Category>> getCategories(){
if(application.getCategories().isEmpty()) {
return new ResponseEntity<Map<Integer, Category>>(HttpStatus.NO_CONTENT);
}
return new ResponseEntity<Map<Integer, Category>>(application.getCategories(), HttpStatus.OK);
}
}
So far I've tried:
Adding just a web.xml with a <welcome-file-list> with a <welcome-file>. (no luck there)
Moving the #RequestMapping("/categories") in the Controller from the class level to all of the methods, and adding a new method with #RequestMapping("/"), which returns either a String or a ModelAndView with the view name. (the former just returned a blank page with the String, for the latter no mapping could be found)
As suggested here: a combination of both, where my web.xml <welcome-file> is "/index", combined with #RequestMapping(value="/index") returning a new ModelAndView("index"), and a ViewResolver in my configuration class. (returns a Warning: No mapping found in DispatcherServlet with name 'dispatcher', even though "/index" is successfully mapped. Manually adding "/index" to the URL successfully resolves it to index.jsp)
When specifying a controller to handle your index page you should use a #Controller not a #RestController. Although the #RestController is a #Controller it doesn't resolve to a view but returns the result as is to the client. When using a #Controller when returning a String it will resolve to the name of a view.
#Controller
public class IndexController {
#RequestMapping("/")
public String index() {
return "index";
}
}
However there is an easier way to configure this and you don't need a controller for it. Configure a view controller. In your configuration class simply override/implement the addViewControllers method.
public void addViewControllers(ViewControllerRegistry registry) {
registry.addViewController("/").setViewName("index");
}
That way you don't even need to create a class for it.

Spring Jersey to inject ContainerRequestContext on request scoped class

I already looked at a lot of posts and nothing seems to work quite as i liked it to.
I want to inject an object into ContainerRequestContext properties from a filter and retrieve it later in other classes.
here is my filter:
#Priority(Priorities.AUTHENTICATION)
public class AuthorizationFilter implements ContainerRequestFilter {
#Override
public void filter(ContainerRequestContext containerRequestContext) throws IOException {
containerRequestContext.setProperty("myObject", new Object());
}
}
here is the class I want access to ContainerRequestContext:
#Provider
public class SessionContextProvider implements ISessionContextProvider {
#Context
private ContainerRequestContext request;
#Override
public Object getSessionContext() {
return request.getProperty("mySessionContext");
}
}
and my spring config:
#Bean(name="sessionContextProvider")
#Scope(value = "request", proxyMode = ScopedProxyMode.INTERFACES)
public ISessionContextProvider sessionContextProvider() {
return new SessionContextProvider();
}
Everything works as expected if I inject ContainerRequestContext into my web resource. However if call my provider class ContainerRequestContext is always null.
I don't seem why this would not work.
Reagrds
Jonas
The problem is that with the Jersey/Spring integration, it allows us to successfully inject Spring beans into Jersey components, but this is not always true the other way around.
Jersey has it's own DI framework, HK21, and it is responsible for handle the injections with Jersey components. With the Jersey Spring integration, Jersey will lookup the Spring Bean, and take it as is, it won't inject it with any dependencies, I guess assuming Spring should take care of it's own injections.
That being said, if you don't require the ISessionContextProvider to be a Spring bean, then you can just make it an HK2 service. It's pretty simple. If you don't require any special initialization, you can just let HK2 create it. Here a simple configuration
public JerseyConfig extends ResourceConfig {
public JerseyConfig() {
register(new AbstractBinder() {
bind(SessionContextProvider.class)
.to(ISessionContextProvider.class)
.in(RequestScoped.class);
});
}
}
And that's it. You have an injectable ISessionContextProvider2.
If you require the ISessionContextProvider provider to be a Spring bean, then another option is to grab the bean from the Spring ApplicatinContext, and explicitly inject it yourself, using HK2's analogue of the ApplicationContext, its ServiceLocator. To do that we would need to use a Factory to do all the work transparently, so you can still inject the bean without doing any extra work on the outside
import javax.inject.Inject;
import org.glassfish.hk2.api.Factory;
import org.glassfish.hk2.api.ServiceLocator;
import org.springframework.context.ApplicationContext;
public class SessionContextProviderFactory
implements Factory<SessionContextProvider> {
private final ISessionContextProvider provider;
#Inject
public SessionContextProviderFactory(ApplicationContext ctx,
ServiceLocator locator) {
provider = ctx.getBean(ISessionContextProvider.class);
locator.inject(provider);
}
#Override
public ISessionContextProvider provide() {
return provider;
}
#Override
public void dispost(ISessionContextProvider provider) { /* noop */ }
}
Then just register the factory
public JerseyConfig extends ResourceConfig {
public JerseyConfig() {
register(new AbstractBinder() {
bindFactory(SessionContextProviderFactory.class)
.to(ISessionContextProvider.class)
.in(RequestScoped.class);
});
}
}
1 - hk2
2 - See also Dependency injection with Jersey 2.0
I found a workaround. I could inject the Spring HttpServletRequest into my AuthorizationFilter and set the SessionContext on this one.
#Autowired
private HttpServletRequest request;
....
request.setAttribute("mySessionContext", sessionContext);
And then because HttpServletRequest is known to spring and besically represents the same thing in my SessionContextProvider I do the same:
#Autowired
private HttpServletRequest request;
#Override
public SessionContext getSessionContext() {
return (SessionContext) request.getAttribute("mySessionContext");
}
I dont think this is the best solution possible. But it works. Ill wait for peeskillet for any other input if there is a better solution.
Regards
Jonas

Spring RESTful Web service and bean "request" and "session" scope

I am using the pure example code of simple REST service from the spring guide as a base:
http://spring.io/guides/gs/rest-service/
I have added single Bean configuration:
#Configuration
public class Config {
#Bean
#Scope(value = WebApplicationContext.SCOPE_REQUEST)
public RequestData requestHelper() {
return new RequestData();
}
}
Then my modified controller looks as follows:
#RestController
public class GreetingController {
private static final String template = "Hello, %s!";
private final AtomicLong counter = new AtomicLong();
AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(Config.class);
#RequestMapping("/greeting")
public Greeting greeting(#RequestParam(value="name", defaultValue="World") String name) {
System.out.println(applicationContext.getBean(RequestData.class));
return new Greeting(counter.incrementAndGet(),
String.format(template, name));
}
}
and I am getting
java.lang.IllegalStateException: No Scope registered for scope 'session']
as the result of calling "/greeting"
I have read some description here:
http://docs.spring.io/spring/docs/current/spring-framework-reference/html/beans.html however I am still confused.
they write:
"The request, session, and global session scopes are only available if you use a web-aware Spring ApplicationContext implementation".
Does it mean that "AnnotationConfigApplicationContext" which I am using is not allowed in such case? Am I forced to use some xml configuration instead?
The quote
web-aware Spring ApplicationContext implementation
refers to an appropriate subclass of WebApplicationContext. You're instantiating a AnnotationConfigApplicationContext which is not a subtype of WebApplicationContext and which does not register the SESSION and REQUEST scopes.
It also makes very little sense to create a brand new ApplicationContext in your #RestController. The #RestController object is already a bean within a Spring WebApplicationContext. Just add your new request scoped #Bean to that context and autowire into your controller.

Categories

Resources