自定义注解以及其结合AOP和反射的使用

用一个词就可以描述注解,那就是元数据,即一种描述数据的数据。Annotations仅仅是元数据,和业务逻辑无关。

元注解

说自定义注解之前,肯定要说元注解,因为自定义注解是由元注解来定义的。

  • @Documented :注解是否将包含在JavaDoc中

  • @Inherited :是否允许子类继承该注解

  • @Retention :什么时候使用该注解,即注解的生命周期

    • RetentionPolicy.SOURCE :在编译阶段丢弃。这些注解在编译结束之后就不再有任何意义,所以它们不会写入字节码。@Override, @SuppressWarnings都属于这类注解。
    • RetentionPolicy.CLASS :在类加载的时候丢弃。在字节码文件的处理中有用。注解默认使用这种方式
    • RetentionPolicy.RUNTIME :始终不会丢弃,运行期也保留该注解,因此可以使用反射机制读取该注解的信息。我们自定义的注解通常使用这种方式
  • @Target :注解用于什么地方

    • ElementType.TYPE:用于描述类、接口或enum声明
    • ElementType.FIELD:用于描述实例变量
    • ElementType.METHOD :用于方法
    • ElementType.PARAMETER
    • ElementType.CONSTRUCTOR
    • ElementType.LOCAL_VARIABLE
    • ElementType.ANNOTATION_TYPE :另一个注释
    • ElementType.PACKAGE :用于记录java文件的package信息

自定义注解

用例:


import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target(ElementType.METHOD)     
@Retention(RetentionPolicy.RUNTIME)
@interface Todo {
    public enum Priority {LOW, MEDIUM, HIGH}
    public enum Status {STARTED, NOT_STARTED}    

    String author() default "Yash";
    Priority priority() default Priority.LOW;
    Status status() default Status.NOT_STARTED;
}

自定义注解类编写的一些规则:
1. Annotation型定义为@interface 所有的Annotation会自动继承java.lang.Annotation这一接口,并且不能再去继承别的类或是接口.
2. 参数成员只能用public或默认(default)这两个访问权修饰
3. 参数成员只能用基本类型byte,short,char,int,long,float,double,boolean八种基本数据类型和String、Enum、Class、annotations等数据类型,以及这一些类型的数组。
4. 如果只有一个成员,成员名称最好用value,因为默认就是它。
4. 一般定义参数成员后,都会给一个默认值,注意String一般不用null做默认值,可以用字符串或空串(”“)
5. 要获取类方法和字段的注解信息,必须通过Java的反射技术来获取 Annotation对象,因为你除此之外没有别的获取注解对象的方法
6. 注解也可以没有定义成员, 不过这样注解就没啥用了

使用:

public class BusinessLogic {
    public BusinessLogic() {
        super();
    }

    public void compltedMethod() {
        System.out.println("This method is complete");
    }    

    @Todo(priority = Todo.Priority.HIGH)
    public void notYetStartedMethod() {
        // No Code Written yet
    }

    @Todo(priority = Todo.Priority.MEDIUM, author = "Uday", status = Todo.Status.STARTED)
    public void incompleteMethod1() {
        //Some business logic is written
        //But its not complete yet
    }

    @Todo(priority = Todo.Priority.LOW, status = Todo.Status.STARTED )
    public void incompleteMethod2() {
        //Some business logic is written
        //But its not complete yet
    }
}

Annotations 仅仅是元数据,和业务逻辑无关,并没有做逻辑处理。那么接下来问题来了,怎么做这个逻辑处理?

AOP+反射+自定义注解

自定义注解可以用来做权限控制、字段校验,最常见的就是Log记录。下面我们就来看看AOP+反射+自定义注解做的Log记录

扫描二维码关注公众号,回复: 916320 查看本文章

1、新建Log记录实体类

public class LogDO {
    private Long id;

    private Long userId;

    private String username;

    private String operation;

    private Integer time;

    private String method;

    private String params;

    private String ip;

    @JsonFormat(timezone = "GMT+8", pattern = "yyyy-MM-dd HH:mm:ss")
    private Date gmtCreate;

    //省去getter、sertter方法
}

这里持久层dao就不写了。不是重点。

2.、新建一个注解Log

import java.lang.annotation.Retention;
import java.lang.annotation.ElementType;
import java.lang.annotation.Target;
import java.lang.annotation.RetentionPolicy;

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Log {
    String value() default "";
}

3、新建一个LogAspect来监听Log对象

@Aspect
@Component
public class LogAspect {
    @Autowired
    LogDao logMapper;  //由于篇幅原因,持久层dao省略了

    @Pointcut("@annotation(com.bootdo.common.annotation.Log)")
    public void logPointCut() {
    }

    @Around("logPointCut()")
    public Object around(ProceedingJoinPoint point) throws Throwable {
        long beginTime = System.currentTimeMillis();
        // 执行方法
        Object result = point.proceed();
        // 执行时长(毫秒)
        long time = System.currentTimeMillis() - beginTime;
        // 保存日志
        saveLog(point, time);
        return result;
    }

    private void saveLog(ProceedingJoinPoint joinPoint, long time) {
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        Method method = signature.getMethod();
        LogDO sysLog = new LogDO();
        //通过注解,获取注解对象
        Log syslog = method.getAnnotation(Log.class);
        if (syslog != null) {
            // 注解上的描述
            sysLog.setOperation(syslog.value());
        }
        // 请求的方法名
        String className = joinPoint.getTarget().getClass().getName();
        String methodName = signature.getName();
        sysLog.setMethod(className + "." + methodName + "()");
        // 请求的参数
        Object[] args = joinPoint.getArgs();
        try {
            String params = JSONUtils.beanToJson(args[0]).substring(0, 4999);
            sysLog.setParams(params);
        } catch (Exception e) {

        }
        // 获取request
        HttpServletRequest request = HttpContextUtils.getHttpServletRequest();
        // 设置IP地址
        sysLog.setIp(IPUtils.getIpAddr(request));
        // 用户名 这里用的是shiro管理用户信息,要是其他的框架,也可以
        UserDO currUser = ShiroUtils.getUser();
        //UserDO currUser = null; //如果不会shiro,用这个,同样不影响日志的打印
        if (null == currUser) {
            if (null != sysLog.getParams()) {
                sysLog.setUserId(-1L);
                sysLog.setUsername(sysLog.getParams());
            } else {
                sysLog.setUserId(-1L);
                sysLog.setUsername("获取用户信息为空");
            }
        } else {
            sysLog.setUserId(ShiroUtils.getUserId());
            sysLog.setUsername(ShiroUtils.getUser().getUsername());
        }
        sysLog.setTime((int) time);
        // 系统当前时间
        Date date = new Date();
        sysLog.setGmtCreate(date);
        // 保存系统日志
        logMapper.save(sysLog);
    }
}

这里面有些方法HttpServletRequestShiroUtils等,都是为了得到LogDO对象字段值的一种手段,这里就不贴代码了。总之,如上面所示,可以通过AOP来监听注解Log这个切面,然后,再利用反射来处理方法上的注解。

4、使用

    @Log("请求访问主页")
    @GetMapping({ "/index" })
    String index(Model model) {
        List<Tree<MenuDO>> menus = menuService.listMenuTree(getUserId());
        model.addAttribute("menus", menus);
        model.addAttribute("name", getUser().getName());
        model.addAttribute("username", getUser().getUsername());
        return "index_v1";
    }

这样,通过一个注解,就可以记录一条用户使用的日志到数据库中去了。

猜你喜欢

转载自blog.csdn.net/she_lock/article/details/80254911