Mixing web.xml and AbstractAnnotationConfigDispatcherServletInitializer in Spring - java

I have an application on Spring and using Java Configs to configure and initialize my application, so that I have no web.xml. Here is how my web initializer looks like,
public class WebInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {
#Override
public void onStartup(ServletContext servletContext) throws ServletException {
super.onStartup(servletContext);
}
#Override
protected Class<?>[] getRootConfigClasses() {
return new Class<?>[]{PublicApiConfig.class, MobileConfig.class};
}
#Override
protected String[] getServletMappings() {
return new String[]{"/*"};
}
#Override
protected Filter[] getServletFilters() {
CharacterEncodingFilter characterEncodingFilter = new CharacterEncodingFilter();
characterEncodingFilter.setEncoding("UTF-8");
LoggingFilter loggingFilter = new LoggingFilter();
return new Filter[]{characterEncodingFilter, loggingFilter};
}
#Override
protected Class<?>[] getServletConfigClasses() {
return new Class<?>[0];
}
}
I need to implement tomcat session replication, and for the sake of purpose I need to have application as distributable. With traditional web.xml I could add <distributable/> attribute and thats it. However as far as I understand there is no way to do this via Java Configs.
My question is if it is possible to have mixed web.xml and java configs, e.g. to have
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://java.sun.com/xml/ns/javaee"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
version="3.0">
<distributable/>
</web-app>
and include it in WebInitializer.

According to the Servlet 3.0 specification its possible to mix web.xml with Programmatic servlet registration as long as web-app version >= 3.0 and metadata-complete attribute is false (default). With your current configuration it should work

You can use a TomcatEmbeddedServletContainerFactory, and there
#Override
public void customize(Context context){
context.setDistributable(true);
}
You find a complete code example in this thread
spring-boot-application-with-embedded-tomcat-session-clustering
Edit : I am not using Spring Boot in this case, and TomcatEmbeddedServletContainerFactory is not available
The javadoc of WebApplicationInitializer says, its possible to use it together with a web.xml :
WEB-INF/web.xml and WebApplicationInitializer use are not mutually exclusive; for example, web.xml can register one servlet, and a WebApplicationInitializer can register another. An initializer can even modify registrations performed in web.xml through methods such as ServletContext#getServletRegistration(String).

Related

JSF (2.2) ViewScope using Spring 4.3.7 on Weblogic 12.2.1.2 with JDK 8 (on Mac OS and Docker Oracle Linux enviroment)

I try to configure Spring 4.3.7 integration with JSF 2.2.14 and Primefaces 6.0. Spring should be responsible for managing scope and ManagedBean from JSF became Spring's beans.
I have configured Weblogic 12.2.1.2 on Mac OS and on Docker with Oracle Linux.
I read and tested this solution (http://www.concretepage.com/spring-4/spring-4-jsf-2-integration-example-using-autowired-annotation) but didn't work. I found this one (https://github.com/michail-nikolaev/primefaces-spring-scopes/) that prevent memory leaks problems but didn't work too.
I could perceive independent of scope (session or view) Spring is working like a request bean.
My configurations and a bean sample. When I access navigation *.xhtml. Independent if I configure Scope as view or session, the method defined on PostConstruct is called any times and pagination didn't work because the state of view is not persisted. I don't know what could cause this behavior.
WebConfig Spring:
#Configuration
#ComponentScan(basePackages = { "packageC" },basePackageClasses {PackageA.class,PackageB.class})
#lombok.extern.slf4j.Slf4j
public class WebAppConfig{
#Bean
public static CustomScopeConfigurer customScopeConfigurer() {
log.info("create customScopeConfigurer bean");
Map<String, Object> scopes = new HashMap<>();
scopes.put("view", new CustomViewScope());
CustomScopeConfigurer customScopeConfigurer = new CustomScopeConfigurer();
customScopeConfigurer.setScopes(scopes);
return customScopeConfigurer;
}
}
Faces-config.xml:
<?xml version="1.0" encoding="UTF-8"?>
<faces-config xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee
http://xmlns.jcp.org/xml/ns/javaee/web-facesconfig_2_2.xsd"
version="2.2">
<application>
<el-resolver>org.springframework.web.jsf.el.SpringBeanFacesELResolver</el-resolver>
</application>
<lifecycle>
<phase-listener>packagetest.listener.PhaseListener</phase-listener>
</lifecycle>
</faces-config>
WebAppInitializer:
public class WebAppInitializer implements WebApplicationInitializer {
public void onStartup(ServletContext servletContext) throws ServletException {
log.info("call WebAppInitializer.onStartup");
AnnotationConfigWebApplicationContext ctx = new AnnotationConfigWebApplicationContext();
ctx.register(ServiceConfig.class);
ctx.setServletContext(servletContext);
servletContext.addListener(new ContextLoaderListener(ctx));
servletContext.addListener(new RequestContextListener());
log.info("end call WebAppInitializer.onStartup");
}
}
JSF Bean sample:
#Scope("session")
#Component("accountBean")
#lombok.extern.slf4j.Slf4j
public class AccountManagedBeanImpl extends GenericManagedBeanAb<AccountEntity, Long> implements AccountManagedBean {
private static final long serialVersionUID = 4359183101897060165L;
#Autowired
AccountService accountService;
#PostConstruct
public void init() {
log.info("init do bean: AccountManagedBeanImpl. Escopo = session");
this.entity = new AccountEntity();
if (getRequest().getParameter("codigo") != null) {
Optional op = accountService.findByPrimaryKey(Long.parseLong(getRequest().getParameter("codigo")));
if (op.isPresent()) {
this.entity = (AccountEntity) op.get();
}
}
this.entityFilter = new AccountEntity();
this.listEntitySelected = new ArrayList<AccountEntity>(0);
}
I put the same application on Wildfly 9.x and it works. I could conclude CDI on Weblogic has a different behavior and it isn't delegating the context to SPRING DI.
I wouldn't like to use EJB, I prefer to use Spring Service Layer integrating with Spring Data + Spring AOP...
I'm still looking for a solution, but this information can be useful for someone

Spring Websockets not working under custom application context path

I have application which uses Spring 4.3.5 and spring mvc - apache tiles .
I wrote chat according to this article https://spring.io/guides/gs/messaging-stomp-websocket/
Everything is working correctly, if my whole application context path is root so for example : http://example.com/ I recieve following frames in websocket
["SUBSCRIBE\nid:sub-0\ndestination:/chat-messages/TST\n\n\u0000"]
["SEND\ndestination:/chat/message/TST\ncontent-length:52\n\n{\"message\":\"\",\"username\":\"USER\",\"event\":\"ONLINE\"}\u0000"]
["MESSAGE\ndestination:/chat-messages/TST\ncontent-type:application/json;charset=UTF-8\nsubscription:sub-0\nmessage-id:x1jpjyes-1\ncontent-length:230\n\n{..SOME JSON CONTENT....}\u0000"]
Problem is that it stops working, If I add some app context ( and I need to do so on my server)
for example : http://example.com/my-app
No messages received , nor sent
UPDATE: No sending was fixed by adding servletContext.getContextPath() to destination prefixes.
With context, I only got this:
["SUBSCRIBE\nid:sub-0\ndestination:/my-app/chat-messages/TST\n\n\u0000"]
["SEND\ndestination:/my-app/chat/message/TST\ncontent-length:52\n\n{\"message\":\"\",\"username\":\"USER\",\"event\":\"ONLINE\"}\u0000"]
Here is my configurations:
#Configuration
public class WebMvcConfig extends WebMvcConfigurationSupport {
private static final String TILES = "/WEB-INF/tiles/tiles.xml";
private static final String VIEWS = "/WEB-INF/views/**/views.xml";
private static final String RESOURCES_HANDLER = "/resources/";
private static final String RESOURCES_LOCATION = RESOURCES_HANDLER + "**";
#Override
public RequestMappingHandlerMapping requestMappingHandlerMapping() {
RequestMappingHandlerMapping requestMappingHandlerMapping = super
.requestMappingHandlerMapping();
requestMappingHandlerMapping.setUseSuffixPatternMatch(false);
requestMappingHandlerMapping.setUseTrailingSlashMatch(false);
return requestMappingHandlerMapping;
}
#Bean
public TilesViewResolver configureTilesViewResolver() {
return new TilesViewResolver();
}
#Bean
public TilesConfigurer configureTilesConfigurer() {
TilesConfigurer configurer = new TilesConfigurer();
configurer.setDefinitions(TILES, VIEWS);
return configurer;
}
#Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler(RESOURCES_HANDLER).addResourceLocations(
RESOURCES_LOCATION);
}
#Override
public void configureDefaultServletHandling(
DefaultServletHandlerConfigurer configurer) {
configurer.enable();
}
}
WebSocketMesssageBroker
#Configuration
#EnableWebSocketMessageBroker
public class WebSocketConfig extends AbstractWebSocketMessageBrokerConfigurer{
#Autowired
private ServletContext servletContext;
#Override
public void configureMessageBroker(MessageBrokerRegistry config) {
config.enableSimpleBroker("/chat-messages");
config.setApplicationDestinationPrefixes(servletContext.getContextPath() + "/chat");
}
#Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
registry.addEndpoint("/chat-websocket").withSockJS();
}
}
And I have a controller to process everything
#MessageMapping("/message/{projectId}")
#SendTo("/chat-messages/{projectId}")
public ChatResponse sendMessage(#DestinationVariable String projectId, MessageSent message) throw InterruptedException {
//Send reponse back like user online/offline or message posted
return new ChatResponse(chatMessage);
}
In JSP file I have following JS called
var socket = new SockJS('<c:url value="/chat-websocket/"/>');
stompClient.subscribe('<c:url value="/chat-messages/${chatProject.projectId}"/>', function (data) { ....SOME RESPONSE PROCESSING... });
stompClient.send("<c:url value="/chat/message/${chatProject.projectId}"/>", {}, JSON.stringify({.....PAYLOAD TO SEND ---}));
and web.xml
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://java.sun.com/xml/ns/javaee"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
version="3.0">
<filter>
<filter-name>springSecurityFilterChain</filter-name>
<filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
</filter>
<filter-mapping>
<filter-name>springSecurityFilterChain</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<error-page>
<exception-type>org.springframework.security.web.authentication.rememberme.CookieTheftException</exception-type>
<location>/signin</location>
</error-page>
<error-page>
<location>/generalError</location>
</error-page>
<error-page>
<error-code>404</error-code>
<location>/404</location>
</error-page>
<jsp-config>
<jsp-property-group>
<url-pattern>*.jsp</url-pattern>
<trim-directive-whitespaces>true</trim-directive-whitespaces>
</jsp-property-group>
</jsp-config>
</web-app>
I do suspect that this might be something with configuration of either tiles or whole dispatcher in web.xml or something like this :/
Would be very greatfull for hints
I was finnaly able to resolve this issue. It turns out whenever I was creating SockJS subscriber I should pass relative path as param, without any context
(I presume the base websocket opened url already have correct url)
So in order to properly receive subscription events all I had to do was change
stompClient.subscribe('<c:url value="/chat-messages/${chatProject.projectId}"/>', function (data) { ....SOME RESPONSE PROCESSING... });
to this:
stompClient.subscribe('/chat-messages/${chatProject.projectId}', function (data) { ....SOME RESPONSE PROCESSING... });
(without the <c:url> which was returning context path all the time)
So whenever I tried to subscribe to chat-messages using <c:url value="chat-messages/ID">in fact I was subscribing to:
my-app/chat-messages/ID and my controller and config was expecting plain relative chat-messages
That's why after adding contextPath to WebSocketController setApplicationDestinationPrefixes, app started sending correct messages
Those are couple of hours I'm not getting back :)

#ControllerAdvice conflict with ResourceHandler : Spring 4

I am running a Spring 4 web mvc project:
Issue:
My controlleradvice for 404 exception handler is not working. However, if I comment the "addResourceHandlers" method in WebConfig class, it will work. (I can't remove that as it resolves my static resources)_
This is my web config:
#EnableWebMvc
#Configuration
public class WebConfig extends WebMvcConfigurerAdapter {
/*
* Resource handler for static resources
*/
#Override
public void addResourceHandlers(final ResourceHandlerRegistry registry) {
registry.addResourceHandler("/**").addResourceLocations("classpath:/static/");
}
}
And this is my 404 exception handler:
#ControllerAdvice
public class ExceptionController {
#ExceptionHandler(NoHandlerFoundException.class)
public String handle404(Exception e) {
return "error/404";
}
}
If your webapp is using web.xml it's very simple - just add the following (assuming usage of InternalResourceViewResolver with prefix pointing at your WEB-INF view folder and suffix .jsp). You can have multiple error-page elements of other error codes too.
<error-page>
<error-code>404</error-code>
<location>/error</location>
</error-page>
If you are not using web.xml it's a bit more complicated.
If you want to catch the NoHandlerFound exception you first have to tell Spring to throw it via setting a flag in the DispatcherServlet directly.
To do so, in the class that you are extending AbstractAnnotationConfigDispatcherServletInitializer override the onStartup method to expose the DispatcherServlet definition and add manually the needed flag:
#Override
public void onStartup(ServletContext servletContext) throws ServletException {
//...
WebApplicationContext context = getContext();
DispatcherServlet dispatcherServlet = new DispatcherServlet(context);
//we did all this to set the below flag
dispatcherServlet.setThrowExceptionIfNoHandlerFound(true);
ServletRegistration.Dynamic dispatcher = servletContext.addServlet("DispatcherServlet",dispatcherServlet );
//..
}
Then your existing code within ExceptionController should work and intercept the exception

Sharing static content and mvc controllers on same path

Apologies if this feels repetitive, I gather alot of people ask about this but I haven't been able to find an answer that works.
I have a web-app, built using maven. I use Spring 4 MVC to deliver a RESTful Json API. I also have a lot of static content (html, css, js) were I use Angular.js to put a pretty face on the data API.
For the life of me, I can't figure out how to get both of these being served at the same time without messing with their paths.
I'd really like to go to {APP_ROOT}/people/{id} in my browser, and be interacting directly with my REST api without any crap about /api/ or /rest/
I'd really like to go to {APP_ROOT}/css/style.css in my browser, and be served content from src/main/webapp/css/style.css without any crap about resources or static
Additionally, I'd really like to configure all of this with annotated Java classes, and not have any web.xml, application-context.xml, etc.
So, the Spring dispatcher servlet should be handling all of the REST resource paths, and then falling back to the default Tomcat/Jetty handler for the static content. I thought this was exactly the scenario default-servlet-handler was intended for? I can't seem to get it to work.
These are my relevant configuration classes:
WebAppInitializer.java
public class WebAppInitializer extends
AbstractAnnotationConfigDispatcherServletInitializer {
#Override
protected Class<?>[] getRootConfigClasses() {
return new Class<?>[0] ;
}
#Override
protected Class<?>[] getServletConfigClasses() {
return new Class<?>[] { WebConfig.class};
}
#Override
protected String[] getServletMappings() {
return new String[] { "/*" };
}
#Override
protected Filter[] getServletFilters() {
CharacterEncodingFilter characterEncodingFilter = new CharacterEncodingFilter();
characterEncodingFilter.setEncoding("UTF-8");
return new Filter[] { characterEncodingFilter};
}
}
WebConfig.java
#Configuration
#EnableWebMvc
#ComponentScan(basePackages = {"my.example.package"})
public class WebConfig extends WebMvcConfigurerAdapter {
#Override
public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {
configurer.enable();
}
}
With this configuration I can interact with the REST api, but not the static content. The default servlet handler seems to have no effect.
To access static resource like css js or html keep all these files inside webapp folder of your module. Suppose all you static resources are in src/main/webapp/static path you can do mvc resource mapping like below in context xml
<mvc:resources mapping="/index-dev.html" location="/static/index-dev.html"/>
<mvc:resources mapping="/index.html" location="/static/index.html"/>
<mvc:resources mapping="/app.js" location="/static/app.js"/>
<mvc:resources mapping="/appinit.js" location="/static/appinit.js"/>
<mvc:resources mapping="/extjs/**" location="/static/extjs/"/>
<mvc:resources mapping="/app/**" location="/static/app/"/>
<mvc:resources mapping="/static/**" location="/static/static/"/>
<mvc:resources mapping="/static/**" location="/static/"/>
Now since you want to do it without xml you can do it in your WebConfig class like below example
#Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/index.html").addResourceLocations("/static/index.html");
}

what's the equivalent code based configuration to add log4j in spring MVC 3.0+

I am migrating my Spring MVC project to code based configuration, but not sure how to add a listener,
here's what in my web.xml
<context-param>
<param-name>log4jConfigLocation</param-name>
<param-value>WEB-INF/spring/log4j.xml</param-value>
</context-param>
<listener>
<listener-class>org.springframework.web.util.Log4jConfigListener</listener-class>
</listener>
Here's my init class:
#Order(1)
public class Initializer extends
AbstractAnnotationConfigDispatcherServletInitializer {
#Override
protected Class<?>[] getRootConfigClasses() {
return new Class[] { RootConfig.class, SecurityConfig.class };
}
#Override
protected Class<?>[] getServletConfigClasses() {
return new Class[] { WebConfig.class };
}
#Override
protected String[] getServletMappings() {
return new String[] { "/" };
}
}
I checked declaration of AbstractAnnotationConfigDispatcherServletInitializer,
and tried to implement onStartUp() like below, but it complains addListener is not found.
#Override
public void onStartup(ServletContext container) throws ServletException {
org.springframework.web.util.Log4jConfigListener log4jConfigListener = new org.springframework.web.util.Log4jConfigListener();
container.addListener(log4jConfigListener);
super.onStartup(container);
}
<listener> element comes from web.xml, not from spring configuration.
What you are trying to achieve here is to programmaticaly add a Listener. This is a feature of Servlet API 3.0. You will have to use servlet-api 3.0 to be able to do what you want.
For example Servlet api 3.0 is supported in Tomcat 6+ Jetty 8+ etc

Categories

Resources