利用@annotation创建一个自定义注解,定义生命周期,以及支持在方法上执行
/**
* 自定义操作日志记录注解
*
* @author zsy
*
*/
@Target({ ElementType.PARAMETER, ElementType.METHOD }) //注解放置的目标位置,METHOD是可注解在方法级别上
@Retention(RetentionPolicy.RUNTIME)//注解在哪个阶段执行
@Documented
public @interface Log
{
/**
* 描述
*/
public String title() default "";
/**
* 类型
* @return
*/
public String type() default "";//这些根据自己的需求进行修改
}
可根据自己的需求进行变更。
然后就是定义切入点,书写流程,这个切点是指定到了自定义注解上,只要使用该注解,就调用
/**
* 操作日志记录处理
*
* @author zsy
*/
@Slf4j
@Aspect
@Component//如果在写完整个流程之后,发现无论使用around还是其他都执行了两次,那就去掉@component注解
public class OperLogAspect {
// 配置织入点
@Pointcut("@annotation(org.jeecg.modules.log.annotation.Log)")
public void logPointCut() {
}
/**
* 处理完请求后执行
*
* @param joinPoint 切点
*/
@Before("logPointCut()")
public void doAfterReturning(JoinPoint joinPoint) {
handleLog(joinPoint);
}
/**
* LOG注解实现方式
* @param joinPoint
*/
protected void handleLog(final JoinPoint joinPoint) {
try {
// 获得注解
Log controllerLog = getAnnotationLog(joinPoint);
if (controllerLog == null) {
return;
}
// *========数据库日志=========*//
//SysLogUtils.getSysLog(controllerLog, joinPoint);自定义方法进行获取参数,可以往下看文章
IClCaseQueueInfo operLog = SysLogUtils.getSysLog(controllerLog, joinPoint);
// 保存数据库 这里是用到了大佬给的方法,其实这里可以直接进行保存数据库的操作,我这里是记录一下我的使用过程
SpringBeansUtils.publishEvent(new LogEvent(operLog));
} catch (Exception exp) {
// 记录本地异常日志
log.error("==前置通知异常==");
log.error("异常信息:{}", exp.getMessage());
exp.printStackTrace();
} catch (Throwable throwable) {
throwable.printStackTrace();
}
}
/**
* 是否存在注解,如果存在就获取
*/
private Log getAnnotationLog(JoinPoint joinPoint) throws Exception {
Signature signature = joinPoint.getSignature();
MethodSignature methodSignature = (MethodSignature) signature;
Method method = methodSignature.getMethod();
if (method != null) {
return method.getAnnotation(Log.class);
}
return null;
}
/**
* 参数拼装
*/
private String argsArrayToString(Object[] paramsArray) {
String params = "";
if (paramsArray != null && paramsArray.length > 0) {
for (int i = 0; i < paramsArray.length; i++) {
if (ObjectUtils.isNotEmpty(paramsArray[i]) && !isFilterObject(paramsArray[i])) {
try {
Object jsonObj = JSON.toJSON(paramsArray[i]);
params += jsonObj.toString() + " ";
} catch (Exception e) {
}
}
}
}
return params.trim();
}
/**
* 判断是否需要过滤的对象。
*
* @param o 对象信息。
* @return 如果是需要过滤的对象,则返回true;否则返回false。
*/
@SuppressWarnings("rawtypes")
public boolean isFilterObject(final Object o) {
Class<?> clazz = o.getClass();
if (clazz.isArray()) {
return clazz.getComponentType().isAssignableFrom(MultipartFile.class);
} else if (Collection.class.isAssignableFrom(clazz)) {
Collection collection = (Collection) o;
for (Iterator iter = collection.iterator(); iter.hasNext(); ) {
return iter.next() instanceof MultipartFile;
}
} else if (Map.class.isAssignableFrom(clazz)) {
Map map = (Map) o;
for (Iterator iter = map.entrySet().iterator(); iter.hasNext(); ) {
Map.Entry entry = (Map.Entry) iter.next();
return entry.getValue() instanceof MultipartFile;
}
}
return o instanceof MultipartFile || o instanceof HttpServletRequest || o instanceof HttpServletResponse
|| o instanceof BindingResult;
}
}
如果在写完整个流程之后,发现无论使用around还是其他都执行了两次,那就去掉@component注解,我本身是使用的around注解,但是他的本身特性是相当于before和after结合,执行方法前后都会执行,记得调process方法进行原有方法的执行。如果是直接使用保存数据库,基本看到这里就结束了,一下是参考大佬代码做的:
/**
* @author lengleng 系统日志事件
*/
public class LogEvent extends ApplicationEvent {
//将自己定义的实体类参数填入,注意这里继承了ApplicationEvent类,主要是做自定义监听事件
public LogEvent(IClCaseQueueInfo source) {
super(source);
}
}
/**
* @author lengleng 异步监听日志事件
*/
@Slf4j
@RequiredArgsConstructor(onConstructor_= @Autowired)
public class LogListener {
private final IClCaseQueueInfoService clCaseInfoService;
// @Async 监听者模式 观察者模式 两种模式 此处请忽略...
@Order
@EventListener(LogEvent.class)
public void saveSysLog(LogEvent event) {
IClCaseQueueInfo sysLog = (IClCaseQueueInfo) event.getSource();
clCaseInfoService.save(sysLog);
}
}
到这里,大致流程已经基本结束了,其中缺少配置相关的东西,以及参数补全的东西。
1:解析参数工具类
/**
* @author wangdi
* @date 21-9-2
*/
@Component
@Slf4j
public class ResolverKit {
private static final String PREFIX = "#{";
private static final String SUBFIX = "}";
/**
* @param contextEL 带表达式的内容
* 举例:"用户名:+#{user.username}+,用户密码:+#{user.password}+,用户年龄:+#{user.age}+,手机号:+#{user.phone.number}"
* 注意:支持map对象获取key(map.key),但是不支持在对象中嵌套map(user.map.key)
* @param proceedingJoinPoint
* @return
*/
public StringBuffer resolverContent(String contextEL, JoinPoint proceedingJoinPoint) {
try {
StringBuffer context = new StringBuffer();
/**
* 文字+#{map.name}+,+#{map.age}+#{map.abc}+.
* 拆分
*
* 文字
* #{map.name}
* ,
* #{map.age}
* #{map.abc}
* .
*/
if (contextEL.contains(PREFIX) && contextEL.contains(SUBFIX) && contextEL.contains("+")) {
List<String> strings = Arrays.asList(contextEL.split("\\+"));
for (String s : strings) {
if (s.contains(PREFIX) && s.contains(SUBFIX)) {
// 解析表达式
String spel = s.substring(s.indexOf(PREFIX) + PREFIX.length(), s.lastIndexOf(SUBFIX));
context.append(resolverExpression(spel, proceedingJoinPoint));
} else {
// 不需要解析
context.append(s);
}
}
} else {
context.append(contextEL);
}
return context;
} catch (Exception e) {
log.error("表达式解析错误, 请检查@Log注解");
return null;
}
}
/**
* 解析表达式(可嵌套两层)
*
* @param expression 举例: #{user.username} #{map.key} #{user.phone.number}
* @param proceedingJoinPoint
* @return
*/
private String resolverExpression(String expression, JoinPoint proceedingJoinPoint) throws NoSuchFieldException, IllegalAccessException {
String first = null;
String second = null;
String third = null;
if (expression.contains(".")) {
List<String> list = Arrays.asList(expression.split("\\."));
if (list.size() > 3) {
throw new RuntimeException("不支持三个变量以上的表达式: " + expression);
}
for (int i = 0; i < list.size(); i++) {
if (0 == i) {
first = list.get(i);
}
if (1 == i) {
second = list.get(i);
}
if (2 == i) {
third = list.get(i);
}
}
} else {
first = expression;
}
//获取参数值
Object[] args = proceedingJoinPoint.getArgs();
//获取运行时参数的名称
Method method = ((MethodSignature) proceedingJoinPoint.getSignature()).getMethod();
//获取参数值类型
Class<?>[] parameterTypes = method.getParameterTypes();
//获取参数值的数据
String[] parameterNames = new DefaultParameterNameDiscoverer().getParameterNames(method);
for (int i = 0; i < parameterNames.length; i++) {
Class<?> parameterType = parameterTypes[i];
String parameterName = parameterNames[i];
Object parameterValue = args[i];
if (first.equals(parameterName)) {
if (null == second) {
return (String) parameterValue;
}
if (null != second) {
// map.name user.username
if (parameterValue instanceof Map<?, ?>) {
return (String) ((Map) parameterValue).get(second);
} else {
return (String) getFieldValue(parameterValue, second + (null != third ? "." + third : ""));
}
}
}
}
return null;
}
/**
* 通过反射取对象指定字段(属性)的值
*
* @param target 目标对象
* @param fieldName 字段的名字
* @return 字段的值
*/
private Object getFieldValue(Object target, String fieldName) throws NoSuchFieldException, IllegalAccessException {
Class<?> clazz = target.getClass();
String[] fs = fieldName.split("\\.");
try {
for (int i = 0; i < fs.length - 1; i++) {
Field f = clazz.getDeclaredField(fs[i]);
f.setAccessible(true);
target = f.get(target);
if (null == target) {
// 嵌套内容中为null的返回null, 防止报错
return null;
}
clazz = target.getClass();
}
Field f = clazz.getDeclaredField(fs[fs.length - 1]);
f.setAccessible(true);
return f.get(target);
} catch (Exception e) {
throw e;
}
}
}
2:spring工具类
/**
* @author dl
* @date 2021/08/13 Spring 工具类
*/
@Slf4j
@Component
@Lazy(false)
public class SpringBeansUtils implements ApplicationContextAware, DisposableBean {
private static ApplicationContext applicationContext = null;
/**
* 取得存储在静态变量中的ApplicationContext.
*/
public static ApplicationContext getApplicationContext() {
return applicationContext;
}
/**
* 实现ApplicationContextAware接口, 注入Context到静态变量中.
*/
@Override
public void setApplicationContext(ApplicationContext applicationContext) {
if(SpringBeansUtils.getApplicationContext() == null) {
SpringBeansUtils.applicationContext = applicationContext;
}
log.info("ApplicationContext配置成功,applicationContext对象:"+ SpringBeansUtils.applicationContext);
}
/**
* 从静态变量applicationContext中取得Bean, 自动转型为所赋值对象的类型.
*/
@SuppressWarnings("unchecked")
public static <T> T getBean(String name) {
return (T) applicationContext.getBean(name);
}
public static <T> T getBean(String name, Class<T> requiredType) {
return applicationContext.getBean(name,requiredType);
}
/**
* 从静态变量applicationContext中取得Bean, 自动转型为所赋值对象的类型.
*/
public static <T> T getBean(Class<T> requiredType) {
return applicationContext.getBean(requiredType);
}
/**
* 清除SpringContextHolder中的ApplicationContext为Null.
*/
public static void clearHolder() {
if (log.isDebugEnabled()) {
log.debug("清除SpringContextHolder中的ApplicationContext:" + applicationContext);
}
applicationContext = null;
}
/**
* 发布事件
* @param event
*/
public static void publishEvent(ApplicationEvent event) {
if (applicationContext == null) {
return;
}
applicationContext.publishEvent(event);
}
/**
* 实现DisposableBean接口, 在Context关闭时清理静态变量.
*/
@Override
@SneakyThrows
public void destroy() {
SpringBeansUtils.clearHolder();
}
}
3:自定义补全实体类参数方法
/**
* 系统日志工具类
*
* @author duliang
*/
@UtilityClass
@Slf4j
public class SysLogUtils {
/**
* 写入日志文件
*
* @param controllerLog
* @return
*/
public static IClCaseQueueInfo getSysLog(Log controllerLog,JoinPoint joinPoint) {
IClCaseQueueInfo clCaseQueueInfo = new IClCaseQueueInfo();
//注入自己要用到的bean对象,之前自己用的注解自动注入,但是大佬不建议,然后就用了getbean的方式
//参数解析 获取到方法传入参数的值
//ResolverKit是用到的解析参数的bean
ResolverKit resolverKit = SpringBeansUtils.getBean(ResolverKit.class);
IClCaseQueueInfoService iClCaseQueueInfoService = SpringBeansUtils.getBean(IClCaseQueueInfoService.class);
IClCaseInfoService clCaseInfoService = SpringBeansUtils.getBean(IClCaseInfoService.class);
//解析数据
String currentQueue = controllerLog.currentQueue();
currentQueue = resolverKit.resolverContent(currentQueue, joinPoint).toString();
...省略一些业务逻辑代码
return clCaseQueueInfo;
}
最后就是配置类
/**
* @author lengleng
* @date 2019/2/1 日志自动配置
*/
@EnableAsync
@RequiredArgsConstructor
@ConditionalOnWebApplication
@Configuration(proxyBeanMethods = false)
public class LogAutoConfiguration {
//如果是使用异步监听 这个必须有
@Bean
public LogListener sysLogListener(IClCaseQueueInfoService clCaseInfoService) {
return new LogListener(clCaseInfoService);
}
@Bean
public OperLogAspect logAspect() {
return new OperLogAspect();
}
}
然后就是使用了,我放一下我使用的方式,因为当时取值是map集合,所以是这样的:
//+{传入的map名字.具体的字段名字}是解析方法定义的格式
@Log(title = "自定义一些信息",currentQueue = "+#{传入的map名字.具体的字段名字}"")
最后就是,咱是个小萌新,慢慢学...

1万+

被折叠的 条评论
为什么被折叠?



