需求:每次删除A表的数据,我们都希望去保留数据,记录到B表。
很多时候我们是这么做的
public void delete(id) {
UserLogMapper.insert(UserBean);//记录日志
UserMapper.delete(id);//删除记录
}
这样还得要加上事务,什么的。但是别忘记了,UserMapper里的方法如果在别的地方调用了,别人也不知道你的业务逻辑,直接调用UserMapper.delete(id);而没有记录日志。那么就破坏了原有的业务逻辑。
所以我们是在别人调用UserMapper.delete(id);之前调用UserLogMapper.insert(UserBean);。我们想到了AOP
现在我们用AOP,无侵入方式解决这个问题。
思路:用SpringAOP
我们已经有一个mapper类(mybatis)如下:
public interface UserMapper{
int delete(@Param("id") Long id);
}
超级普通的,mybatis这样的接口,用代理去实现。我们也能对这个接口的方法进行拦截。mybatis xml 如下:
delete from user
where id=#{id,jdbcType=BIGINT}
1、建一个注释
import java.lang.annotation.*;
@Target(ElementType.METHOD) //支持注解在方法上
@Retention(RetentionPolicy.RUNTIME) //支持运行时可用
@Documented //DOC文档可见
@Inherited
public @interface DatabaseAfterAspects {
Class processorClass();//调用方法前先调用这个类
String method();//调用这个类的这个方法
}
2、AOP切面,这个切面拦截以上的注释的方法后,调用注释传进来的类和方法
package com.ea.utils.aop;
import com.ea.utils.SpringContextUtils;
import org.apache.ibatis.annotations.Param;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;
import org.springframework.util.ReflectionUtils;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.util.*;
@Aspect
@Component
public class DatabaseAspectsAop {
@Pointcut("@annotation(com.ea.utils.aop.DatabaseAspects)")
private void databaseCalculate() {
}
/**
* 拦截方法,并调用processor相同的方法
* @param joinPoint
* @return
* @throws Throwable
*/
@Around("databaseCalculate()")
public Object databaseCalculate(ProceedingJoinPoint joinPoint) throws Throwable {
Object[] args = joinPoint.getArgs();
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
Class[] type = signature.getMethod().getParameterTypes();
Annotation[] annotations = signature.getMethod().getAnnotations();
// Annotation[][] paramAnnotations = method.getParameterAnnotations();
//拦截方法参数
// Map paraMap = this.getParamMap(paramAnnotations, args);
for (Annotation annotation : annotations) {
if (annotation instanceof DatabaseAspects) {
DatabaseAspects databaseCalculate = (DatabaseAspects) annotation;
Class processorName = databaseCalculate.processorClass();
String processMethod = databaseCalculate.method();
Object object = SpringContextUtils.getApplicationContext().getBean(processorName);
springInvokeMethod(object, processMethod, args, type);
}
}
return joinPoint.proceed();
}
/**
* @param service
* 服务
* @param methodName
* 方法名称
* @param params
* 参数
* @return
* @throws Exception
*/
public static Object springInvokeMethod(Object service, String methodName, Object[] params, Class[] type) {
// 找到方法
Method method = ReflectionUtils.findMethod(service.getClass(), methodName, type);
// 执行方法
return ReflectionUtils.invokeMethod(method, service, params);
}
/**
* 获取需要的参数(@PARAM)
* @param paramAnnotations
* @param args
* @return
*/
private Map getParamMap(Annotation[][] paramAnnotations, Object[] args) {
// 该集合用于记录参数索引与参数名称的对应关系
Map map = new HashMap<>();
int paramCount = paramAnnotations.length;
// 遍历所有参数
for (int paramIndex = 0; paramIndex < paramCount; paramIndex++) {
String name = null;
// 遍历该参数上的注解集合
for (Annotation annotation : paramAnnotations[paramIndex]) {
if (annotation instanceof Param) {
// 获取@Param注解指定的参数名称
name = ((Param) annotation).value();
break;
}
}
if(null == name) {
name = String.valueOf(paramIndex);
}
map.put(name, args[paramIndex]);
}
return map;
}
}
Application获取类
package com.ea.utils;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Component;
@Component
public class SpringContextUtils implements ApplicationContextAware {
/**
* 上下文对象实例
*/
private static ApplicationContext applicationContext;
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
}
/**
* 获取applicationContext
*
* @return
*/
public static ApplicationContext getApplicationContext() {
return applicationContext;
}
/**
* 通过name获取 Bean.
*
* @param name
* @return
*/
public static Object getBean(String name) {
return getApplicationContext().getBean(name);
}
/**
* 通过class获取Bean.
*
* @param clazz
* @param
* @return
*/
public static T getBean(Class clazz) {
return getApplicationContext().getBean(clazz);
}
/**
* 通过name,以及Clazz返回指定的Bean
*
* @param name
* @param clazz
* @param
* @return
*/
public static T getBean(String name, Class clazz) {
return getApplicationContext().getBean(name, clazz);
}
}
3、建记录日志类
@Service
public class UserLogService {
private UserMapper userMapper;
private UserLogMapper userLogMapper;
int insert(long id) {
userLogMapper.insert(userMapper.select(id));
}
}
XML(略)
4、配置
public interface UserMapper{
@DatabaseAspects(processorClass=UserLogService.class, method="insert")
int delete(@Param("id") Long id);
}
总结:
1、mybatis的接口类也可以进行拦截。
2、其实以上思路很简单,就是A类执行时候调用B类。其实B类可以也是mapper,只要入参一样就行。那么B类的SQL写成
insert B select A 这种形式就行。这样就可以省去service类的麻烦。
以上仅提供思路,代码可能有点瑕疵!