Magic AOP:面向切面的业务日志框架设计,第二部分 博客分类: Analysis&Design AOP框架JPAActiveMQ企业应用
本文是Magic AOP:面向切面的业务日志框架设计的第二部分,第一部分请见http://lee5593.iteye.com/admin/show/88163
3.1. 业务日志记录组件设计
业务日志记录组件的设计思路是通过事先定义好的业务类型和操作类型对业务方法进行两个维度的分类,利用BusinessLog注解对需要记录业务日志的业务方法进行声明,然后用AOP对声明了BusinessLog注解的业务方法进行拦截,取到注解中的参数组装业务日志消息对象,调用业务日志输出组件进行输出。
<o:p></o:p>
对一个业务方法进行业务方法进行声明的代码看起来象这样:
- @BusinessLog(info="info message")
- public void anService(){…}
- @BusinessLog(success="success message",failture="failture message",state=State.VALID)
- public void anotherService(){…}
对于业务日志信息中的会话信息(如用户标识、客户端机器标识),采用了ThreadLocal通过Filter获取后,直接在业务日志消息对象构造方法中进行赋值,以避免客户端开发人员手动编码。
我们推荐使用上述的方式来记录业务日志,当然你也可以将BnLog对象直接注入到你的业务对象中通过显式方法调用来记录业务日志,甚至可以直接拿到BnLogConfiguration对象来按需生成BnLog对象,一切取决于你的喜好,这里只是演示了一种Best Practice。
<o:p></o:p>
业务日志记录组件中的主要接口和类清单如下:
BnLog<o:p></o:p>
业务日志记录接口,包含了多个log方法
BnLogItem<o:p></o:p>
业务日志对象,使用JPA进行数据库表映射,数据库相应的表名需要与对象类名相同。主要属性有业务类型、事件类型、详细信息、记录时间、用户标识、客户端机器标识、是否成功标识。如果扩展该业务日志对象,用JPA对扩展属性进行配置即可,表名需要与扩展对象类名相同。
BnLogConfiguration<o:p></o:p>
业务日志配置类,主要负责初始化业务类型、事件类型和维护业务日志实例缓存池。初始化业务日志框架时,该类从外部的属性文件bnlog.properties中读取定义好的参数,然后分别对业务类型、事件类型以及业务日志消息对象进行初始化,bnlog.properties的格式定义如下:
- #----------------------定制业务类型----------------------------------
- #在这里定制业务类型,每个业务类型标识字符串必须是唯一的,且不区分大小写,不能超过50个字符,多个业务类型之间请用,进行分隔。
- #默认内置general类型
- bnlog.businessTypes=general
- #----------------------定制事件类型--------------------------
- #在这里定制事件类型,每个事件类型标识字符串必须是唯一的,且不区分大小写,不能超过50个字符,多个事件类型之间请用,进行分隔。
- #默认内置general类型
- bnlog.eventTypes=general
- #-----------------------定制业务日志领域对象----------------------
- #在这里定制自己扩展的业务日志领域对象,扩展的业务日志领域对象必须继承基类BnLogItem,并自行实现扩展属性的annotations。
- #如果自行扩展业务日志领域对象,需要修改关系表结构脚本,使之与扩展的业务日志领域对象一致。
- #默认为基类BnLogItem
- bnlog.entityClass=com.relax.component.bnlog.BnLogItem
主要的方法如下:
void init()<o:p></o:p>
初始化方法<o:p></o:p>
<o:p></o:p>
void destory()<o:p></o:p>
销毁方法<o:p></o:p>
<o:p></o:p>
void setBusinessTypes(String businessTypes)<o:p></o:p>
设置业务类型 <o:p></o:p>
<o:p></o:p>
void setEventTypes(String eventTypes)<o:p></o:p>
设置事件类型<o:p></o:p>
<o:p></o:p>
BnLog getBnLog(String name)<o:p></o:p>
获取BnLog实例,所有的BnLog实例都从这里获取<o:p></o:p>
<o:p></o:p>
static boolean validateBusinessType(String businessType)<o:p></o:p>
验证业务类型<o:p></o:p>
<o:p></o:p>
static boolean validateEventType(String eventType)<o:p></o:p>
验证事件类型<o:p> </o:p>
BusinessLog<o:p></o:p>
业务日志注解,用于提供方法级别的声明,配合AOP进行切面业务日志记录。<o:p></o:p>
<o:p>- @Retention(RetentionPolicy.RUNTIME)
- @Target(ElementType.METHOD)
- public @interface BusinessLog {
- /**
- * 业务类型,默认为内置general类型
- * @return general业务类型
- */
- String businessType() default BusinessType.GENERAL;
- /**
- * 事件类型,默认为内置general类型
- * @return general事件类型
- */
- String eventType() default EventType.GENERAL;
- /**
- * 当state状态为无效时的日志信息
- * @return 日志信息
- */
- String info() default "";
- /**
- * 当业务操作成功时的日志信息
- * @return 记录成功业务操作的日志信息
- */
- String success() default "";
- /**
- * 当业务操作失败时的日志信息
- * @return 记录失败业务操作的日志信息
- */
- String failture() default "";
- /**
-
* 业务操作状态,有两种选项INVALID,VALID.
- * 默认为INVALID
- * @return 业务操作状态
- */
- State state() default State.INVALID;
- }
State<o:p></o:p>
业务操作状态枚举,配合BusinessLog注解协同工作。
- public enum State {
- /**
- * 无效
- */
- INVALID,
- /**
- * 有效
- */
- VALID
- }
BnLogAspect<o:p></o:p>
业务日志通用切面,采用@AspectJ风格,Spring配置文件中需引入下列元素来启用对@AspectJ的支持。<o:p></o:p>
- @Aspect
- public class BnLogAspect {
- private final Logger logger = Logger.getInstance(BnLogAspect.class);
- private BnLog bnLog;
- /**
- * 业务日志切入点:所有声明@BusinessLog的方法
- *
- */
- @Pointcut("@annotation(com.relax.component.bnlog.annotation.BusinessLog)")
- private void bnLogAnnotation() {
- }
- /**
- * 业务操作正常返回后的业务日志切面
- *
- * @param businessLog 业务日志声明对象
- */
- @AfterReturning("bnLogAnnotation() && @annotation(businessLog)")
- public void afterReturning(BusinessLog businessLog) {
- // FIXME 正式系统应该将记录业务日志操作可能抛出的异常捕获进行处理,以避免业务日志出错对正常的业务逻辑造成干扰
- // this.log(businessLog, true);
- try {
- this.log(businessLog, true);
- } catch (BnLogException e) {
- logger.error("业务日志切面抛出异常", e);
- }
- }
- /**
- * 业务操作抛出异常后的业务日志切面
- *
- * @param businessLog 业务日志声明对象
- * @param e 业务方法抛出的异常
- * @throws Exception 将业务方法抛出的异常重新进行抛出
- */
- @AfterThrowing(pointcut = "bnLogAnnotation() && @annotation(businessLog)", throwing = "e")
- public void afterThrowing(BusinessLog businessLog, Exception e)
- throws Exception {
- // FIXME 正式系统应该将记录业务日志操作可能抛出的异常捕获进行处理,以避免业务日志出错对正常的业务逻辑造成干扰
- // this.log(businessLog, false);
- try {
- this.log(businessLog, false);
- } catch (BnLogException ex) {
- logger.error("业务日志切面抛出异常", ex);
- }
- /*将异常重新抛出*/
- throw e;
- }
- /**
- * 记录业务日志
- *
- * @param businessLog 业务日志声明对象
-
* @param state 操作状态,
true
orfalse
- * @throws BnLogException 当业务日志记录失败时抛出业务日志异常
- */
- private void log(BusinessLog businessLog, boolean state)
- throws BnLogException {
- BnLogItem bnLogItem;
- bnLogItem = new BnLogItem();
- if (!BnLogConfiguration
- .validateBusinessType(businessLog.businessType())) {
- throw new BnLogPreparationException("无效的业务类型:"
- + businessLog.businessType());
- }
- bnLogItem.setBusinessType(businessLog.businessType());
- if (!BnLogConfiguration.validateEventType(businessLog.eventType())) {
- throw new BnLogPreparationException("无效的事件类型:"
- + businessLog.eventType());
- }
- bnLogItem.setEventType(businessLog.eventType());
- if (State.INVALID == businessLog.state()) {
- if (businessLog.info().equals("")) {
- throw new BnLogPreparationException(
- "当state设置为INVALID时,info内容不能为空");
- }
- bnLogItem.setMessage(businessLog.info());
- } else {
- if (businessLog.success().equals("")
- || businessLog.failture().equals("")) {
- throw new BnLogPreparationException(
- "当state设置为VALID时,success和failture内容不能为空");
- }
- if (state) {
- bnLogItem.setMessage(businessLog.success());
- } else {
- bnLogItem.setMessage(businessLog.failture());
- }
- bnLogItem.setState(state);
- }
- bnLog.log(bnLogItem);
- }
- /**
- * 设置业务日志记录对象
- * @param bnLog
- * 业务日志记录对象
- */
- public void setBnLog(BnLog bnLog) {
- this.bnLog = bnLog;
- }
- }
3.2. 业务日志输出组件
相对于业务日志记录组件,业务日志输出组件要简单得多,我们采用了ActiveMq来对业务日志消息对象进行异步处理,以便隔离业务日志输出异常给业务操作带来的影响。你也可以不用异步处理机制,只需要实现Appender接口,重新编写一种实现来替换默认的输出机制。Appender接口非常简洁,其中只定义了一个doAppend()方法。
<o:p></o:p>
业务日志记录组件中的主要接口和类清单如下:
Appender<o:p></o:p>
业务日志输出接口,定义了一个doAppend(BnLogItem bnLogItem)方法,实现类需实现该方法对业务日志消息对象进行输出
DefaultAppenderImpl<o:p></o:p>
默认的业务日志输出类,实现了Appender接口,将业务日志持久化到数据库
<o:p></o:p>
BnLogMessageProducer<o:p></o:p>
业务日志JMS消息生产者,实现了Appender接口
<o:p></o:p>
BnLogMessageConverter<o:p></o:p>
业务日志消息转换类
<o:p></o:p>
BnLogMessageConsumer<o:p></o:p>
业务日志消息消费者,接受注入的Appender对象对业务日志消息进行真正地输出
3.3. 业务日志查询组件
业务日志查询组件定义了一个BnLogQuery接口,包含了多个查询方法,接口定义如下:
- public interface BnLogQuery {
- public List<bnlogitem></bnlogitem> listAll();
- public List<bnlogitem></bnlogitem> list(String businessType, String eventType);
- public List<bnlogitem></bnlogitem> list(String businessType, String eventType,
- Boolean state);
- public List<bnlogitem></bnlogitem> list(String businessType, String eventType,
- Boolean state, Date beginTime, Date endTime);
- public List<bnlogitem></bnlogitem> list(String hql, Object... values);
- public List<bnlogitem></bnlogitem> pageList(String hql, int pageNo, int pageSize,
- Object... values);
- }
该查询组件相对来说没有什么难度,本文不再予以详细描述。
4. 总结
本文通过我们在实际企业级项目中应用AOP的实际经验,总结了通过AOP来进行业务日志框架设计的一些经验供大家参考,并希望借此起到抛砖引玉的效果,寻求更加优秀的AOP设计方案,使面向方面软件设计(AOSD)深入人心,来改善目前面向对象设计在某些特定问题领域的不足之处,让我们试目以待。
</o:p>