天天都在使用的 Java 注解,你真的了解它吗?(二)

简介: Hello,大家好,我是阿粉,Java 的注解相信大家天天都在用,但是关于注解的原理,大家都了解吗?这篇文章通过意见简单的示例给大家演示一下注解的使用和原理。

使用

上面的动作只是把注解定义出来了,但是光光定义出来是没有用的,必须有一个地方读取解析,才能提现出注解的价值,我们就采用 Spring 的 AOP 拦截这个注解,将所有携带这个注解的方法所进行的操作都记录下来。

package com.api.config;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
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.beans.factory.annotation.Autowired;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.*;
/**
 * <br>
 * <b>Function:</b><br>
 * <b>Author:</b>@author 子悠<br>
 * <b>Date:</b>2020-11-17 14:40<br>
 * <b>Desc:</b>aspect for operation log<br>
 */
@Aspect
@Component
@Order(-5)
@Slf4j
public class LogAspect {
    /**
     * Pointcut for methods which need to record operate log
     */
    @Pointcut("within(com.xx.yy.controller..*) && @annotation(com.api.annotation.OperationLog)")
    public void logAspect() {
    }
    /**
     * record log for Admin and DSP
     *
     * @param joinPoint parameter
     * @return result
     * @throws Throwable
     */
    @Around("logAspect()")
    public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
        Object proceed = null;
        String classType = joinPoint.getTarget().getClass().getName();
        Class<?> targetCls = Class.forName(classType);
        MethodSignature ms = (MethodSignature) joinPoint.getSignature();
        Method targetMethod = targetCls.getDeclaredMethod(ms.getName(), ms.getParameterTypes());
        OperationLog operation = targetMethod.getAnnotation(OperationLog.class);
        if (null != operation && operation.write()) {
            SysMenuOpLogEntity opLogEntity = new SysMenuOpLogEntity();
            StringBuilder change = new StringBuilder();
            if (StrUtil.isNotBlank(operation.type())) {
                switch (operation.type()) {
                    case OperationType.ADD:
                        proceed = joinPoint.proceed();
                        String addString = genAddData(targetCls, operation.defaultServiceClass(), joinPoint.getArgs());
                        opLogEntity.setAfterJson(addString);
                        change.append(OperationType.ADD);
                        break;
                    case OperationType.DELETE:
                        String deleteString = autoQueryDeletedData(targetCls, operation.primaryKey(), operation.defaultServiceClass(), joinPoint.getArgs());
                        opLogEntity.setBeforeJson(deleteString);
                        change.append(OperationType.DELETE);
                        proceed = joinPoint.proceed();
                        break;
                    case OperationType.EDIT:
                        change.append(OperationType.EDIT);
                        setOpLogEntity(opLogEntity, targetCls, operation.primaryKey(), operation.defaultServiceClass(), joinPoint.getArgs());
                        proceed = joinPoint.proceed();
                        break;
                    case OperationType.SELECT:
                        opLogEntity.setBeforeJson(getQueryString(targetCls, operation.defaultServiceClass(), joinPoint.getArgs()));
                        change.append(operation.type());
                        proceed = joinPoint.proceed();
                        break;
                    case OperationType.SAVE:
                        savedDataOpLog(opLogEntity, targetCls, operation.primaryKey(), operation.defaultServiceClass(), joinPoint.getArgs());
                        change.append(operation.type());
                        proceed = joinPoint.proceed();
                        break;
                    case OperationType.EXPORT:
                    case OperationType.DOWNLOAD:
                        change.append(operation.type());
                        proceed = joinPoint.proceed();
                        break;
                    default:
                }
                opLogEntity.setExecType(operation.type());
            }
            StringBuilder changing = new StringBuilder();
            if (StrUtil.isNotBlank(opLogEntity.getExecType())) {
                if (operation.auth()) {
                    LoginUserVO loginUser = getLoginUser();
                    if (null != loginUser) {
                        opLogEntity.setUserId(loginUser.getUserId());
                        opLogEntity.setUserName(loginUser.getUserName());
                        changing.append(loginUser.getUserName()).append("-");
                    } else {
                        log.error("用户未登录");
                    }
                }
                opLogEntity.setCreateTime(DateUtils.getCurDate());
                opLogEntity.setRemark(getOperateMenuName(targetMethod, operation.desc()));
                opLogEntity.setPath(getPath(targetMethod, targetMethod.getName()));
                opLogEntity.setChanging(changing.append(change).toString());
                menuOpLogService.save(opLogEntity);
            }
        }
        return proceed;
    }
    /**
     * query data by userId
     *
     * @param targetCls           class
     * @param defaultServiceClass default service class
     * @return
     * @throws Exception
     */
    private String queryByCurrentUserId(Class<?> targetCls, Class<?> defaultServiceClass) throws Exception {
        BaseService baseService = getBaseService(targetCls, defaultServiceClass);
        LoginUserVO loginUser = dspBaseService.getLoginUser();
        if (null != loginUser) {
            Object o = baseService.queryId(loginUser.getUserId());
            return JsonUtils.obj2Json(o);
        }
        return null;
    }
    /**
     * return query parameter
     *
     * @param targetCls           class
     * @param args                parameter
     * @param defaultServiceClass default service class
     * @return
     * @throws Exception
     */
    private String getQueryString(Class<?> targetCls, Class<?> defaultServiceClass, Object[] args) {
        if (args.length > 0) {
            Class<?> entityClz = getEntityClz(targetCls, defaultServiceClass);
            for (Object arg : args) {
                if (arg.getClass().equals(entityClz) || arg instanceof BaseModel) {
                    return JsonUtils.obj2Json(arg);
                }
            }
        }
        return null;
    }
    /**
     * save record log while OperatorType is SAVE
     *
     * @param opLogEntity         entity
     * @param targetCls           class
     * @param primaryKey          primaryKey
     * @param defaultServiceClass default service class
     * @param args                parameter
     * @throws Exception
     */
    private void savedDataOpLog(SysMenuOpLogEntity opLogEntity, Class<?> targetCls, String primaryKey, Class<?> defaultServiceClass, Object[] args) throws Exception {
        Class<?> entityClz = getEntityClz(targetCls, defaultServiceClass);
        BaseService baseService = getBaseService(targetCls, defaultServiceClass);
        for (Object arg : args) {
            if (arg.getClass().equals(entityClz)) {
                if (StrUtil.isNotBlank(primaryKey)) {
                    Field declaredField = entityClz.getDeclaredField(primaryKey);
                    declaredField.setAccessible(true);
                    Object primaryKeyValue = declaredField.get(arg);
                    //if primary key is not null that means edit, otherwise is add
                    if (null != primaryKeyValue) {
                        //query data by primary key
                        Object o = baseService.queryId(primaryKeyValue);
                        opLogEntity.setBeforeJson(JsonUtils.obj2Json(o));
                    }
                }
                opLogEntity.setAfterJson(JsonUtils.obj2Json(arg));
            }
        }
    }
    /**
     * set parameter which edit data
     *
     * @param opLogEntity         entity
     * @param targetCls           class
     * @param primaryKey          primaryKey
     * @param defaultServiceClass default service class
     * @param args                parameter
     * @throws Exception
     */
    private void setOpLogEntity(SysMenuOpLogEntity opLogEntity, Class<?> targetCls, String primaryKey, Class<?> defaultServiceClass, Object[] args) throws Exception {
        Map<String, String> saveMap = autoQueryEditedData(targetCls, primaryKey, defaultServiceClass, args);
        if (null != saveMap) {
            if (saveMap.containsKey(ASPECT_LOG_OLD_DATA)) {
                opLogEntity.setBeforeJson(saveMap.get(ASPECT_LOG_OLD_DATA));
            }
            if (saveMap.containsKey(ASPECT_LOG_NEW_DATA)) {
                opLogEntity.setBeforeJson(saveMap.get(ASPECT_LOG_NEW_DATA));
            }
        }
    }
    /**
     * query data for edit and after edit operate
     *
     * @param targetCls           class
     * @param primaryKey          primaryKey
     * @param defaultServiceClass default service class
     * @param args                parameter
     * @return map which data
     * @throws Exception
     */
    private Map<String, String> autoQueryEditedData(Class<?> targetCls, String primaryKey, Class<?> defaultServiceClass, Object[] args) throws Exception {
        if (StrUtil.isBlank(primaryKey)) {
            throw new Exception();
        }
        Map<String, String> map = new HashMap<>(16);
        Class<?> entityClz = getEntityClz(targetCls, defaultServiceClass);
        BaseService baseService = getBaseService(targetCls, defaultServiceClass);
        for (Object arg : args) {
            if (arg.getClass().equals(entityClz)) {
                Field declaredField = entityClz.getDeclaredField(primaryKey);
                declaredField.setAccessible(true);
                Object primaryKeyValue = declaredField.get(arg);
                //query the data before edit
                if (null != primaryKeyValue) {
                    //query data by primary key
                    Object o = baseService.queryId(primaryKeyValue);
                    map.put(ASPECT_LOG_OLD_DATA, JsonUtils.obj2Json(o));
                    map.put(ASPECT_LOG_NEW_DATA, JsonUtils.obj2Json(arg));
                    return map;
                }
            }
        }
        return null;
    }
    /**
     * return JSON data which add operate
     *
     * @param targetCls           class
     * @param args                parameter
     * @param defaultServiceClass default service class
     * @return add data which will be added
     * @throws Exception
     */
    private String genAddData(Class<?> targetCls, Class<?> defaultServiceClass, Object[] args) throws Exception {
        List<Object> parameter = new ArrayList<>();
        for (Object arg : args) {
            if (arg instanceof HttpServletRequest) {
            } else {
                parameter.add(arg);
            }
        }
        return JsonUtils.obj2Json(parameter);
    }
    /**
     * query delete data before delete operate
     *
     * @param targetCls           class
     * @param primaryKey          primaryKey
     * @param defaultServiceClass default service class
     * @param ids                 ids
     * @return delete data which will be deleted
     * @throws Throwable
     */
    private String autoQueryDeletedData(Class<?> targetCls, String primaryKey, Class<?> defaultServiceClass, Object[] ids) throws Throwable {
        if (StrUtil.isBlank(primaryKey)) {
            throw new OriginException(TipEnum.LOG_ASPECT_PRIMARY_KEY_NOT_EXIST);
        }
        //get service
        BaseService baseService = getBaseService(targetCls, defaultServiceClass);
        //get entity
        Class<?> entityClz = getEntityClz(targetCls, defaultServiceClass);
        //query deleted data by primary key
        Query query = new Query();
        WhereOperator whereOperator = new WhereOperator(entityClz);
        Set<Object> set = new HashSet<>(Arrays.asList((Object[]) ids[0]));
        whereOperator.and(primaryKey).in(set.toArray());
        query.addWhereOperator(whereOperator);
        List list = baseService.queryList(query);
        return JsonUtils.obj2Json(list);
    }
    /**
     * return service by targetCls
     *
     * @param targetCls           current controller class
     * @param defaultServiceClass default service class
     * @return service instance
     * @throws Exception
     */
    private BaseService getBaseService(Class<?> targetCls, Class<?> defaultServiceClass) throws Exception {
        //根据类名拿到对应的 service 名称
        String serviceName = getServiceName(targetCls, defaultServiceClass);
        BaseService baseService;
        if (null != defaultServiceClass) {
            baseService = (BaseService) ApplicationContextProvider.getBean(serviceName, defaultServiceClass);
        } else {
            Class<?> type = targetCls.getDeclaredField(serviceName).getType();
            baseService = (BaseService) ApplicationContextProvider.getBean(serviceName, type);
        }
        return baseService;
    }
    /**
     * return service name
     *
     * @param targetCls           current controller class
     * @param defaultServiceClass default service class
     * @return service name
     */
    private String getServiceName(Class<?> targetCls, Class<?> defaultServiceClass) {
        if (null != defaultServiceClass && Object.class != defaultServiceClass) {
            return StrUtil.left(defaultServiceClass.getSimpleName(), 1).toLowerCase() + defaultServiceClass.getSimpleName().substring(1);
        }
        return StrUtil.left(targetCls.getSimpleName(), 1).toLowerCase() + targetCls.getSimpleName().substring(1).replace("Controller", "Service");
    }
    /**
     * return entity class
     *
     * @param targetCls           current controller class
     * @param defaultServiceClass default service class
     * @return entity class
     * @throws Exception
     */
    private Class<?> getEntityClz(Class<?> targetCls, Class<?> defaultServiceClass) {
        try {
            Class<?> type;
            if (null != defaultServiceClass && Object.class != defaultServiceClass) {
                type = defaultServiceClass;
            } else {
                type = targetCls.getDeclaredField(getServiceName(targetCls, null)).getType();
            }
            String entityName = type.getName().replace("service", "entity").replace("Service", "Entity");
            Class<?> entityClz = Class.forName(entityName);
            return entityClz;
        } catch (Exception e) {
            log.error("获取 class 失败");
        }
        return null;
    }
    /**
     * require path
     *
     * @param targetMethod target method
     * @param defaultPath  default require path
     * @return require path
     */
    private String getPath(Method targetMethod, String defaultPath) {
        String path = defaultPath;
        PostMapping postMapping = targetMethod.getAnnotation(PostMapping.class);
        GetMapping getMapping = targetMethod.getAnnotation(GetMapping.class);
        RequestMapping requestMapping = targetMethod.getAnnotation(RequestMapping.class);
        if (null != postMapping) {
            path = postMapping.value()[0];
        } else if (null != getMapping) {
            path = getMapping.value()[0];
        } else if (null != requestMapping) {
            path = requestMapping.value()[0];
        }
        return path;
    }
}

上面的代码中我们定义了一个切面指定需要拦截的包名和注解,因为涉及到很多业务相关的代码,所以不能完整的提供出来,但是整个思路就是这样的,在每种操作类型前后将需要记录的数据查询出来进行记录。代码很长主要是用来获取相应的参数值的,大家使用的时候可以根据自己的需要进行取舍。比如在新增操作的时候,我们将新增的数据进行记录下来;编辑的时候将编辑前的数据查询出来和编辑后的数据一起保存起来,删除也是一样的,在删除前将数据查询出来保存到日志表中。

同样导出和下载都会记录相应信息,整个操作类型的代码如下:

package com.api.annotation;
/**
 * <br>
 * <b>Function:</b><br>
 * <b>Author:</b>@author 子悠<br>
 * <b>Date:</b>2020-11-17 22:11<br>
 * <b>Desc:</b>无<br>
 */
public interface OperationType {
    /**
     * 新增
     **/
    String ADD = "add";
    /**
     * 删除
     **/
    String DELETE = "delete";
    /**
     * 使用实体参数修改
     **/
    String EDIT = "edit";
    /**
     * 查询
     **/
    String SELECT = "select";
    /**
     * 新增和修改的保存方法,使用此类型时必须配置主键字段名称
     **/
    String SAVE = "save";
    /**
     * 导出
     **/
    String EXPORT = "export";
    /**
     * 下载
     **/
    String DOWNLOAD = "download";
}

后续在使用的时候只需要在需要的方法上加上注解,填上相应的参数即可@OperationLog(desc = "查询单条记录", path = "/data")

总结

注解一个我们天天再用的东西,虽然不难,但是我们却很少自己去写注解的代码,通过这篇文章能给大家展示一下注解的使用逻辑,希望对大家有帮助。Spring 中的各种注解本质上也是这种逻辑都需要定义使用和解析。很多时候我们可以通过自定义注解去解决很多场景,比如日志,缓存等。

相关文章
|
16天前
|
Java API 数据库
Java一分钟之-JPA注解:@Entity, @Table, @Id等
【6月更文挑战第14天】Java Persistence API (JPA) 是Java开发中的ORM框架,通过注解简化数据访问层。本文介绍了三个核心注解:`@Entity`标识实体类,`@Table`自定义表名,`@Id`定义主键。易错点包括忘记添加`@Entity`、未正确设置主键。建议使用`@GeneratedValue`和`@Column`细化主键策略和字段映射。正确理解和应用这些注解能提高开发效率和代码质量。
30 3
|
1天前
|
Java Spring
JAVA注解:传统与现代的完美结合,你的代码值得拥有!
【6月更文挑战第29天】Java注解,作为连接传统与现代的编程工具,简化企业级应用开发,提升代码可读性和维护性。通过自定义注解如`@Loggable`,可以将行为(如日志记录)与方法实现分离,减少模板代码。使用AOP(如Spring)处理注解,实现行为拦截,增强代码灵活性和可扩展性。拥抱Java注解,让代码更现代、更高效!
26 16
|
1天前
|
IDE Java 程序员
JAVA注解大揭秘:为何程序员都爱它如命?
【6月更文挑战第29天】Java注解是元数据机制,用于在代码中嵌入信息供编译器、IDE和工具使用。它们以`@`标识,可用于类、方法等,用于编译时检查、代码生成(如Lombok的`@Getter`、`@Setter`)、框架集成(如Spring的`@Autowired`)。程序员喜欢注解因其简洁性、可读性和可扩展性,能减少冗余代码并增强代码的可理解性。
22 15
|
1天前
|
IDE Java 编译器
JAVA注解,你的代码需要的一次“心灵按摩”!
【6月更文挑战第29天】Java注解是提升代码可维护性的关键,它们是编译器和IDE理解代码意图的特殊标记,不同于仅作解释的注释。注解可用于编译时检查(如@Override、@NotNull)、自动生成代码(Lombok的@Getter、@Setter)、框架集成(Spring的@Autowired、MyBatis的@Mapper)。通过注解,代码变得更简洁、功能更强大,为项目带来效率提升。尝试使用注解,赋予代码新生命!
21 12
|
1天前
|
IDE Java 编译器
深入解析JAVA注解:元数据如何改变编程世界
【6月更文挑战第29天】Java注解,作为元数据机制,为代码增添上下文信息,改变编程方式。注解标记在类、方法等上,不直接影响执行,但为编译器等提供额外信息。分为元注解、编译时和运行时注解,用于元数据提供、代码简化、提高可读性及自动化。示例展示了定义`@Loggable`注解来标记日志记录方法。注解广泛应用于依赖注入、ORM、Web服务等,提升效率和灵活性,是现代Java开发的关键。未来其应用将更广泛。
12 3
|
1天前
|
Java 编译器 开发者
JAVA注解,让代码“开口说话”的魔法术!
【6月更文挑战第29天】Java注解,一种元数据机制,让代码“开口”传达意图。它们增强可读性,提供编译器与框架处理代码的额外信息。例如,@Description注解描述方法功能,@Autowired在Spring中自动装配Bean,自定义注解如@MyCustomAnnotation允许定义独特行为。注解提升开发效率,是理解与使用Java的关键。
|
1天前
|
Java 编译器 数据库连接
JAVA注解:代码界的“隐形翅膀”?!
【6月更文挑战第29天】Java注解,编程的“隐形翅膀”,提供编译检查、框架集成及自定义元数据功能。如@Override确保方法重写正确,@Autowired在Spring中自动装配Bean。通过自定义注解,开发者能创造独特代码逻辑。例如,定义@SpecialProcessing注解标记需特殊处理的方法,增强代码可读性和可扩展性。利用注解,让代码飞翔在更广阔的世界。
11 1
|
1天前
|
XML Java 编译器
JAVA注解大揭秘:元数据,你真的了解它吗?
【6月更文挑战第29天】Java注解是元数据机制,用于为代码添加不改变逻辑的额外信息。它们在编译检查(如`@Override`)、配置(如Spring的`@Autowired`)和自定义元数据中发挥作用。自定义注解如`@Loggable`可配合AOP实现日志记录,简化代码并增强可维护性。通过定义切面类和使用`@Before`、`@After`,可以拦截并处理带注解的方法,展示注解在实际应用中的灵活性。
|
2天前
|
Java
Java自定义注解:优雅的代码标记
Java自定义注解:优雅的代码标记
9 1
|
1天前
|
IDE Java 数据库连接
JAVA注解:元数据,代码的“身份证”?!
【6月更文挑战第29天】Java注解,作为代码的“身份证”,提供元数据,用于编译时检查、自动生成代码和框架集成。例如,@Override确保方法重写正确,@Deprecated标记过时,@Autowired在Spring中实现依赖注入。Lombok的@Getter/@Setter简化getter/setter。注解提升代码质量和效率,是现代Java开发的关键实践。
7 0