我有一个使用 Spring 和 Hibernate 实现的 Web 应用程序。应用程序中的典型 Controller 方法如下所示:
@RequestMapping(method = RequestMethod.POST)
public @ResponseBody
Foo saveFoo(@RequestBody Foo foo, HttpServletRequest request) throws Exception {
// authorize
User user = getAuthorizationService().authorizeUserFromRequest(request);
// service call
return fooService.saveFoo(foo);
}
典型的服务类如下所示:
@Service
@Transactional
public class FooService implements IFooService {
@Autowired
private IFooDao fooDao;
@Override
public Foo saveFoo(Foo foo) {
// ...
}
}
现在,我想创建一个 Log
对象,并在每次保存 Foo
对象时将其插入到数据库中。这些是我的要求:
Log
对象应包含来自授权User
对象的userId
。Log
对象应该包含来自HttpServletRequest
对象的一些属性。- 保存操作和日志创建操作应该是原子的。 IE。如果一个 foo 对象保存在对象中,我们应该在数据库中有一个相应的日志,指示用户和操作的其他属性。
由于事务管理是在服务层处理的,创建日志并将其保存在 Controller 中违反了原子性要求。
我可以将 Log
对象传递给 FooService
但这似乎违反了关注点分离原则,因为日志记录是一个横切关注点。
我可以将事务注释移动到 Controller ,这在我读过的许多地方都没有建议。
我还阅读了有关使用 spring AOP 和拦截器完成工作的信息,对此我经验很少。但他们使用的信息已经存在于服务类中,我无法弄清楚如何将信息从 HttpServletRequest
或授权的 User
传递给该拦截器。
我很感激任何指导或示例代码来满足这种情况下的要求。
最佳答案
要解决您的问题需要执行多个步骤:
- 以非侵入式方式将 Log 对象传递给服务类。
- 创建基于 AOP 的拦截器以开始将日志实例插入数据库。
- 维护 AOP 拦截器(事务拦截器和日志拦截器)的顺序,以便首先调用事务拦截器。这将确保用户插入和日志插入在单个事务中发生。
<强>1。传递日志对象
您可以使用 ThreadLocal 来设置 Log 实例。
public class LogThreadLocal{
private static ThreadLocal<Log> t = new ThreadLocal();
public static void set(Log log){}
public static Log get(){}
public static void clear(){}
}
Controller:saveFoo(){
try{
Log l = //create log from user and http request.
LogThreadLocal.set(l);
fooService.saveFoo(foo);
} finally {
LogThreadLocal.clear();
}
}
<强>2。日志拦截器 查看 spring AOP 的工作原理 ( http://docs.spring.io/spring/docs/current/spring-framework-reference/html/aop-api.html )
a) 创建一个注释(作为切入点),@Log 用于方法级别。此注释将放在要进行日志记录的服务方法上。
@Log
public Foo saveFoo(Foo foo) {}
b) 创建 org.aopalliance.intercept.MethodInterceptor 的实现,LogInteceptor(作为建议)。
public class LogInterceptor implements MethodInterceptor, Ordered{
@Transactional
public final Object invoke(MethodInvocation invocation) throws Throwable {
Object r = invocation.proceed();
Log l = LogThreadLocal.get();
logService.save(l);
return r;
}
}
c) 连接切入点和顾问。
<bean id="logAdvice" class="com.LogInterceptor" />
<bean id="logAnnotation" class="org.springframework.aop.support.annotation.AnnotationMatchingPointcut">
<constructor-arg type="java.lang.Class" value="" />
<constructor-arg type="java.lang.Class" value="com.Log" />
</bean>
<bean id="logAdvisor" class="org.springframework.aop.support.DefaultPointcutAdvisor">
<property name="advice" ref="logAdvice" />
<property name="pointcut" ref="logAnnotation" />
</bean>
<强>3。拦截器的排序(事务和日志)
确保您实现了 org.springframework.core.Ordered 接口(interface)到 LogInterceptor 并从 getOrder() 方法返回 Integer.MAX_VALUE。在您的 spring 配置中,确保您的事务拦截器具有较低的订单值(value)。
因此,首先调用您的事务拦截器并创建一个事务。然后,您的 LogInterceptor 被调用。此拦截器首先进行调用(保存 foo),然后保存日志(从线程本地提取)。
关于java - 使用 Spring 框架以原子方式维护服务层事务和数据库日志记录,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/33720432/