I work for a large insurance company where we actively use Spring in the backend. I will show you how to create a modular application.
WEB-INF skeleton without class catalog
ar WEB-INF web.xml ar-servlet.xml web moduleA account form.jsp moduleB order form.jsp
Skeleton Class Directory
classes ar-persistence.xml ar-security.xml ar-service.xml messages.properties br com ar web moduleA AccountController.class moduleB OrderController.class br com ar moduleA model domain Account.class repository moduleA.hbm.xml service br com ar moduleB model domain Order.class repository moduleB.hbm.xml service ...
Note that each package under br.com.ar.web commands corresponds to the WEB-INF / view directory. This is the key needed to run conditional configuration in Spring MVC. How??? rely on ControllerClassNameHandlerMapping
WEB-INF / ar-servlet.xml Pay attention to the basePackage property, which means searching for any @Controller class in the br.com.ar.view package. This property allows you to create modular @ Controller's
<context:component-scan base-package="br.com.ar.web"/> <mvc:annotation-driven/> <bean class="org.springframework.web.servlet.mvc.support.ControllerClassNameHandlerMapping"> <property name="basePackage" value="br.com.ar.web"/> <property name="caseSensitive" value="true"/> <property name="defaultHandler"> <bean class="org.springframework.web.servlet.mvc.UrlFilenameViewController"/> </property> </bean> <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver"> <property name="prefix" value="/view/"/> <property name="suffix" value=".jsp"/> </bean>
Now let's see, for example, AccountController
package br.com.ar.web; @Controller public class AccountController { @Qualifier("categoryRepository") private @Autowired Repository<Category, Category, Integer> categoryRepository; @Qualifier("accountRepository") private @Autowired Repository<Account, Accout, Integer> accountRepository; @RequestMapping(method=RequesMethod.GET) public void form(Model model) { model.add(categoryRepository().getCategoryList()); } @RequestMapping(method=RequesMethod.POST) public void form(Account account, Errors errors) { accountRepository.add(account); } }
How it works?
Suppose you made a request for http://127.0.0.1:8080/ar/ moduleA / account / form .html
Spring will remove the path between the pipeline and the file extension - highlighted above. Let the read extracted path from right to left
- form method name
- unqualified class account name without controller suffix
- moduleA package to be added to the basePackage property
which translates to
br.com.ar.web.moduleA.AccountController.form
Ok But how does Spring know which view to show ??? See here
And about the problems of persistence .
First of all, see here how we implement the repository. Note that each related module request is stored in the corresponding repository package . See Skeleton above. Shown here is ar-persistence.xml Note the mappingLocations and packagesToScan property
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util-2.5.xsd http://www.springframework.org/schema/jee http://www.springframework.org/schema/jee/spring-jee-2.5.xsd"> <jee:jndi-lookup id="dataSource" jndi-name="jdbc/dataSource" resource-ref="true"> <bean id="sessionFactory" class="org.springframework.orm.hibernate3.annotation.AnnotationSessionFactoryBean"> <property name="dataSource" ref="dataSource"/> <property name="mappingLocations"> <util:list> <value>classpath:br/com/ar/model/repository/hql.moduleA.hbm.xml</value> <value>classpath:br/com/ar/model/repository/hql.moduleB.hbm.xml</value> </util:list> </property> <property name="packagesToScan"> <util:list> <value>br.com.ar.moduleA.model.domain</value> <value>br.com.ar.moduleB.model.domain</value> </util:list> </property> <property name="hibernateProperties"> <props> <prop key="hibernate.dialect">org.hibernate.dialect.Oracle9Dialect</prop> <prop key="hibernate.connection.charSet">UTF-8</prop> <prop key="hibernate.show_sql">true</prop> <prop key="hibernate.format_sql">true</prop> <prop key="hibernate.validator.autoregister_listeners">false</prop> </props> </property> </bean> </beans>
Please note that I am using Hibernate. JPA must be configured correctly.
Transaction management and component scanning ar-service.xml Note the Two dots after br.com.ar in the aop: pointcut expression attribute, which means
Any package and subpackage under br.com.ar
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.5.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-2.5.xsd"> <context:component-scan base-package="br.com.ar.model"> <bean id="transactionManager" class="org.springframework.orm.hibernate3.HibernateTransactionManager"> <property name="sessionFactory" ref="sessionFactory"/> </bean> <tx:advice id="repositoryTransactionManagementAdvice" transaction-manager="transactionManager"> <tx:attributes> <tx:method name="add" propagation="REQUIRED"/> <tx:method name="remove" propagation="REQUIRED"/> <tx:method name="update" propagation="REQUIRED"/> <tx:method name="find*" propagation="SUPPORTS"/> </tx:attributes> </tx:advice> <tx:advice id="serviceTransactionManagementAdvice" transaction-manager="transactionManager"> <tx:attributes> <tx:method name="*" propagation="REQUIRED"/> </tx:attributes> </tx:advice> <aop:config> <aop:pointcut id="servicePointcut" expression="execution(* br.com.ar..service.*Service.*(..))"/> <aop:pointcut id="repositoryPointcut" expression="execution(* br.com.ar..repository.*Repository.*(..))"/> <aop:advisor advice-ref="serviceTransactionManagementAdvice" pointcut-ref="servicePointcut"/> <aop:advisor advice-ref="repositoryTransactionManagementAdvice" pointcut-ref="repositoryPointcut"/> </aop:config> <bean class="org.springframework.dao.annotation.PersistenceExceptionTranslationPostProcessor"/> </beans>
Testing
To test the annotated @Controller method, see here how
In addition to the web tier. Note how to configure the JNDI data source in the @Before method
@RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(locations={"classpath:ar-service.xml", "classpath:ar-persistence.xml"}) public class AccountRepositoryIntegrationTest { @Autowired @Qualifier("accountRepository") private Repository<Account, Account, Integer> repository; private Integer id; @Before public void setUp() { SimpleNamingContextBuilder builder = new SimpleNamingContextBuilder(); DataSource ds = new SimpleDriverDataSource(new oracle.jdbc.driver.OracleDriver(), "jdbc:oracle:thin:@127.0.0.1:1521:ar", "#$%#", "#$%#"); builder.bind("/jdbc/dataSource", ds); builder.activate(); } @Test public void assertSavedAccount() { Account account = repository.findById(id); assertNotNull(account); } }
If you need a test suite, do the following
@RunWith(Suite.class) @Suite.SuiteClasses(value={AccountRepositoryIntegrationTest.class}) public void ModuleASuiteTest {}
web.xml is shown as follows
<?xml version="1.0" encoding="UTF-8"?> <web-app version="2.4" xmlns="http://java.sun.com/xml/ns/j2ee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd"> <context-param> <param-name>contextConfigLocation</param-name> <param-value> classpath:ar-persistence.xml classpath:ar-service.xml </param-value> </context-param> <listener> <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class> </listener> <servlet> <servlet-name>ar</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <load-on-startup>1</load-on-startup> </servlet> <servlet-mapping> <servlet-name>ar</servlet-name> <url-pattern>*.html</url-pattern> </servlet-mapping> <session-config> <session-timeout>30</session-timeout> </session-config> <resource-ref> <description>datasource</description> <res-ref-name>jdbc/dataSource</res-ref-name> <res-type>javax.sql.DataSource</res-type> <res-auth>Container</res-auth> </resource-ref> </web-app>
Hope this can be helpful. Upgrade the schema to Spring 3.0. See the Spring Help documentation. Mvc circuit. As far as I know, it is only supported in Spring 3.0. Keep that in mind