我有一个使用Spring和Hibernate实现的Web应用程序。应用程序中的典型控制器方法如下所示:
SELECT substring(firstname,1) AS first_letter
AND substring(lastname,1) AS first_letter
FROM kids
ORDER BY firstname
典型的服务类如下所示:
@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
对象中的userId
。 User
对象应包含Log
对象的一些属性。由于事务管理是在服务层中处理的,因此创建日志并将其保存在控制器中会违反原子性要求。
我可以将HttpServletRequest
对象传递给Log
,但这似乎违反了关注点分离原则,因为记录是一个跨领域的问题。
我可以将事务注释移动到控制器上,这在我读过的很多地方都没有建议。
我还读到了使用Spring AOP和拦截器来完成这项工作,我很少有经验。但是他们正在使用服务类中已经存在的信息,我无法弄清楚如何将信息从FooService
或授权HttpServletRequest
传递给那些拦截器。
我很欣赏任何方向或示例代码,以满足此方案中的要求。
答案 0 :(得分:2)
要解决您的问题,需要执行多个步骤:
<强> 1。传递日志对象
您可以使用ThreadLocal设置Log实例。
var bluetoothle = {isInitialized:function(){setTimeout(function(){func({isInitialized:false});},20);}};
// to execute:
bluetoothle.isInitialized(/*callbackfunction*/);
<强> 2。日志拦截器 了解spring AOP的工作原理(http://docs.spring.io/spring/docs/current/spring-framework-reference/html/aop-api.html)
a)创建注释(充当切入点),@ 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();
}
}
b)创建org.aopalliance.intercept.MethodInterceptor的实现,LogInteceptor(充当建议)。
@Log
public Foo saveFoo(Foo foo) {}
c)切入点切口&amp;顾问。
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;
}
}
第3。拦截器的排序(交易和日志)
确保为LogInterceptor实现org.springframework.core.Ordered接口,并从getOrder()方法返回Integer.MAX_VALUE。在弹簧配置中,确保您的事务拦截器具有较低的订单值。
因此,首先调用您的事务拦截器并创建一个事务。然后,调用您的LogInterceptor。此拦截器首先进行调用(保存foo),然后保存日志(从本地线程中提取)。
答案 1 :(得分:2)
另一个基于Spring AOP但使用java配置的例子,我讨厌XML :)基本上这个想法与mohit
几乎相同但没有ThreadLocals,Interceptor Orders和XML configs :)
所以你需要:
@Loggable
注释将方法标记为创建日志的一次。 TransactionTemplate
我们将用它来以编程方式控制交易。Aspect
,它将把所有东西都放在原处。首先让我们创建注释
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Loggable {}
如果您缺少TransactionTemplate
配置或EnableAspectJAutoProxy
只需将以下内容添加到Java配置中。
@EnableAspectJAutoProxy
@Configuration
public class ApplicationContext {
.....
@Bean
TransactionTemplate transactionTemplate(PlatformTransactionManager transactionManager){
TransactionTemplate template = new TransactionTemplate();
template.setTransactionManager(transactionManager);
return template;
}
}
接下来我们需要一个Aspect
来做所有的魔术:)
@Component
@Aspect
public class LogAspect {
@Autowired
private HttpServletRequest request;
@Autowired
private TransactionTemplate template;
@Autowired
private LogService logService;
@Around("execution(* *(..)) && @annotation(loggable)")
public void logIt(ProceedingJoinPoint pjp, Loggable loggable) {
template.execute(s->{
try{
Foo foo = (Foo) pjp.proceed();
Log log = new Log();
log.setFoo(foo);
// check may be this is a internal call, not from web
if(request != null){
log.setSomeRequestData(request.getAttribute("name"));
}
logService.saveLog(log);
} catch (Throwable ex) {
// lets rollback everything
throw new RuntimeException();
}
return null;
});
}
}
最后在你的FooService
中@Loggable
public Foo saveFoo(Foo foo) {}
您的控制器保持不变。
答案 2 :(得分:0)
如果在Spring上下文中使用LocalSessionFactoryBean
或它的子类(例如AnnotationSessionFactoryBean
),那么最好的选择是使用entityInterceptor
属性。你必须传递orh.hibernate.Interceptor接口的实例。例如:
// java file
public class LogInterceptor extends ScopedBeanInterceptor {
// you may use your authorization service to retrieve current user
@Autowired
private AutorizationService authorizationService
// or get the user from request
@Autowired
private HttpServletRequest request;
@Override
public boolean onSave(final Object entity, final Serializable id, final Object[] state, final String[] propertyNames, final Type[] types) {
// get data from request
// your save logic here
return true;
}
}
// in spring context
<bean id="sessionFactory"
class="org.springframework.orm.hibernate3.annotation.AnnotationSessionFactoryBean" destroy-method="destroy">
<property name="dataSource" ref="dataSource"/>
<property name="hibernateProperties">
....
</property>
....
<property name="entityInterceptor" ref="logInterceptor"/>
</bean>
将以下内容添加到web.xml
(或在java代码中添加侦听器,具体取决于您使用的内容)。
<listener>
<listener-class>
org.springframework.web.context.request.RequestContextListener
</listener-class>
</listener>
添加请求范围bean,因此它将被请求识别。
<bean id="logInterceptor" class="LogInterceptor" scope="request">
<aop:scoped-proxy proxy-target-class="false" />
</bean>
您可以将日志数据提取与拦截器分开,因此将有一个不同的请求范围组件,或者您也可以使用过滤器在ThreadLocal
中存储数据。