How to load Spring AOP into web app? - spring

I was trying to implement Spring AOP in web app. Unfortunately all the sample code I found on the Web are console app. I was running out of clue how could I do it in web app?
In web.xml file, I load the applicationContext.xml like this:
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/applicationContext.xml</param-value>
</context-param>
In applicationContext.xml file, I have ProxyFactoryBean defined like this:
<bean id="theBo" class="my.package.TheBo">
...
</bean>
<bean id="theProxy" class="org.springframework.aop.framework.ProxyFactoryBean">
<property name="proxyInterfaces">
<list>
<value>my.package.ITheBo</value>
</list>
</property>
<property name="target" ref="theBo"/>
<property name="interceptorNames">
<list>
<value>loggingBeforeAdvice</value>
</list>
</property>
</bean>
My situation now is I don't know where is the best place to put this code:
ApplicationContext context = new ClassPathXmlApplicationContext("WEB-INF/applicationContext.xml");
theBo = (ITheBo) context.getBean("theProxy");
If this was a console app, I would rather put it in the main(), but how could I do it in web app?

You do not need the following piece of code to load the context:
ApplicationContext context = new ClassPathXmlApplicationContext("WEBINF/applicationContext.xml");
theBo = (ITheBo) context.getBean("theProxy");
You have to add the ContextLoaderListener to your web.xml file:
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
Now when your web application starts the contexts declared in the <context-param> contextConfigLocation are loaded. In your case '/WEB-INF/applicationContext.xml'.
If you need your context in a specific class you can implement the ApplicationContextAware interface to retrieve it.
For the rest, your webapp is now a basic spring application where you can wire your classes as you would normally do.

Thanks to #Dave Newton giving me the clue. In order for me to inject theProxy from the web, for my case, it was JSF, I have to put following code in faces-config.xml.
<application>
<variable-resolver>
org.springframework.web.jsf.DelegatingVariableResolver
</variable-resolver>
</application>
<managed-bean>
<managed-bean-name>theAction</managed-bean-name>
<managed-bean-class>org.huahsin68.theAction</managed-bean-class>
<managed-bean-scope>session</managed-bean-scope>
<managed-property>
<property-name>theBo</property-name>
<value>#{theProxy}</value>
</managed-property>
</managed-bean>
And the put in the listener provided by #tom as well into web.xml.

Related

Transactional annotation does not save on exit

I have configured spring jpa with annotation driven. I would expect the following code to persist the changes to the database upon method exist.
#Transactional
public Foo changeValue(int id){
final Foo foo = fooRepository.findOne(id);
if(foo != null){
foo.setValue("new value");
//fooRepository.save(foo);
}
}
FooRepository is a JPARepository and the foo object is getting fetched so it is managed. Based on what I read about #Transactional I would expect that even without the fooRepository.save(foo) call the changes in foo's value column in the database would be persisted upon method exist. However, this only happens if I uncomment the call to fooRepository.save(foo)
No exceptions are thrown and the configuration for the jpa datasource is as below.
<bean id="entityManagerFactory" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
...
<bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager"
p:entityManagerFactory-ref="entityManagerFactory" />
<tx:annotation-driven transaction-manager="transactionManager" />
<context:component-scan
base-package="com.example.package.data" />
<jpa:repositories
base-package="com.example.package.data.repository"
entity-manager-factory-ref="entityManagerFactory"
transaction-manager-ref="transactionManager" />
Any ideas?
Update
I do have a ContextLoaderListener and a DispatcherServlet but I do not have component scan for both.
<servlet>
<servlet-name>appServlet</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/spring/appServlet/servlet-context.xml</param-value>
</init-param>
</servlet>
....
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
Update
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/spring/root-context.xml</param-value>
</context-param>
And there I do
<context:component-scan base-package="com.example.package">
<context:exclude-filter type="regex"
expression="com.example.package.data.*" />
<context:exclude-filter type="regex"
expression="com.example.package.web.controller.*" />
<context:exclude-filter type="regex"
expression="com.example.package.web.service.*" />
</context:component-scan>
And in servlet-context
<context:component-scan base-package="com.example.package.web" />
Your general approach is correct. The changes should get saved and committed on exit of the method. While this approach works great when it works it is a bitch to debug.
Your configuration looks ok to me, so I would double check, that you actually have Spring Beans at your disposal and not some instance that you created simply by calling the constructor.
To verify this, just put a breakpoint at the code point where the annotated method gets called. The bean with the annotated method should NOT by of the class you wrote, but some ProxySomething class.
This article also provides some pointers what might be wrong with your setup.
If you have both a ContextLoaderListener and DispatcherServlet they both load a configuration file.
Judging from your configuration the services are loaded by the DispatcherServlet and your <tx:annotation-driven /> is loaded by the ContextLoaderListener. They both create an ApplicationContext and as AOP is only applied to the beans in the same context your services will not be transactional (Transaction management is done by using AOP).
As a rule of thumb your ContextLoaderListener should contain all shared or global resources (like services, daos, datasources etc.) whereas your DispatcherServlet should only contain web related beans like controllers, view resolvers etc.

Beans injected into Apache Wink with Spring aren't registered

Following on from How do I inject a Spring bean into Apache Wink?
I'm now using wink-spring-support and I thought I had things set up correctly.
web.xml includes:
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>
classpath:META-INF/wink/wink-core-context.xml
classpath:applicationContext.xml
</param-value>
</context-param>
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<servlet>
<servlet-name>restServlet</servlet-name>
<servlet-class>org.apache.wink.server.internal.servlet.RestServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>restServlet</servlet-name>
<url-pattern>/rest/*</url-pattern>
</servlet-mapping>
META-INF/wink/wink-core-context.xml contains:
<bean class="org.apache.wink.spring.Registrar">
<property name="instances">
<set>
<ref bean="myservice" />
</set>
</property>
</bean>
<bean id="myservice" class="mystuff.ServiceImpl"/>
There's a #Autowired annotation in mystuff.ServiceImpl that injects other Spring stuff, and mystuff.ServiceImpl implements a JAX-RS annotated interface and itself includes a JAX-RS #Path("/services") annotation.
I can see Spring loading up this stuff just fine, including the myservice bean. However when I request my resources, I get a 404 not found. As Wink starts, I can see a couple of log entries that might indicate the problem:
applicationConfigLocation property was not defined
Using application classes null named in init-param applicationConfigLocation
Have I missed something somewhere? Any advice?
The problem was my misunderstanding the docs.
There is a Spring configuration META-INF/server/wink-core-context.xml provided with wink-spring-support. This registers the BeanPostProcessors that actually do the setup and must be referenced from contextConfigLocation.
I thought that I put my configuration in there, which explains why the application didn't get registered with Wink on startup.

How do I catch Spring errors when starting up a Tomcat webapp?

I have a webapp running in Tomcat which uses Spring for dependency injection. (It's a GWT application, but I don't think that makes much of a difference to the solution I'm looking for.)
My web.xml file is of the following format:
<web-app>
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/applicationContext.xml</param-value>
</context-param>
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<!-- Servlets -->
<servlet>
<servlet-name>dispatch</servlet-name>
<servlet-class>com.example.my.gwt.dispatch.DispatchServlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>dispatch</servlet-name>
<url-pattern>/my_gwt/dispatch</url-pattern>
</servlet-mapping>
... more servlets ...
</web-app>
One of the things my Spring configuration does is to connect to a databse via Hibernate:
<bean id="datasource"
class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName" value="${db.driver}" />
<property name="url"
value="${db.url}" />
<property name="username" value="${db.username}" />
<property name="password" value="${db.password}" />
</bean>
<bean id="databaseSessionFactory"
class="org.springframework.orm.hibernate3.annotation.AnnotationSessionFactoryBean">
<property name="dataSource" ref="datasource" />
<property name="packagesToScan">
<array>
<value>com.example.my.gwt.model</value>
</array>
</property>
<property name="hibernateProperties">
<props>
<prop key="hibernate.hbm2ddl.auto">update</prop>
</props>
</property>
</bean>
If the database is unavailable, this causes an org.h2.jdbc.JdbcSQLException to be thrown, so the Spring initialisation does not continue, so the rest of the webapp cannot be used. Navigating to the webapp's URL gives an HTTP 503 'Service Unavailable' error.
What I want to do is to catch that error and display a page to the user (when they first navigate to the app) explaining what the problem is likely to be and suggested fixes. How can I do this?
I have tried using a custom ContextLoaderListener class that delegates to the one in the XML above, but catches any exceptions. This allows me to catch the exception, but there is not much I can do - the web.xml is still pointing the user's request to a servlet that is not running after the Spring initialisation has failed. Is there any way that I can change the webapp config when I catch that exception, so that it doesn't try to load the servlets from the web.xml and perhaps changes the welcome file to point to a page about the error? Or is there any other way that I can make the webapp gracefully handle this exception?
Thanks
Basically you're asking if you can have a functioning web application after the web application fails to start up.
You could try configuring a 503 handler page and/or have a welcome page, not dependent on Spring, that checks for something in the application context that's set only on a good spin up. If it didn't spin up, the exception you've already captured could be placed into the app context.
Not sure if anything in the app, even web.xml-only resources, if Spring doesn't spin up, though.
You could add a Servlet Filter to your web app that would intercept all the requests to the Spring servlet and forward to your custom error page if the Spring initialization has failed.

spring httpinvoker client can not find the service url

I have following configuration for the web application.
web.xml
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>
classpath:mail-application-context.xml
....
</context-param>
....
<servlet>
<servlet-name>mailbox</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>mailbox</servlet-name>
<url-pattern>*.htm</url-pattern>
</servlet-mapping>
mailbox-servlet.xml
<bean name="remoteProvisioningServiceImpl" class="pw.domain.service.RemoteProvisioningServiceImpl"/>
<bean name="/remote/domain/provision.htm" class="org.springframework.remoting.httpinvoker.HttpInvokerServiceExporter">
<property name="service" ref="remoteProvisioningServiceImpl" />
<property name="serviceInterface" value="pw.domain.service.RemoteProvisioningService" />
</bean>
I have the clients configured properly, like following, in mail-application-context.xml
<bean id="remoteProvisioningService1" class="org.springframework.remoting.httpinvoker.HttpInvokerProxyFactoryBean">
<property name="serviceUrl" value="http://${mailapp.dc.host.1.url}/remote/domain/provision.htm" />
<property name="serviceInterface" value="pw.domain.service.RemoteProvisioningService" />
</bean>
<bean id="remoteProvisioningService2" class="org.springframework.remoting.httpinvoker.HttpInvokerProxyFactoryBean">
<property name="serviceUrl" value="http://${mailapp.dc.host.2.url}/remote/domain/provision.htm" />
<property name="serviceInterface" value="pw.domain.service.RemoteProvisioningService" />
</bean>
And now... the problem.
On an appropriate event, the clients rightly call service through httpinvoker facility but the response is http-404. In nginx logs, i trace that spring makes a POST call to http://{host}/remote/domain/provision.htm.
Questions :
Do i need to create a separate app-context for httpInvoker bit? i mean, i already have one web context to deal with normal web operations. Do i need to declare {another-context} in web.xml for httpInvoker facility and having the service config. defined in {another-context}-servlet.xml?
If not 1, what is wrong in my configuration?
Got it.. Question 1 above has the answer attached to it. I need to create a separate web context for http-invoking facility to work.
This is because, in my normal webapp context (mailbox), i already have defined SimpleUrlHandlerMapping which maps appropriate urls to controllers. There it doesnt find place for http-invoker-serivce i want to define. When i separated web contexts, it worked like a charm! :D
Happy coding! :)

Faces conversion service is not found for converting 'dataModel'

While creating my first swf application with JSF integration, I get the following error : Unable to load class 'dataModel' when parsing my flow definition on the first request.
It appears that the FacesConversionService (the class that understands the 'dataModel' alias) is not invoked while trying to find a class for that alias, although my webflow application context contains the <faces:flow-builder-services/> tag in it.
Doing some debug while trying to find out the cause, I noticed the following:
the xml file containing <faces:flow-builder-services/> is loaded by the context loader listener, and the facesConversionService bean is registered
the flow handler mapping and adapter are correctly invoked and the request is forwarded to the expected flow, which definition is created on the fly.
while creating internal infrastructure objects for the flow, swf creates a web application context on top of the app context loaded by the listener. This new wac contains the definitions for the swf scopes, etc, and has the listener's application context from above as parent.
the flow definition file for my flow is found and parsed, and when it tries to find the class for the result type of an evaluate element, the conversion service does not know about the 'dataModel' alias.
Actually, the funny thing on the last point is that the conversion service is either looked up as a local bean in the application context created on point 3. above, or -if not found- looked up in the parent flow builder context.
The faces conversion service is neither a local bean of the context (it is registered in the parent context of the flow context), nor is it registered as the conversion service of the flow builder context (at least the FlowBuilderContextImpl that is created by the <flow:flow-registry/> element).
Does anybody know what went wrong ?
I don't think it's related to the application server, but just in case, I'm running on websphere 6.1.0.23, with spring 2.5.6 and webflow 2.0.8.RELEASE.
Here are my configuration files:
web.xml:
<listener>
<listener-class>
org.springframework.web.context.ContextLoaderListener
</listener-class>
</listener>
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/applicationContext-*.xml</param-value>
</context-param>
...
<servlet>
<servlet-name>Spring MVC Dispatcher Servlet</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value></param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>Spring MVC Dispatcher Servlet</servlet-name>
<url-pattern>/swf/*</url-pattern>
</servlet-mapping>
Beans definitions for the MVC part (/WEB-INF/applicationContext-webmvc.xml):
<bean class="org.springframework.webflow.mvc.servlet.FlowHandlerMapping">
<property name="flowRegistry" ref="flowRegistry" />
<property name="defaultHandler">
<bean class="org.springframework.web.servlet.mvc.UrlFilenameViewController" />
</property>
</bean>
<bean class="org.springframework.webflow.mvc.servlet.FlowHandlerAdapter">
<property name="flowExecutor" ref="flowExecutor" />
</bean>
<bean id="faceletsViewResolver"
class="org.springframework.web.servlet.view.UrlBasedViewResolver">
<property name="viewClass" value="org.springframework.faces.mvc.JsfView" />
<property name="prefix" value="/WEB-INF/" />
<property name="suffix" value=".jspx" />
</bean>
<bean class="org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter" />
Beans definitions for the swf part (/WEB-INF/applicationContext-webflow.xml):
<flow:flow-registry id="flowRegistry" base-path="/WEB-INF/flows">
<flow:flow-location path="/navigation/navigation.xml"/>
<flow:flow-location path="/edition/edition.xml"/>
</flow:flow-registry>
<flow:flow-executor id="flowExecutor">
<flow:flow-execution-listeners>
<flow:listener ref="jpaFlowExecutionListener" criteria="edition"/>
</flow:flow-execution-listeners>
</flow:flow-executor>
<faces:flow-builder-services id="facesFlowBuilderServices" development="true"/>
<bean id="jpaFlowExecutionListener"
class="org.springframework...JpaFlowExecutionListener">
<constructor-arg ref="entityManagerFactory" />
<constructor-arg ref="transactionManager" />
</bean>
The stack trace for the exception:
[Servlet Error]-[Spring MVC Dispatcher Servlet]: java.lang.IllegalArgumentException: Unable to load class 'dataModel'
at org.springframework.webflow.engine.builder.model.FlowModelFlowBuilder.toClass(FlowModelFlowBuilder.java:965)
at org.springframework.webflow.engine.builder.model.FlowModelFlowBuilder.parseEvaluationActionResultExposer(FlowModelFlowBuilder.java:867)
at org.springframework.webflow.engine.builder.model.FlowModelFlowBuilder.parseEvaluateAction(FlowModelFlowBuilder.java:858)
at org.springframework.webflow.engine.builder.model.FlowModelFlowBuilder.parseActions(FlowModelFlowBuilder.java:834)
at org.springframework.webflow.engine.builder.model.FlowModelFlowBuilder.parseAndAddViewState(FlowModelFlowBuilder.java:547)
at org.springframework.webflow.engine.builder.model.FlowModelFlowBuilder.buildStates(FlowModelFlowBuilder.java:207)
at org.springframework.webflow.engine.builder.FlowAssembler.directAssembly(FlowAssembler.java:106)
at org.springframework.webflow.engine.builder.FlowAssembler.assembleFlow(FlowAssembler.java:91)
at org.springframework.webflow.engine.builder.DefaultFlowHolder.assembleFlow(DefaultFlowHolder.java:109)
at org.springframework.webflow.engine.builder.DefaultFlowHolder.getFlowDefinition(DefaultFlowHolder.java:84)
at org.springframework.webflow.definition.registry.FlowDefinitionRegistryImpl.getFlowDefinition(FlowDefinitionRegistryImpl.java:61)
at org.springframework.webflow.executor.FlowExecutorImpl.launchExecution(FlowExecutorImpl.java:138)
at org.springframework.webflow.mvc.servlet.FlowHandlerAdapter.handle(FlowHandlerAdapter.java:193)
at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:875)
at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:807)
at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:571)
at org.springframework.web.servlet.FrameworkServlet.doGet(FrameworkServlet.java:501)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:743)
I forgot to specify the flow-builder-services attribute on flow:flow-registry element !!! Works perfectly now.
<flow:flow-registry id="flowRegistry" base-path="/WEB-INF/flows" flow-builder-services="facesFlowBuilderServices">
<flow:flow-location path="/navigation/navigation.xml"/>
<flow:flow-location path="/edition/edition.xml"/>
</flow:flow-registry>
<faces:flow-builder-services id="facesFlowBuilderServices" development="true"/>
I had the same problem but it was because I was specifying my own conversion service which extended DefaultConversionService rather than FacesConversionService.

Resources