承接上篇 Maven 環境搭建,本文聚焦 Web 環境下 Spring 容器的啟動方式、ApplicationContext 獲取策略,以及基於註解與 AOP 的聲明式事務管理配置,構建企業級應用的堅實基礎。
使用 org.springframework.web.context.ContextLoaderListener,需引入 spring-web 依賴:
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
<version>${spring.version}</version>
</dependency>
web.xml 基礎配置:
<listener>
<description>Spring 容器啟動監聽器</description>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
預設掃描 /WEB-INF/applicationContext.xml,多配置檔指定方式:
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath*:applicationContext.xml</param-value>
</context-param>
常見啟動失敗排查:
ContextLoaderListener 類 → Maven 依賴未發布到伺服器(專案屬性 → Deployment Assembly 添加 Maven Dependencies)使用 ContextLoaderServlet 需設定 <load-on-startup>1</load-on-startup>,但 Spring 3.0 已移除,建議統一使用監聽器。
如 Spring MVC 的 DispatcherServlet 自帶 contextConfigLocation 參數,屬於特定框架用法,不做通用介紹。
適用於 Servlet、Filter 等非 Spring 管理物件:
@Override
public void init() throws ServletException {
WebApplicationContext ctx = WebApplicationContextUtils.getWebApplicationContext(getServletContext());
bookTypeService = ctx.getBean(BookTypeService.class);
}
注意:重寫帶參 init(ServletConfig) 時必須呼叫 super.init(config),否則無法獲取 ServletContext。
實現 ApplicationContextAware 並交由 Spring 管理(@Component 或 XML <bean>),容器啟動時自動回調 setApplicationContext():
@Component
public class CtxUtil implements ApplicationContextAware {
public static ApplicationContext springCtx;
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
springCtx = applicationContext;
}
public static Object getBean(String name) { return springCtx.getBean(name); }
public static <T> T getBean(Class<T> clazz) { return springCtx.getBean(clazz); }
}
後續任何處直接呼叫 CtxUtil.getBean(BookTypeService.class) 即可,無需傳遞 ServletContext。
需引入 spring-tx,並在 applicationContext.xml 引入 tx 命名空間:
xmlns:tx="https://www.springframework.org/schema/tx"
xsi:schemaLocation="...
https://www.springframework.org/schema/tx https://www.springframework.org/schema/tx/spring-tx-4.3.xsd"
<!-- 數據源事務管理器 -->
<bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="jdbcDataSource"/>
</bean>
<!-- 開啟 @Transactional 註解支援 -->
<tx:annotation-driven transaction-manager="txManager"/>
@Service
public class BookTypeService {
@Resource BookTypeDAO bookTypeDAO;
@Transactional
public int addDouble(BookType entity1, BookType entity2) {
int rows = 0;
rows += bookTypeDAO.add(entity1);
rows += bookTypeDAO.add(entity2); // 若此處拋異常,前一條自動回滾
return rows;
}
}
驗證:entity2 缺少必填字段導致異常時,資料庫兩條記錄均不會插入,保證原子性。
適合統一按方法名規則套用事務:
<tx:advice id="transactionAdvice" transaction-manager="txManager">
<tx:attributes>
<tx:method name="add*" propagation="REQUIRED"/>
<tx:method name="save*" propagation="REQUIRED"/>
<tx:method name="update*" propagation="REQUIRED"/>
<tx:method name="delete*" propagation="REQUIRED"/>
<tx:method name="get*" propagation="SUPPORTS"/>
<tx:method name="find*" propagation="SUPPORTS"/>
<tx:method name="*" propagation="SUPPORTS"/>
</tx:attributes>
</tx:advice>
<aop:config>
<aop:pointcut id="transactionPointcut" expression="execution(* com.service..*Impl.*(..))"/>
<aop:advisor pointcut-ref="transactionPointcut" advice-ref="transactionAdvice"/>
</aop:config>
對比:註解式靈活精準,AOP 式統一規約;查詢方法建議 SUPPORTS(不需要事務),寫操作用 REQUIRED。
<beans ... xmlns:tx="...">
<context:property-placeholder location="classpath:db.properties"/>
<bean id="jdbcDataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource" destroy-method="close">
<property name="driverClass" value="${jdbc.driver}"/>
<property name="jdbcUrl" value="${jdbc.url}"/>
<property name="user" value="${jdbc.uid}"/>
<property name="password" value="${jdbc.pwd}"/>
<property name="acquireIncrement" value="5"/>
<property name="initialPoolSize" value="10"/>
<property name="minPoolSize" value="5"/>
<property name="maxPoolSize" value="20"/>
</bean>
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="jdbcDataSource"/>
<property name="typeAliasesPackage" value="com.example.entities"/>
<property name="mapperLocations" value="classpath:com/example/mapping/*Mapper.xml"/>
</bean>
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"/>
<property name="basePackage" value="com.example.mapping"/>
</bean>
<bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="jdbcDataSource"/>
</bean>
<tx:annotation-driven transaction-manager="txManager"/>
<context:component-scan base-package="com.example">
<context:exclude-filter type="aspectj" expression="com.example.dao.*"/>
</context:component-scan>
<aop:aspectj-autoproxy proxy-target-class="true"/>
</beans>
ApplicationContextAware 提供靜態獲取 Bean 能力,適合非 Spring 管理物件DataSourceTransactionManager + <tx:annotation-driven/> + @Transactional