AOP技术——日志功能
一.AOP技术本质
AOP(Aspect-Oriented Programming,面向方面编程),可以说是OOP(Object-Oriented Programing,面向对象编程)的补充和完善。OOP引入封装、继承和多态性等概念来建立一种对象层次结构,用以模拟公共行为的一个集合。当我们需要为分散的对象引入公共行为的时候,OOP则显得无能为力。也就是说,OOP允许你定义从上到下的关系,但并不适合定义从左到右的关系。例如日志功能。日志代码往往水平地散布在所有对象层次中,而与它所散布到的对象的核心功能毫无关系。对于其他类型的代码,如安全性、异常处理和透明的持续性也是如此。这种散布在各处的无关的代码被称为横切(cross-cutting)代码,在OOP设计中,它导致了大量代码的重复,而不利于各个模块的重用。
而AOP技术则恰恰相反,它利用一种称为“横切”的技术,剖解开封装的对象内部,并将那些影响了多个类的公共行为封装到一个可重用模块,并将其名为“Aspect”,即方面。所谓“方面”,简单地说,就是将那些与业务无关,却为业务模块所共同调用的逻辑或责任封装起来,便于减少系统的重复代码,降低模块间的耦合度,并有利于未来的可操作性和可维护性。AOP代表的是一个横向的关系,如果说“对象”是一个空心的圆柱体,其中封装的是对象的属性和行为;那么面向方面编程的方法,就仿佛一把利刃,将这些空心圆柱体剖开,以获得其内部的消息。而剖开的切面,也就是所谓的“方面”了。然后它又以巧夺天功的妙手将这些剖开的切面复原,不留痕迹。
如果将整个模块比喻为一个圆柱体,那么关注点识别过程可以用三棱镜法则来形容,穿越三棱镜的光束(指需求),照射到圆柱体各处,获得不同颜色的光束,最后识别出不同的关注点。
AOP的目的,就是要将诸如Logging之类的横切关注点从BusinessLogic类中分离出来。利用AOP技术,可以对相关的横切关注点封装,形成单独的“aspect”。这就保证了横切关注点的复用。由于BusinessLogic类中不再包含横切关注点的逻辑代码,为达到调用横切关注点的目的,可以利用横切技术,截取BusinessLogic类中相关方法的消息,然后将这些“aspect”织入到该方法中。
二.日志实现
为了建立松散耦合的、可扩展的企业系统,AOP应用到的横切技术,通常分为两种类型:动态横切和静态横切,此处用的是静态织入的方式,大致可分为如下步骤:
1.定义切面
1)OperationLogAspect.java
@Aspect
public class OperationLogAspect {
private static final Logger logger = LoggerFactory.getLogger(OperationLogAspect.class.getName());
@Autowired
OperationLogService logService;
@Pointcut("@annotation(com.vastio.aop.OperationLog)")
public void methodCachePointcut() {
}
@Around("methodCachePointcut()")
public Object methodCachePointcut(ProceedingJoinPoint joinPoint) throws Throwable {
Map<String, String> operationRecord = getMethodRemark(joinPoint);
System.out.println("1-----------------------------------------");
Object result = null;
try {
// 记录操作日志...谁..在什么时间..做了什么事情..
result = joinPoint.proceed();
String ref = operationRecord.get("ref");
String refId = operationRecord.get("refId");
if (refId.isEmpty()){
if (ref.equals("true")){
refId = result.toString();
} else{
String methodName = coventArgName(ref);
Method tmpM = result.getClass().getMethod(methodName);
refId = tmpM.invoke(result).toString();
}
operationRecord.put("refId", refId);
}
append(operationRecord);
} catch (Exception e) {
logger.error("Error while save log, Message: {}", e.toString());
throw e;
}
return result;
}
private boolean append(Map<String, String> operationRecord) {
String operation = operationRecord.get("remark");
String description = operationRecord.get("des");
String refId = operationRecord.get("refId");
logService.create(operation, description, refId);
System.out.println("2-----------------------------------------");
return true;
}
/**
* 获取方法名
*
*/
private String coventArgName(String argName){
System.out.println("3-----------------------------------------");
if (!argName.isEmpty()){
String str = argName.substring(0,1).toUpperCase()+argName.substring(1);
return "get" + str;
}
return "";
}
/**
* 此方法描述的是:获得参数中的refId
* @param m
* @param arguments
* @return
* @throws Exception
*/
private String getRefIdName(Method m, Object[] arguments) {
System.out.println("4-----------------------------------------");
Annotation[][]parameterAnnotations = m.getParameterAnnotations(); //获得所有参数
String argName = "";
int argIndex = -1;
for(int i=0; i<parameterAnnotations.length; i++) {
for (Annotation an:parameterAnnotations[i]) {
// 获得指定的注释
if (an instanceof RefId) {
argName = ((RefId)an).value(); //获得参数描述
argIndex = i;
break;
}
}
}
if (argIndex != -1){
if (!argName.isEmpty()){
try {
String methodName = coventArgName(argName);
Method tmpM = arguments[argIndex].getClass().getMethod(methodName);
return tmpM.invoke(arguments[argIndex]).toString();
} catch (Exception e) {
logger.error("Error while get:{}, Message: {}", argName, e);
}
}else{
return arguments[argIndex].toString();
}
}
return "";
}
/**
* 此方法描述的是:获得注解的内容和参数的值
* @throws Exception
*/
private Map<String, String> getMethodRemark(ProceedingJoinPoint joinPoint) {
MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
Method targetMethod = methodSignature.getMethod();
OperationLog methodCache = targetMethod.getAnnotation(OperationLog.class);
OperationType type = methodCache.remark();
Object[] arguments = joinPoint.getArgs();
System.out.println("5-----------------------------------------");
Map <String, String> operationRecord = new HashMap <String, String>();
operationRecord.put("remark", type.getName());
operationRecord.put("ref", methodCache.ref());
operationRecord.put("refId", getRefIdName(targetMethod, arguments));
String description = methodCache.des();
if (description == null || description.equals("")) {
description = type.getDescription();
}
operationRecord.put("des", description);
return operationRecord;
}
}
2)OperationgLog.java
/**
* 此类描述的是:操作日志注解
*/
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface OperationLog {
//操作功能说明
public OperationType remark();
//操描述
public String des() default "";
//返回值作是否做为ref_id
public String ref() default "";
}
3)OperationType.java
/**
* 定义所有需要跟踪的操作行为
*/
public enum OperationType {
DOC_CREATE("DOC_CREATE","新建报表"),
DOC_UPDATE("DOC_UPDATE","修改报表"),
DOC_DELETE("DOC_DELETE","删除报表"),
SCREEN_CREATE("SCREEN_CREATE","添加屏幕"),
SCREEN_DELETE_DOC_ID("SCREEN_DELETE","删除屏幕"),
CHART_CREATE("CHART_CREATE","添加图表"),
CHART_DELETE("CHART_DELETE","删除图表"),
USER_MODIFY("USER_MODIFY","修改用户"),
PWD_MODIFY("PWD_MODIFY","修改密码"),
USER_CREATE("USER_CREATE","添加用户");
private String _name;
private String _description;
private OperationType(String name, String description) {
_name = name;
_description = description;
}
public String getDescription() {
return _description;
}
public String getName() {
return _name;
}
}
4)RefId.java
@Target({ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface RefId {
String value() default "";
}
5)OperationLogService.java
@Service
public class OperationLogService {
private static final Logger logger = LoggerFactory.getLogger(OperationLogService.class.getName());
@Autowired
private IDBI dbi;
public long create(String operation, String description, String refId) {
OperationLogDao operationLogDao = dbi.onDemand(OperationLogDao.class);
OperationLog record = new OperationLog();
Integer id = operationLogDao.getSequence();
Date realDate = new Date();
Timestamp timestamp = new Timestamp(realDate.getTime());
Subject currentUser = SecurityUtils.getSubject();
String userId = (String)currentUser.getPrincipal();
if (userId == null) {
userId = "0";
l ogger.error("Cannot get User info. Use id=0 instead.");
}
record.setId(id);
record.setOperationRecord(operation, timestamp, userId, description, refId);
try{
return operationLogDao.insert(record);
}catch(Exception e){
logger.error("Error save db, Message: {}", e.getMessage());
return -1;
}
}
public long count(StatCriteria criteria) {
OperationLogDao operationLogDao = dbi.onDemand(OperationLogDao.class);
Date startTime = DateTimeUtil.getStartOfDay(criteria.startDate);
Date endTime = DateTimeUtil.getEndOfDay(criteria.endDate);
return operationLogDao.count(
new Timestamp(startTime.getTime()),
new Timestamp(endTime.getTime()));
}
public List<OperationLog> findLogs(int from , int size, SearchBean searchBean) {
OperationLogDao operationLogDao = dbi.onDemand(OperationLogDao.class);
if(searchBean.getUserId() != null && !("".equals(searchBean.getUserId().trim()))){
searchBean.setUserId("%"+searchBean.getUserId().trim()+"%");
}
return operationLogDao.findLogs(from,size,searchBean);
}
public int total(SearchBean searchBean){
OperationLogDao operationLogDao = dbi.onDemand(OperationLogDao.class);
return operationLogDao.total(searchBean);
}
public void clearLog(long day){
OperationLogDao operationLogDao = dbi.onDemand(OperationLogDao.class);
Date d1=new Date();
Long t1=d1.getTime() - (day*1000L*3600L*24L);
Timestamp time=new Timestamp(t1);
operationLogDao.clearLog(time);
}
}
还有Bean,Dao之类的这里不说明了。
2.通过@Bean注解注入容器
在配置文件(此处以我自己的为例AppConfig.java)中,将其放入容器当中。
@Bean(name = "operationLogAspect")
public OperationLogAspect getOperationLogAspect() {
return new OperationLogAspect();
}
3.找到目标类service
//插入一个Doc
@OperationLog(remark = OperationType.DOC_CREATE)
public void insert(@RefId ("id") Doc doc) {
DocDao docDao = dbi.onDemand(DocDao.class);
Timestamp createTime = new Timestamp(new Date().getTime());
doc.setCreateTime(createTime);
Timestamp modifyTime = new Timestamp(0);
doc.setModifyTime(modifyTime);
docDao.insert(doc);
}
//根据id删除Doc
@OperationLog(remark = OperationType.DOC_DELETE)
public void deleteById(@RefId String id) {
DocDao docDao = dbi.onDemand(DocDao.class);
docDao.deleteById(id);
}