KittyDaddy's blog KittyDaddy's blog
首页
  • 学习笔记

    • 《Java基础》
    • 《常用设计模式》
    • 《MYSQL》
    • 《GO语言》
    • 《Spring源码解读》
  • 微服务解决方案

    • 锁的演化
    • 简单限流方案
    • 海量数据切分
  • 中间件

    • Nginx
    • MQ
    • Redis
    • Keepalived
  • 面试记
  • 杂文
  • 开源
  • 友情链接
关于
收藏
  • 分类
  • 标签
  • 归档
GitHub (opens new window)

老猫

万物皆系统
首页
  • 学习笔记

    • 《Java基础》
    • 《常用设计模式》
    • 《MYSQL》
    • 《GO语言》
    • 《Spring源码解读》
  • 微服务解决方案

    • 锁的演化
    • 简单限流方案
    • 海量数据切分
  • 中间件

    • Nginx
    • MQ
    • Redis
    • Keepalived
  • 面试记
  • 杂文
  • 开源
  • 友情链接
关于
收藏
  • 分类
  • 标签
  • 归档
GitHub (opens new window)
  • java基础-反射基础
  • java基础-反射应用
  • java基础-泛型
  • java基础-注解
    • java基础-cglib和jdk动态代理
    • java基础-拓展了个新业务枚举类型,资损了
    • 麻了,这让人绝望的大事务提交
    • java基础-背会了常见的几个线程池用法,结果被问翻了
    • 小猫日常踩坑-前任开发在代码里下毒了,支付下单居然没加幂等
    • 小猫日常踩坑-糟糕!商详页进不去了,缓存被击穿了
    • 小帅面试记-又栽了,惨遭ThreadLocal夺命连环问
    • 都说了别用BeanUtils.copyProperties,这不翻车了吧
    • 简直了,被“Java并发锁”问题追问到自闭...
    • 如此丝滑的API设计,用起来就是爽
    • 《Java基础》笔记
    老猫
    2022-09-17
    目录
    背景
    缘起
    元注解
    @Retention注解
    @Document注解
    @Target注解
    @Inherited注解
    @Repeatable注解
    自定义注解实战应用

    java基础-注解原创

    # 背景

    为什么要再次梳理一下java注解,显而易见,因为重要啊。也是为研究各大类开源框架做铺垫,只有弄清楚Java注解相关原理,才能看懂大部分框架底层的设计。

    # 缘起

    注解也叫做元数据,是JDK1.5版本开始引入的一个特性,用来对代码进行标记说明,可以对包、类、接口、字段、方法参数、局部变量等进行注解修饰。其本身不包含任何业务逻辑。 一般注解大类分为三种:

    • JDK自带的相关注解
    • 自定义的注解
    • 第三方的(例如相关的框架中的注解)

    注解三步走:定义、配置、解析

    • 定义:定义标记
    • 配置:把标记打到需要用到的代码中
    • 解析:在编译器或运行时检测到标记,并进行特殊操作

    # 元注解

    什么是元注解?元注解的作用就是负责注解其他注解。元注解有以下五种:

    • @Retention:指定其所修饰的注解的保留策略
    • @Document:该注解是一个标记注解,用于指示一个注解将被文档化
    • @Target:用来限制注解的使用范围
    • @Inherited:该注解使父类的注解能被其子类继承
    • @Repeatable:该注解是Java8新增的注解,用于开发重复注解

    # @Retention注解

    用于指定被修饰的注解可以保留多长时间,即指定JVM策略在哪个时间点上删除当前注解。 目前存在以下三种策略

    策略值 功能描述
    Retention.SOURCE 注解只在源文件中保留,在编译期间删除
    Retention.CLASS 注解只在编译期间存在于.class文件中,运行时JVM不可获取注解信息,该策略值也是默认值
    Retention.RUNTIME 运行时JVM可以获取注解信息(反射),是最长注解持续期

    # @Document注解

    @Document注解用于指定被修饰的注解可以被javadoc工具提取成文档。定义注解类时使用@Document注解进行修饰,则所有使用该注解修饰的程序元素的API文档中将会包含该注解说明。

    # @Target注解

    @Target注解用来限制注解的使用范围,即指定被修饰的注解能用于哪些程序单元。标记注解方式如下:@Target({应用类型1, 应用类型2,...})【@Target(ElementType.FIELD)】 枚举值的介绍如下:

    枚举值 功能描述
    ElementType.Type 可以修饰类、接口、注解或枚举类型
    ElementType.FIELD 可以修饰属性(成员变量),包括枚举常量
    ElementType.METHOD 可以修饰方法
    ElementType.PAPAMETER 可以修饰参数
    ElementType.CONSTRUCTOR 可以修饰构造方法
    ElementType.LOCAL_VARIABLE 可以修饰局部变量
    ElementType.ANNOTATION_TYPE 可以修饰注解类
    ElementType.PACKAGE 可以修饰包
    ElementType.TYPE_PARAMETER JDK8之后的新特性,表示该注解能写在类型变量的声明语句中(如,泛型声明)
    ElementType.TYPE_USE JDK8之后的新特性,表示该注解能写在使用类型的任何语句中(例如:声明语句、泛型和强制转换语句中的类型)

    # @Inherited注解

    @Inherited注解指定注解具有继承性,如果某个注解使用@Inherited进行修饰,则该类使用该注解时,其子类将自动被修饰。 按照以上三步走的流程,咱们这里来举例子写代码说明一下: (1)定义注解

    @Target(ElementType.TYPE)
    @Retention(RetentionPolicy.RUNTIME)
    @Inherited
    public @interface InheritedExtend {
        String comment();
        int order() default 1;
    }
    
    1
    2
    3
    4
    5
    6
    7

    (2)配置:标记打到类上

    @InheritedExtend(comment ="注解继承",order = 2)
    public class Base {
    }
    
    1
    2
    3

    (3)解析:获取注解并解析做测试

    public class InheritedDemo extends Base{
        public static void main(String[] args) {
            //从本类中获取父类注解信息
            InheritedExtend extend = InheritedDemo.class.getAnnotation(InheritedExtend.class);
            //输出InheritedExtend注解成员信息
            System.out.println(extend.comment()+":"+extend.order());
            //打印出InheritedDemo是否类是否具有@InheritedExtend修饰
                              System.out.println(InheritedDemo.class.isAnnotationPresent(InheritedExtend.class));
        }
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10

    结果输出: 注解继承:2 true 以上结果就很好地说明了该注解的继承性质。

    # @Repeatable注解

    @Repeatable注解是Java8新增的注解,用于开发重复注解。在Java8之前,同一个程序元素前只能使用一个相同类型的注解,如果需要在同一个元素前使用多个相同类型的注解必须通过注解容器来实现。从Java8开始,允许使用多个相同的类型注解来修饰同一个元素,前提是该类型的注解是可重复的,即在定义注解时要用 @Repeatable元注解进行修饰。

    (1)定义注解

    @Target(ElementType.TYPE)
    @Retention(RetentionPolicy.RUNTIME)
    @Repeatable(AnnolContents.class)
    public @interface RepeatableAnnol {
        String name() default "老猫";
        int age();
    }
    
    //注解为容器,
    @Target(ElementType.TYPE)
    @Retention(RetentionPolicy.RUNTIME)
    @interface AnnolContents {
        //定义value成员变量,该成员变量可以接受多个@RepeatableAnnol注解
        RepeatableAnnol[] value();
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15

    (2)注解使用以及解析

    @RepeatableAnnol(name = "张三",age = 12)
    @RepeatableAnnol(age = 23)
    public class RepeatableAnnolDemo {
        public static void main(String[] args) {
            RepeatableAnnol[] repeatableAnnols = RepeatableAnnolDemo.class.getDeclaredAnnotationsByType(RepeatableAnnol.class);
    
            for(RepeatableAnnol repeatableAnnol : repeatableAnnols){
                System.out.println(repeatableAnnol.name() + "----->" + repeatableAnnol.age());
            }
    
            AnnolContents annolContents = RepeatableAnnolDemo.class.getDeclaredAnnotation(AnnolContents.class);
            System.out.println(annolContents);
        }
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14

    结果输出:

    张三----->12
    老猫----->23
    @com.ktdaddy.annotation.repeatable.AnnolContents(value={@com.ktdaddy.annotation.repeatable.RepeatableAnnol(name="张三", age=12), @com.ktdaddy.annotation.repeatable.RepeatableAnnol(name="老猫", age=23)})
    
    1
    2
    3

    # 自定义注解实战应用

    利用注解+springAOP实现系统日志记录,主要用于记录相关的日志到数据库,当然,老猫这里的demo只会到日志打印层面,至于数据库落库存储有兴趣的小伙伴可以进行扩展。

    以下是maven依赖:

     <dependencies>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter</artifactId>
            </dependency>
            
            <dependency>
                  <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-web</artifactId>
            </dependency>
    
            <dependency>
            <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-aop</artifactId>
            </dependency>
            <dependency>
                <groupId>com.alibaba</groupId>
                <artifactId>fastjson</artifactId>
                <version>1.2.79</version>
            </dependency>
        </dependencies>
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21

    注解的定义如下:

    @Target(ElementType.METHOD)
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    public @interface OperateLog {
        String desc() default "";
    }
    
    1
    2
    3
    4
    5
    6

    这个地方只是定义了一个字段,当然大家也可以进行拓展。

    接下来,咱们以这个注解作为切点编写相关的切面程序。具体代码如下:

    @Aspect
    @Component
    @Order(0)
    public class OperateLogAdvice {
    
        @Pointcut("@annotation(com.ktdaddy.annotation.OperateLog)")
        public void recordLog(){
        }
    
        @Around("recordLog()")
        public Object recordLogOne(ProceedingJoinPoint joinPoint) throws Throwable {
            System.out.println("进来了");
            MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
            Method method = methodSignature.getMethod();
            OperateLog operateLog = methodSignature.getMethod().getAnnotation(OperateLog.class);
            String spELString = operateLog.desc();
            //创建解析器
            SpelExpressionParser parser = new SpelExpressionParser();
            //获取表达式
            Expression expression = parser.parseExpression(spELString);
            //设置解析上下文(有哪些占位符,以及每种占位符的值)
            EvaluationContext context = new StandardEvaluationContext();
            //获取参数值
            Object[] args = joinPoint.getArgs();
            //获取运行时参数的名称
            DefaultParameterNameDiscoverer discoverer = new DefaultParameterNameDiscoverer();
            String[] parameterNames = discoverer.getParameterNames(method);
            for (int i = 0; i < parameterNames.length; i++) {
                context.setVariable(parameterNames[i],args[i]);
            }
            //解析,获取替换后的结果
            String result = expression.getValue(context).toString();
            System.out.println(result);
            return joinPoint.proceed();
        }
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36

    关于切面这块不多做赘述,非本篇文章的重点。 接下来就可以在我们的代码层面使用相关的注解了,具体如下:

    @Service
    public class UserServiceImpl implements UserService {
        @OperateLog(desc = "#user.desc")
        public void saveUser(User user){
            System.out.println("测试注解...");
        }
    }
    
    1
    2
    3
    4
    5
    6
    7

    关于controller层面就省略了,都是比较简单的。 通过上述切面以及注解解析,我们可以获取每次传参的参数内容,并且将相关的日志进行记录下来,当然这里面涉及到了SpEL表达式注入,相关的知识点,小伙伴们可以自行学习。 最终启动服务,并且请求之后具体的日志如下。

    进来了
    这是测试
    测试注解...
    {"age":12,"desc":"这是测试","id":1,"name":"张三","operator":"操作人"}
    
    1
    2
    3
    4

    至此关于Java注解的回顾学习已经结束,之后咱们再去看一些底层代码的时候或许会轻松很多。

    #Java基础#注解
    上次更新: 2022/11/30, 00:06:25
    java基础-泛型
    java基础-cglib和jdk动态代理

    ← java基础-泛型 java基础-cglib和jdk动态代理→

    阅读全文
    最近更新
    01
    让大龄程序员欲罢不能的事儿
    09-23
    02
    运营明明设置了活动开始时间,为什么到点没生效?聊聊动态定时任务
    07-30
    03
    不是,大哥,咱这小门小户的,别搞我CDN流量啊
    07-25
    更多文章>

    1 评论
    Powered By Valine
    v1.5.1
    Theme by Vdoing | Copyright © 2020-2025 Kitty Daddy | License
    • 跟随系统
    • 浅色模式
    • 深色模式
    • 阅读模式