三层Spring应用、测试与AOP综合解析
1. AOP配置与应用
AOP(面向切面编程)在应用中扮演着重要角色,特别是在带有
@Configurable
注解的领域类中。以下是启用切面类的配置文件
sffs-aop-domain.xml
:
<?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"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:p="http://www.springframework.org/schema/p"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-2.5.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-2.5.xsd">
<!--
Enable @Configurable Annotation in conjunction with LTW jvm:
(test) -javaagent:<path>/spring-agent.jar
(tomcat) <tomcat6>/lib/spring-tomcat-weaver.jar-->
<context:load-time-weaver /> <!-- enable the use of META-INF/aop.xml -->
<context:annotation-config />
<context:spring-configured />
<aop:config proxy-target-class="true">
<aop:pointcut id="customerFactoryReadOperation"
expression="execution(* it.freshfruits.domain.factory.CustomerFactoryImpl.get*(..))" />
<aop:pointcut id="customerRepoReadOperation"
expression="execution(* it.freshfruits.application.repository.CustomerRepositoryImpl.select*(..))" />
<aop:pointcut id="customerRepoInsertOperation"
expression="execution(* it.freshfruits.application.repository.CustomerRepositoryImpl.insert*(..))" />
<aop:pointcut id="customerRepoUpdateOperation"
expression="execution(* it.freshfruits.application.repository.CustomerRepositoryImpl.update*(..))" />
<aop:pointcut id="customerRepoDisableOperation"
expression="execution(* it.freshfruits.application.repository.CustomerRepositoryImpl.disable*(..))" />
<aop:pointcut id="fruitReadOperation" expression="execution(*
it.freshfruits.application.repository.FruitTypeRepositoryImpl.get*(..))" />
<aop:pointcut id="fruitInsertOperation" expression="execution(*
it.freshfruits.application.repository.FruitTypeRepositoryImpl.insert*(..))" />
<aop:pointcut id="fruitUpdateOperation" expression="execution(*
it.freshfruits.application.repository.FruitTypeRepositoryImpl.update*(..))" />
<aop:aspect id="customerAspect" ref="customerCacheAspect">
<aop:around pointcut-ref="customerFactoryReadOperation"
method="invoke" />
<aop:around pointcut-ref="customerRepoReadOperation"
method="invoke" />
<aop:before pointcut-ref="customerRepoInsertOperation"
method="flush" />
<aop:before pointcut-ref="customerRepoUpdateOperation"
method="flush" />
<aop:before pointcut-ref="customerRepoDisableOperation"
method="flush" />
</aop:aspect>
<aop:aspect id="fruitAspect" ref="fruitCacheAspect">
<aop:around pointcut-ref="fruitReadOperation" method="invoke"
/>
<aop:before pointcut-ref="fruitInsertOperation"
method="flush" />
<aop:before pointcut-ref="fruitUpdateOperation"
method="flush" />
</aop:aspect>
</aop:config>
<bean id="customerCacheAspect" class="it.freshfruits.aspect.CacheAspect" >
<property name="cache">
<bean id="customerCache" parent="cache">
<property name="cacheName" value="customerCache" />
</bean>
</property>
</bean>
<bean id="fruitCacheAspect" class="it.freshfruits.aspect.CacheAspect" >
<property name="cache">
<bean id="fruitCache" parent="cache">
<property name="cacheName" value="fruitCache" />
</bean>
</property>
</bean>
<bean id="customer" scope="prototype" class="it.freshfruits.domain.entity.CustomerImpl"/>
<bean id="fruitType" scope="prototype" class="it.freshfruits.domain.entity.FruitTypeImpl" />
<bean id="order" scope="prototype" class="it.freshfruits.domain.entity.OrderImpl"/>
<bean id="supplyService" class="it.freshfruits.domain.service.SupplyServiceImpl"/>
</beans>
此配置文件定义了多个切入点和切面,用于管理客户和水果相关的操作。同时,还配置了缓存切面,用于处理缓存的读写和刷新操作。
为了配置AspectJ,需要在
META-INF
目录下放置
aop.xml
文件:
<!DOCTYPE aspectj PUBLIC "-//AspectJ//DTD//EN"
"http://www.eclipse.org/aspectj/dtd/aspectj.dtd">
<aspectj>
<weaver options="-showWeaveInfo
-XmessageHandlerClass:org.springframework.aop.aspectj.AspectJWeaverMessageHandler">
<!-- only weave classes in our application-specific packages -->
<include within="it.freshfruits.domain.entity.*"/>
<include within="it.freshfruits.domain.factory.*"/>
<include within="it.freshfruits.domain.service.*"/>
<include within="it.freshfruits.domain.vo.*"/>
<include within="it.freshfruits.application.repository.*"/>
<include within="it.freshfruits.ui.*"/>
<exclude within="it.freshfruits.aspect.*"/>
</weaver>
<!-- Be careful, those Aspects lose injected
properties (AspectJ ignore Spring at LWT),
otherwise consider aspect xml definition -->
<aspects>
<aspect name="it.freshfruits.aspect.ConcurrentAspect" />
<aspect name="it.freshfruits.aspect.LogManagedAspect" />
<aspect name="it.freshfruits.aspect.TimeExecutionManagedAspect" />
</aspects>
</aspectj>
这个文件指定了需要织入的类和排除的类,以及要使用的切面。
2. 缓存管理切面
缓存管理切面
CacheAspect
用于处理缓存的读写和刷新操作:
package it.freshfruits.aspect;
import it.freshfruits.util.Constants;
import net.sf.ehcache.Cache;
import net.sf.ehcache.Element;
import org.apache.log4j.Logger;
import org.aspectj.lang.ProceedingJoinPoint;
public class CacheAspect {
public void flush() {
cache.flush();
}
public Object invoke(ProceedingJoinPoint pjp) throws Throwable {
Object result;
String cacheKey = getCacheKey(pjp);
Element element = (Element) cache.get(cacheKey);
logger.info(new StringBuilder("CacheAspect invoke:").append("\n get:")
.append(cacheKey).append(" value:").append(element).toString());
if (element == null) {
result = pjp.proceed();
element = new Element(cacheKey, result);
cache.put(element);
logger.info(new StringBuilder("\n put:").append(cacheKey).append(
" value:").append(result).toString());
}
return element.getValue();
}
private String getCacheKey(ProceedingJoinPoint pjp) {
String targetName = pjp.getTarget().getClass().getSimpleName();
String methodName = pjp.getSignature().getName();
Object[] arguments = pjp.getArgs();
StringBuilder sb = new StringBuilder();
sb.append(targetName).append(".").append(methodName);
if ((arguments != null) && (arguments.length != 0)) {
for (int i = 0; i < arguments.length; i++) {
sb.append(".").append(arguments[i]);
}
}
return sb.toString();
}
public void setCache(Cache cache) {
this.cache = cache;
}
private Cache cache;
private Logger logger = Logger.getLogger(Constants.LOG_NAME);
}
该切面的主要逻辑如下:
1.
flush
方法
:用于刷新缓存。
2.
invoke
方法
:首先根据连接点生成缓存键,然后检查缓存中是否存在该键对应的值。如果不存在,则执行目标方法,并将结果存入缓存;如果存在,则直接返回缓存中的值。
3.
getCacheKey
方法
:根据连接点的目标类名、方法名和参数生成缓存键。
3. 并发管理切面
并发管理切面
ConcurrentAspect
用于处理并发操作的读写锁:
package it.freshfruits.aspect;
import it.freshfruits.util.Constants;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import org.apache.log4j.Logger;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.core.annotation.Order;
@SuppressWarnings("unused")
@Aspect()
@Order(0)
public class ConcurrentAspect {
@Pointcut("execution (* isAvailable(..))")
private void isAvailable() {}
@Pointcut("execution (* retainItem(..))")
private void retainItem() {}
@Pointcut("execution (* release(..))")
private void release() {}
@Pointcut("release() || retainItem()")
private void releaseOrRetain() {}
@Before("isAvailable()")
public void setReadLock() {
log.info("setReadLock");
rLock.lock();
}
@After("isAvailable()")
public void releaseReadLock() {
rLock.unlock();
log.info("releaseReadLock");
}
@Before("releaseOrRetain()")
public void setWriteLock() {
log.info("setWriteLock");
wLock.lock();
}
@After("releaseOrRetain()")
public void releaseWriteLock() {
wLock.unlock();
log.info("releaseWriteLock");
}
private final ReadWriteLock lock = new ReentrantReadWriteLock();
private final Lock rLock = lock.readLock();
private final Lock wLock = lock.writeLock();
private Logger log = Logger.getLogger(Constants.LOG_NAME);;
}
该切面的主要逻辑如下:
1.
切入点定义
:定义了几个切入点,用于匹配不同的方法。
2.
读写锁操作
:在
isAvailable
方法执行前获取读锁,执行后释放读锁;在
release
或
retainItem
方法执行前获取写锁,执行后释放写锁。
4. 方法执行时间管理切面
方法执行时间管理切面
TimeExecutionManagedAspect
用于测量方法的平均执行时间:
package it.freshfruits.aspect;
import it.freshfruits.util.Constants;
import org.apache.log4j.Logger;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.core.annotation.Order;
import org.springframework.jmx.export.annotation.ManagedAttribute;
import org.springframework.jmx.export.annotation.ManagedOperation;
import org.springframework.jmx.export.annotation.ManagedResource;
import org.springframework.util.StopWatch;
@ManagedResource("freshfruitstore:type=TimeExecutionManagedAspect")
@Aspect() @Order(2)
public class TimeExecutionManagedAspect {
@ManagedAttribute
public boolean isLogEnabled() {
return isLogEnabled;
}
@ManagedAttribute
public void setLogEnabled(boolean isLogEnabled) {
this.isLogEnabled = isLogEnabled;
}
@ManagedAttribute
public boolean isTimeExecutionEnabled() {
return isTimeExecutionEnabled;
}
@ManagedAttribute
public void setTimeExecutionEnabled(boolean isTimeExecutionEnabled) {
this.isTimeExecutionEnabled = isTimeExecutionEnabled;
}
@ManagedAttribute
public long getAverageCallTime() {
return (this.callCount > 0 ? this.accumulatedCallTime / this.callCount
: 0);
}
@ManagedOperation
public void resetCounters() {
this.callCount = 0;
this.accumulatedCallTime = 0;
}
@Around("within(it.freshfruits.domain.entity.CustomerImpl )")
public Object invoke(ProceedingJoinPoint joinPoint) throws Throwable {
if (this.isTimeExecutionEnabled) {
StopWatch sw = new StopWatch(joinPoint.toString());
sw.start("invoke");
try {
return joinPoint.proceed();
} finally {
sw.stop();
synchronized (this) {
this.accumulatedCallTime += sw.getTotalTimeMillis();
}
if (isLogEnabled) {
logger.info(sw.prettyPrint());
}
}
} else {
return joinPoint.proceed();
}
}
private boolean isTimeExecutionEnabled = true;
private boolean isLogEnabled = true;
private long accumulatedCallTime = 0;
private int callCount = 0;
private Logger logger = Logger.getLogger(Constants.LOG_NAME);
}
该切面的主要逻辑如下:
1.
管理属性和操作
:通过
@ManagedAttribute
和
@ManagedOperation
注解将一些属性和方法暴露给JMX。
2.
执行时间测量
:在
CustomerImpl
类的方法执行前后使用
StopWatch
测量执行时间,并累加总时间和调用次数。
为了将该切面的方法暴露给JMX MBeanServer,需要进行如下配置:
<!-- JMX -->
<bean id="mbeanServer" class="org.springframework.jmx.support.MBeanServerFactoryBean"
p:locateExistingServerIfPossible="true"/>
<bean id="exporter" class="org.springframework.jmx.export.MBeanExporter"
p:assembler-ref="assembler" p:namingStrategy-ref="namingStrategy" p:autodetect="true"/>
<bean id="jmxAttributeSource" class="org.springframework.jmx.export.annotation.AnnotationJmxAttributeSource"/>
<bean id="assembler" class="org.springframework.jmx.export.assembler.MetadataMBeanInfoAssembler"
p:attributeSource-ref="jmxAttributeSource"/>
<bean id="namingStrategy" class="org.springframework.jmx.export.naming.MetadataNamingStrategy"
p:attributeSource-ref="jmxAttributeSource"/>
5. 事务管理配置
所有数据库操作都需要以事务方式进行,以下是Spring的事务管理配置:
<?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:p="http://www.springframework.org/schema/p"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx"
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/aop
http://www.springframework.org/schema/aop/spring-aop-2.5.xsd">
<!-- D A T A S O U R C E -->
<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close"
p:url="${jdbc.url}" p:username="${jdbc.username}" p:password="${jdbc.password}"
p:driverClassName="${jdbc.production.driver}" p:maxIdle="3" p:maxWait="50" p:removeAbandoned="true"
p:removeAbandonedTimeout="550" p:logAbandoned="true" p:maxActive="20"/>
<!-- iBATIS -->
<bean id="sqlMapClient" class="org.springframework.orm.ibatis.SqlMapClientFactoryBean"
p:dataSource-ref="dataSource"
p:configLocation="/sffs-sqlMapConfig.xml"/>
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"
p:dataSource-ref="dataSource"/>
<tx:advice id="txAdvice" transaction-manager="transactionManager">
<tx:attributes>
<tx:method name="save*" propagation="REQUIRED" rollback-for="Exception"/>
<tx:method name="insertOrder" propagation="REQUIRED" rollback-for="OrderItemsException"/>
<tx:method name="insert*" propagation="REQUIRED" rollback-for="Exception"/>
<tx:method name="update*" propagation="REQUIRED" rollback-for="Exception"/>
<tx:method name="delete*" propagation="REQUIRED" rollback-for="Exception"/>
<tx:method name="disable*" propagation="REQUIRED" rollback-for="Exception"/>
<tx:method name="*" read-only="true"/>
</tx:attributes>
</tx:advice>
<aop:config>
<aop:pointcut id="repoOperations" expression="execution(* it.freshfruits.application.repository.*.*(..))" />
<aop:advisor advice-ref="txAdvice" pointcut-ref="repoOperations"/>
</aop:config>
</beans>
该配置的主要逻辑如下:
1.
数据源配置
:配置了数据源
dataSource
,用于连接数据库。
2.
事务管理器配置
:配置了事务管理器
transactionManager
,用于管理事务。
3.
事务通知配置
:定义了事务通知
txAdvice
,指定了不同方法的事务传播行为和回滚规则。
4.
切入点和通知器配置
:定义了切入点
repoOperations
,并将事务通知应用到该切入点。
6. 安全配置
为了实现用户信息的透明使用,进行了如下安全配置:
首先,定义了自定义用户类
FreshFruitUser
:
package it.freshfruits.security;
import org.springframework.security.GrantedAuthority;
import org.springframework.security.userdetails.User;
public class FreshFruitUser extends User {
public FreshFruitUser(String username, String password, boolean isEnabled,
GrantedAuthority[] authorities, Object user) {
super(username, password, isEnabled, true, true, true, authorities);
this.setUserInfo(user);
}
public FreshFruitUser(String username, String password, boolean isEnabled,
GrantedAuthority[] arrayAuths) {
super(username, password, isEnabled, true, true, true, arrayAuths);
}
public Object getUserInfo() {
return userInfo;
}
public void setUserInfo(Object userInfo) {
this.userInfo = userInfo;
}
private Object userInfo;
private static final long serialVersionUID = -343812156239227785L;
}
该类继承自Spring Security的
User
类,并添加了用户信息字段。
然后,定义了用户数据加载类
AuthenticationJdbcDaoImpl
:
package it.freshfruits.security;
import it.freshfruits.application.repository.CustomerRepository;
import it.freshfruits.util.Constants;
import java.util.HashMap;
import java.util.Map;
import org.springframework.dao.DataAccessException;
import org.springframework.security.userdetails.UserDetails;
import org.springframework.security.userdetails.UsernameNotFoundException;
import org.springframework.security.userdetails.jdbc.JdbcDaoImpl;
public class AuthenticationJdbcDaoImpl extends JdbcDaoImpl {
public UserDetails loadUserByUsername(String username) {
try {
UserDetails user = super.loadUserByUsername(username);
Map userInfo = new HashMap();
userInfo.put(Constants.ID_CUSTOMER, repo.getIdCustomer(username));
return new FreshFruitUser(user.getUsername(), user.getPassword(),
user.isEnabled(), user.getAuthorities(), userInfo);
} catch (UsernameNotFoundException ex1) {
ex1.printStackTrace();
throw ex1;
} catch (DataAccessException ex2) {
ex2.printStackTrace();
throw ex2;
}
}
public void setRepo(CustomerRepository repo) {
this.repo = repo;
}
private CustomerRepository repo;
}
该类用于在用户认证时加载用户数据和ID。
最后,定义了安全工具类
SecurityUtils
:
package it.freshfruits.security;
import it.freshfruits.util.Constants;
import java.util.Map;
import org.springframework.security.context.SecurityContextHolder;
public class SecurityUtils {
public static String getIdCustomer() {
FreshFruitUser user = (FreshFruitUser) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
Map userInfo = (Map) user.getUserInfo();
return userInfo.get(Constants.ID_CUSTOMER).toString();
}
public static String getCustomerName() {
return SecurityContextHolder.getContext().getAuthentication().getName();
}
}
该类用于从当前执行线程中获取用户的ID和姓名。
以下是安全配置文件
sffs-security.xml
:
<?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:p="http://www.springframework.org/schema/p"
xmlns:sec="http://www.springframework.org/schema/security"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
http://www.springframework.org/schema/security
http://www.springframework.org/schema/security/spring-security-2.0.4.xsd">
<sec:http>
<sec:intercept-url pattern="/log*.jsp" filters="none" />
<sec:intercept-url pattern="/*.page" access="ROLE_ADMIN" />
<sec:form-login login-page="/login.jsp"
default-target-url="/" login-processing-url="/j_security_check"
authentication-failure-url="/loginError.jsp" />
<sec:logout logout-url="/logout.jsp" invalidate-session="true"
logout-success-url="/login.jsp" />
<sec:remember-me />
<sec:intercept-url pattern="*.htm"
access="ROLE_USER,ROLE_ANONYMOUS" />
<sec:intercept-url pattern="*.page" access="ROLE_USER,ROLE_ADMIN" />
<sec:intercept-url pattern="*.edit" access="ROLE_USER,ROLE_ADMIN" />
<sec:intercept-url pattern="*.admin" access="ROLE_ADMIN" />
</sec:http>
<sec:authentication-provider
user-service-ref="sffsUserDetailservice">
<sec:password-encoder hash="sha" />
</sec:authentication-provider>
<bean id="accessManager" class="org.springframework.security.vote.AffirmativeBased">
<property name="decisionVoters">
<list>
<bean class="org.springframework.security.vote.RoleVoter" />
<bean class="org.springframework.security.vote.AuthenticatedVoter" />
</list>
</property>
</bean>
<bean id="sffsUserDetailservice" class="it.freshfruits.security.AuthenticationJdbcDaoImpl">
<property name="rolePrefix" value="ROLE_" />
<property name="dataSource" ref="dataSource" />
<property name="usersByUsernameQuery"
value="SELECT id AS username, password, enabled FROM riot_users WHERE id = ? " />
<property name="authoritiesByUsernameQuery"
value="SELECT id AS username, role FROM riot_users WHERE id = ? " />
</bean>
<bean id="accessDecisionManager" class="org.springframework.security.vote.AffirmativeBased">
<property name="decisionVoters">
<list>
<bean class="org.springframework.security.vote.RoleVoter" />
<bean class="org.springframework.security.vote.AuthenticatedVoter" />
</list>
</property>
</bean>
<sec:global-method-security
access-decision-manager-ref="accessDecisionManager">
<sec:protect-pointcut
expression="execution(* it.freshfruits.domain.entity.*.*(..))"
access="ROLE_USER,ROLE_ADMIN" />
</sec:global-method-security>
</beans>
该配置文件的主要逻辑如下:
1.
HTTP安全配置
:配置了不同URL的访问权限,以及登录、注销和记住我等功能。
2.
认证提供者配置
:配置了认证提供者,指定了用户服务
AuthenticationJdbcDaoImpl
和密码编码器。
3.
访问决策管理器配置
:配置了访问决策管理器,用于决定用户是否有权限访问资源。
4.
全局方法安全配置
:配置了全局方法安全,指定了需要保护的方法和访问权限。
7. 其他配置文件
除了上述配置文件,还有
sffs-servlet.xml
和
sffs-application.xml
等配置文件:
sffs-servlet.xml
:
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:p="http://www.springframework.org/schema/p"
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/context
http://www.springframework.org/schema/context/spring-context-2.5.xsd">
<context:component-scan base-package="it.freshfruits.ui"/>
<bean name="urlMapping" class="org.springframework.web.servlet.mvc.annotation.DefaultAnnotationHandlerMapping">
<property name="interceptors">
<list>
<ref bean="customerInterceptor"/>
</list>
</property>
</bean>
<bean name="customerInterceptor" class=" it.freshfruits.ui.CustomerInterceptor"/>
<!-- M E S S A G E S -->
<bean id="messageSource" class="org.springframework.context.support.ResourceBundleMessageSource"
p:basename=" it.freshfruits.ui.message"/>
<!-- V I E W R E S O L V E R -->
<bean name="viewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver"
p:viewClass="org.springframework.web.servlet.view.JstlView" p:prefix="WEB-INF/jsp/" p:suffix=".jsp"/>
</beans>
该文件主要配置了组件扫描、URL映射、拦截器、消息源和视图解析器。
sffs-application.xml
:
<?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:p="http://www.springframework.org/schema/p"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx"
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/aop
http://www.springframework.org/schema/aop/spring-aop-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="it.freshfruits"/>
<context:property-placeholder location="/config.properties" />
<bean id="cache" abstract="true" class="org.springframework.cache.ehcache.EhCacheFactoryBean"
p:cacheManager-ref="cacheManager" />
<bean id="cacheManager" class="org.springframework.cache.ehcache.EhCacheManagerFactoryBean"
p:configLocation="classpath:ehcache.xml" />
<bean id="messageSource" class="org.springframework.context.support.ResourceBundleMessageSource"
p:basename="it.freshfruits.messages.msg"/>
<bean id="viewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver"
p:viewClass="org.springframework.web.servlet.view.JstlView" p:prefix="WEB-INF/jsp/" p:suffix=".jsp"/>
<bean id="exceptionResolver" class="org.springframework.web.servlet.handler.SimpleMappingExceptionResolver">
<property name="exceptionMappings">
<props>
<prop key="java.lang.Exception">errors/exception</prop>
</props>
</property>
</bean>
<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close"
p:url="${jdbc.url}" p:username="${jdbc.username}" p:password="${jdbc.password}"
p:driverClassName="${jdbc.production.driver}" p:maxIdle="3" p:maxWait="50" p:removeAbandoned="true"
p:removeAbandonedTimeout="550" p:logAbandoned="true" p:maxActive="20"/>
<bean id="sqlMapClient" class="org.springframework.orm.ibatis.SqlMapClientFactoryBean"
p:dataSource-ref="dataSource" p:configLocation="/sffs-sqlMapConfig.xml"/>
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"
p:dataSource-ref="dataSource"/>
<tx:advice id="txAdvice" transaction-manager="transactionManager">
<tx:attributes>
<tx:method name="save*" propagation="REQUIRED" rollback-for="Exception"/>
<tx:method name="insertOrder" propagation="REQUIRED" rollback-for="OrderItemsException"/>
<tx:method name="insert*" propagation="REQUIRED" rollback-for="Exception"/>
<tx:method name="update*" propagation="REQUIRED" rollback-for="Exception"/>
<tx:method name="delete*" propagation="REQUIRED" rollback-for="Exception"/>
<tx:method name="disable*" propagation="REQUIRED" rollback-for="Exception"/>
<tx:method name="*" read-only="true"/>
</tx:attributes>
</tx:advice>
<aop:config>
<aop:pointcut id="repoOperations" expression="execution(* it.freshfruits.application.repository.*.*(..))" />
<aop:advisor advice-ref="txAdvice" pointcut-ref="repoOperations"/>
</aop:config>
<!-- JMX -->
<bean id="mbeanServer" class="org.springframework.jmx.support.MBeanServerFactoryBean"
p:locateExistingServerIfPossible="true"/>
<bean id="exporter" class="org.springframework.jmx.export.MBeanExporter"
p:assembler-ref="assembler" p:namingStrategy-ref="namingStrategy" p:autodetect="true"/>
<bean id="jmxAttributeSource" class="org.springframework.jmx.export.annotation.AnnotationJmxAttributeSource"/>
<bean id="assembler" class="org.springframework.jmx.export.assembler.MetadataMBeanInfoAssembler"
p:attributeSource-ref="jmxAttributeSource"/>
<bean id="namingStrategy" class="org.springframework.jmx.export.naming.MetadataNamingStrategy"
p:attributeSource-ref="jmxAttributeSource"/>
</beans>
该文件主要配置了组件扫描、属性占位符、缓存、消息源、视图解析器、异常解析器、数据源、事务管理、JMX等。
最后,
config.properties
文件用于配置数据库连接信息:
jndi.datasource=java:comp/env/jdbc/sffs
jdbc.url=jdbc:postgresql://localhost:5432/sffs
jdbc.username=sffs
jdbc.password=sffs
jdbc.production.driver=org.postgresql.Driver
jdbc.debug.driver=com.p6spy.engine.spy.P6SpyDriver
jdbc.driver=org.postgresql.Driver
通过上述配置和代码,实现了一个完整的三层Spring应用,包括AOP、缓存、并发管理、事务管理和安全管理等功能。在实际开发中,可以根据具体需求对这些配置和代码进行调整和扩展。
三层Spring应用、测试与AOP综合解析
8. 配置文件总结
为了更清晰地了解各个配置文件的作用,我们可以将其整理成如下表格:
| 配置文件名称 | 主要作用 |
| ---- | ---- |
|
sffs-aop-domain.xml
| 定义AOP相关的切入点、切面和缓存切面,同时配置了一些实体类和服务类 |
|
aop.xml
| 配置AspectJ,指定需要织入的类和排除的类,以及要使用的切面 |
|
sffs-servlet.xml
| 配置组件扫描、URL映射、拦截器、消息源和视图解析器 |
|
sffs-application.xml
| 配置组件扫描、属性占位符、缓存、消息源、视图解析器、异常解析器、数据源、事务管理、JMX等 |
|
sffs-security.xml
| 配置安全相关的内容,包括HTTP安全、认证提供者、访问决策管理器和全局方法安全 |
|
config.properties
| 配置数据库连接信息 |
9. 整体架构流程
下面是整个三层Spring应用的架构流程,使用mermaid格式的流程图来展示:
graph LR
classDef startend fill:#F5EBFF,stroke:#BE8FED,stroke-width:2px;
classDef process fill:#E5F6FF,stroke:#73A6FF,stroke-width:2px;
classDef decision fill:#FFF6CC,stroke:#FFBC52,stroke-width:2px;
A([客户端请求]):::startend --> B(Web层: sffs-servlet.xml):::process
B --> C(Service层: sffs-application.xml):::process
C --> D(Repository层: 数据库操作):::process
D --> E{操作结果}:::decision
E -->|成功| F(返回响应给客户端):::process
E -->|失败| G(异常处理: sffs-application.xml):::process
G --> F
H(缓存管理: CacheAspect):::process --> C
I(并发管理: ConcurrentAspect):::process --> C
J(时间执行管理: TimeExecutionManagedAspect):::process --> C
K(安全管理: sffs-security.xml):::process --> B
K --> C
K --> D
从流程图可以看出,客户端的请求首先经过Web层,然后传递到Service层进行业务逻辑处理,Service层可能会调用Repository层进行数据库操作。在整个过程中,缓存管理、并发管理和时间执行管理会对Service层的方法进行增强,而安全管理则贯穿整个应用,确保只有授权用户可以访问相应的资源。
10. 操作步骤总结
如果要搭建一个类似的三层Spring应用,可以按照以下步骤进行:
1.
配置数据源
:在
config.properties
文件中配置数据库连接信息,在
sffs-application.xml
中配置数据源和事务管理器。
- 在
config.properties
中设置数据库的URL、用户名、密码和驱动等信息。
- 在
sffs-application.xml
中使用
BasicDataSource
配置数据源,并使用
DataSourceTransactionManager
配置事务管理器。
2.
配置AOP
:在
sffs-aop-domain.xml
中定义切入点和切面,在
aop.xml
中配置AspectJ。
- 在
sffs-aop-domain.xml
中使用
<aop:pointcut>
定义切入点,使用
<aop:aspect>
定义切面。
- 在
aop.xml
中指定需要织入的类和排除的类,以及要使用的切面。
3.
配置缓存管理
:在
sffs-aop-domain.xml
中配置缓存切面
CacheAspect
,并在
CacheAspect
类中实现缓存的读写和刷新操作。
- 在
sffs-aop-domain.xml
中配置
customerCacheAspect
和
fruitCacheAspect
。
- 在
CacheAspect
类中实现
flush
、
invoke
和
getCacheKey
方法。
4.
配置并发管理
:在
ConcurrentAspect
类中实现并发操作的读写锁管理。
- 使用
@Pointcut
定义切入点,使用
@Before
和
@After
注解在方法执行前后获取和释放读写锁。
5.
配置时间执行管理
:在
TimeExecutionManagedAspect
类中实现方法执行时间的测量,并在
sffs-application.xml
中配置JMX相关内容。
- 在
TimeExecutionManagedAspect
类中使用
StopWatch
测量方法执行时间。
- 在
sffs-application.xml
中配置
MBeanServerFactoryBean
和
MBeanExporter
将该切面的方法暴露给JMX。
6.
配置事务管理
:在
sffs-application.xml
中配置事务通知和切入点,确保数据库操作在事务中进行。
- 使用
<tx:advice>
定义事务通知,使用
<aop:pointcut>
和
<aop:advisor>
将事务通知应用到指定的切入点。
7.
配置安全管理
:定义自定义用户类、用户数据加载类和安全工具类,在
sffs-security.xml
中配置安全相关内容。
- 定义
FreshFruitUser
、
AuthenticationJdbcDaoImpl
和
SecurityUtils
类。
- 在
sffs-security.xml
中配置HTTP安全、认证提供者、访问决策管理器和全局方法安全。
8.
配置Web层
:在
sffs-servlet.xml
中配置组件扫描、URL映射、拦截器、消息源和视图解析器。
- 使用
<context:component-scan>
进行组件扫描,使用
DefaultAnnotationHandlerMapping
配置URL映射,使用
ResourceBundleMessageSource
配置消息源,使用
InternalResourceViewResolver
配置视图解析器。
11. 代码复用与扩展
在实际开发中,我们可以根据具体需求对上述配置和代码进行复用和扩展:
-
代码复用
:可以将一些通用的切面类(如
CacheAspect
、
ConcurrentAspect
和
TimeExecutionManagedAspect
)和工具类(如
SecurityUtils
)提取出来,作为独立的模块,在其他项目中复用。
-
代码扩展
:如果需要添加新的功能,可以按照以下步骤进行:
1.
定义新的切入点和切面
:在
sffs-aop-domain.xml
中定义新的切入点,在新的切面类中实现相应的逻辑。
2.
配置新的组件
:在
sffs-servlet.xml
或
sffs-application.xml
中配置新的组件,如过滤器、拦截器等。
3.
修改安全配置
:如果新功能需要进行安全控制,可以在
sffs-security.xml
中添加相应的配置。
12. 注意事项
在使用上述配置和代码时,需要注意以下几点:
-
AspectJ配置
:在使用
@Configurable
注解时,需要确保AspectJ的织入配置正确,否则可能会导致注入失败。
-
缓存管理
:需要根据实际情况合理设置缓存的过期时间和大小,避免缓存数据过期或占用过多内存。
-
并发管理
:在使用读写锁时,需要确保锁的获取和释放顺序正确,避免死锁的发生。
-
事务管理
:需要根据业务需求合理设置事务的传播行为和回滚规则,确保数据的一致性。
-
安全管理
:需要定期更新密码编码器和认证机制,确保系统的安全性。
通过以上的配置和代码,我们可以构建一个功能强大、安全可靠的三层Spring应用。在实际开发中,需要根据具体需求对这些配置和代码进行灵活调整和扩展,以满足不同的业务场景。
超级会员免费看
964

被折叠的 条评论
为什么被折叠?



