注解的使用以及常用应用场景+案例代码

news/2024/5/19 5:19:47 标签: spring, java, aop, annotation, 反射

文章目录

  • 前言
  • 一、什么是注解(Annotation)
    • 1,java内置注解
    • 2,元注解
  • 二、如何自定义注解
  • 三、注解的应用
    • 1,反射获取注解
    • 2,自定义注解+拦截器 实现登录校验
    • 3,自定义注解+AOP 实现日志打印
  • 总结


前言

Java自定义注解一般使用场景为:自定义注解+拦截器或者AOP,使用自定义注解来自己设计框架,使得代码看起来非常优雅。本文将先从自定义注解的基础概念说起,然后开始实战,写小段代码实现自定义注解+拦截器,自定义注解+AOP。

一、什么是注解(Annotation)

Java注解又称Java标注,是JDK5.0版本开始支持加入源代码的特殊语法元数据。
Java语言中的类、方法、变量、参数和包等都可以被标注。和Javadoc不同,Java标注可以通过反射获取标注内容。在编译器生成类文件时,标注可以被嵌入到字节码中。Java虚拟机可以保留标注内容,在运行时可以获取到标注内容。当然它也支持自定义Java标注。

java_17">1,java内置注解

@Override:定义在java.land.Overide中,此只适用于修饰方法,表明一个方法声明打算重写超类中的另一个方法声明
@Deprecated :定义在java.land.Deprecated中,此注释可以用于修饰方法、属性、类,表示不鼓励程序员使用这样的元素,通常是因为它很危险或者存在更好的选择
@SuppressWarnings:定义在java.lang.SuppressWarnings中,用来抑制编译时的警告信息。与前两个注释有所不同,你需要添加一个参数才能正确的使用,这些参数都是已经定义好的
@SuppressWarnings(“all”)、@SuppressWarnings(“unchecked”)、@@SuppressWarnings(value={“unchecked”,“deprecation”})

2,元注解

@Target
描述了注解修饰的对象范围,取值在java.lang.annotation.ElementType定义,常用的包括:

  • METHOD:用于描述方法
  • PACKAGE:用于描述包
  • PARAMETER:用于描述方法变量
  • TYPE:用于描述类、接口或enum类型

@Retention:
表示注解保留时间长短。取值在java.lang.annotation.RetentionPolicy中,取值为

  • SOURCE:在源文件中有效,编译过程中会被忽略
  • CLASS:随源文件一起编译在class文件中,运行时忽略
  • RUNTIME:在运行时有效

只有定义为RetentionPolicy.RUNTIME时,我们才能通过注解反射获取到注解

@Documented
表示注解将被包含在javadoc中

@Inherited
表示子类可以继承父类中的该注解

二、如何自定义注解

使用 @interface自定义注解时,自动继承java.land.annotation.Annotation接口
分析:

  • @interface是用来声明一个注解,格式 public @interface 注解名{定义内容}
  • 其中的每一个方法实际上是声明了一个配置参数
  • 方法的名称就是参数的名称
  • 返回值类型就是参数的类型(返回值类型只能是基本类型,Class,String,enum)
  • 可以通过default来声明参数的默认值
  • 如果只有一个参数成员,一般参数名为value
  • 注解元素必须要有值,我们定义注解元素时,经常使用空字符串、0作为默认值

说了这么多给个代码案例应该就懂了

public  class  Test{
    // 注解可以显示赋值,如果没有默认值,就必须给注解赋值
    @MyAnnotation(name = 1,favolate = {"ds","dfd"})
    public void go(){

    }

}

// 可以作用在class和方法上
@Target({ElementType.TYPE,ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@interface MyAnnotation{
    //注解的参数 参数类型+参数名()
    String value() default "";
    int name();
    // 参数类型为数组
    String[] favolate() default {"bug"};
}

三、注解的应用

1,反射获取注解

类注解

package com.custom;

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

/**
 * 类名注解映射表
 * @author LiuCheng
 * @data 2021/2/26 11:41
 */
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface TableAnnotation {
    String value();
}

字段注解

package com.custom;

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

/**
 * 属性注解
 * @author LiuCheng
 * @data 2021/2/26 11:43
 */
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface FileAnnotation {
    String cloumName();
    String type();
    int length();
}

实体类中引用注解

package com.custom;

/**
 * @author LiuCheng
 * @data 2021/2/26 11:45
 */
@TableAnnotation("db_student")
public class Student {
    @FileAnnotation(cloumName = "s_id",type = "int",length = 50)
    private int id;
    @FileAnnotation(cloumName = "s_name",type = "varchar",length = 20)
    private String name;

    public Student(int id, String name) {
        this.id = id;
        this.name = name;
    }
}

测试,通过反射获取注解

 @Test
    public void handlerAnnotation() throws ClassNotFoundException, NoSuchFieldException {
        Class c1 = Class.forName("com.custom.Student");
        // 通过反射获取注解
        Annotation[] annotations = c1.getAnnotations();
        for (Annotation a : annotations ) {
            System.out.println(a);
        }
        // 获取注解的value的值
        TableAnnotation tableAnnotation = (TableAnnotation)c1.getAnnotation(TableAnnotation.class);
        String value = tableAnnotation.value();
        System.out.println(value);
        // 获取类指定的注解
        Field f = c1.getDeclaredField("name");
        FileAnnotation fAnnotation = f.getAnnotation(FileAnnotation.class);
        System.out.println(fAnnotation.cloumName());
        System.out.println(fAnnotation.type());
        System.out.println(fAnnotation.length());
    }

测试结果
在这里插入图片描述
可以看到通过反射可以获取注解的属性值,如表名,表字段,那是不是可以写sql了呢

2,自定义注解+拦截器 实现登录校验

接下来,我们使用springboot拦截器实现这样一个功能,如果方法上加了@LoginRequired,则提示用户该接口需要登录才能访问,否则不需要登录。
首先定义一个LoginRequired注解

package com.custom;

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

/**
 * @author LiuCheng
 * @data 2021/2/26 13:54
 */
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface LoginRequired {
}

然后写两个简单的接口,访问sourceA,sourceB资源

package com.controller;

import com.custom.LoginRequired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

/**
 * @author LiuCheng
 * @data 2021/2/26 10:17
 */
@RestController
public class DemoController {
    @GetMapping("/sourceA")
    public String sourceA(){
        return "resourceA";
    }
    @GetMapping("/sourceB")
    public String sourceB(){
        return "resourceB";
    }
}


没添加拦截器之前成功访问
在这里插入图片描述
实现spring的HandlerInterceptor 类先实现拦截器,但不拦截,只是简单打印日志,如下:

package com.interceptor;

import com.custom.LoginRequired;
import org.springframework.lang.Nullable;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

/**
 * @author LiuCheng
 * @data 2021/2/26 13:58
 */
public class SourceAccessInterceptor implements HandlerInterceptor {
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        System.out.println("进入了拦截器");
       
        return true;
    }

    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {

    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {

    }
}

实现spring类WebMvcConfigurer,创建配置类把拦截器添加到拦截器链中

package com.config;

import com.interceptor.SourceAccessInterceptor;
import org.springframework.context.annotation.Configuration;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

/**
 * @author LiuCheng
 * @data 2021/2/26 14:01
 */

@Component
public class InterceptorTrainConfigurer implements WebMvcConfigurer {
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new SourceAccessInterceptor()).addPathPatterns("/**");
    }
}

拦截成功如下
在这里插入图片描述

在sourceB方法上添加我们的登录注解@LoginRequired

package com.controller;

import com.custom.LoginRequired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

/**
 * @author LiuCheng
 * @data 2021/2/26 10:17
 */
@RestController
public class DemoController {
    @GetMapping("/sourceA")
    public String sourceA(){
        return "resourceA";
    }
    @GetMapping("/sourceB")
    @LoginRequired
    public String sourceB(){
        return "resourceB";
    }
}

简单实现登录拦截逻辑

 @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        System.out.println("进入了拦截器");
        HandlerMethod handlerMethod = (HandlerMethod) handler;
        LoginRequired methodAnnotation = handlerMethod.getMethodAnnotation(LoginRequired.class);
        if(methodAnnotation ==null){
            return true;
        }
        // 有loginrequire的注解时需要登录,提示用户需要登录
        response.setContentType("application/json;charset=utf-8");
        response.getWriter().print("请先登录");
        return false;
    }

运行成功,访问sourceB时需要登录了,访问sourceA则不用登录
在这里插入图片描述

在这里插入图片描述

3,自定义注解+AOP 实现日志打印

先导入切面需要的依赖包

 <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-aop</artifactId>
        </dependency>

定义一个注解@MyLog

package com.custom;

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

/**
 * @author LiuCheng
 * @data 2021/2/26 14:43
 */
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface MyLog {
}

定义一个切面类,见如下代码注释理解:

package com.aspet;

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.springframework.stereotype.Component;

/**
 * @author LiuCheng
 * @data 2021/2/26 14:44
 */
// 表名这是一个切面
@Aspect
@Component
public class MyLogAspect {
    /**
     * Pointcut表示   这是一个切点, @annotation 表示这个切点切到一个注解上,值为带改注解的的全类名
     * 切面主要的就是切点,所有通知都围绕切点发生
     * logPointCut()代表起点名称
     */
    @Pointcut("@annotation(com.custom.MyLog)")
    public void logPointCut(){

    }
    // 环绕通知
    @Around("logPointCut()")
    public void logAround(ProceedingJoinPoint joinPoint){
        // 获取方法名
        String methodName = joinPoint.getSignature().getName();
        String typeName = joinPoint.getSignature().getDeclaringTypeName();
        // 获取入参
        Object[] params = joinPoint.getArgs();
        // 拼接入参信息
        StringBuilder sb=new StringBuilder();
        for (Object obj : params ) {
            sb.append(obj+";");
        }
        String logInfo="进入===  "+methodName +"方法===,参数为: "+sb.toString();
        System.out.println(logInfo);
        // 继续执行方法
        try {
            joinPoint.proceed();
        } catch (Throwable throwable) {
            throwable.printStackTrace();
        }
        System.out.println(methodName+ "   执行完毕");
    }
}

在Controller写一个sourceC进行测试,加上我们的自定义注解:

 @GetMapping("/sourceC")
    @MyLog
    public String sourceC(String name){
        return "sourceC";
    }

启动springboot web项目,开始测试
在这里插入图片描述
日志输出
在这里插入图片描述

总结

注解使用主要还是基于和反射结合使用,后面都是扩展出来的


http://www.niftyadmin.cn/n/1466130.html

相关文章

Mysq基本语法练习以及连表查询语法

mysql练习题基础部分进阶部分sql练习基础部分 建表语句 CREATE TABLE dept (deptno int NOT NULL COMMENT 部门编号,dname varchar(14) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL COMMENT 部门名称,loc varchar(13) CHARACTER SET utf8 COLLATE utf8_general…

WIN7系统安装mysql8超详细教程

文章目录前言一、官网下载MySQL二、配置解压文件1.解压2.配置my.ini文件三、 环境变量配置四、初始化四、安装启动mysql服务其他前言 Linux版 https://blog.csdn.net/HBliucheng/article/details/113601299 最近自己在学习SpringBoot&#xff0c;需要mysql&#xff0c;以前装过…

Android Studio 每日tips:快速关闭所有标签

Android Studio 每日tips&#xff1a;快速关闭所有标签tips&#xff1a;翻译&#xff1a;生词tips&#xff1a; 翻译&#xff1a; 有两种方法关闭编辑器中所有的标签&#xff0c;当前方法除外&#xff1a; 第一&#xff0c;右键编辑器选项卡&#xff0c;在菜单中选择Close Ot…

SpringBoot 整合Shiro框架

文章目录前言一、Shiro 三大核心组件1 &#xff0c;Subject 为认证主体2&#xff0c; SecurityManager 为安全管理员3 Realm 是一个域4 其他核心组件① UserMangerPasswordToken② Subject③ AuthenticationInfo④ AuthorzationInfo⑤ DefaultWebSecurityMannager⑥ ShiroFilte…

Android Studio 每日tips:比较两个目录

Android Studio 每日tips&#xff1a;比较两个目录tips翻译tips 翻译 要比较两个目录&#xff0c;请在项目视图中选择一个或两个目录&#xff0c;然后按ctrld。

Java IO 字节流

Java IO流创建基于文件的输入输出流以字节流的形式读取文件内容以字节流的形式向文件写数据使用mkdir创建多级目录练习&#xff1a;分割文件练习&#xff1a;合并文件传送门&#xff1a;此处更详细创建基于文件的输入输出流 package JavaIOTest;import java.io.File; import j…

Java IO 关闭流的方式

Java IO 关闭流的方式分类练习&#xff1a;将分割文件中的流关闭方式改为finally形式练习&#xff1a;文件合并中的流关闭方式改为try()形式传送门&#xff1a;这里更详细分类 在try中关闭 弊端是如果文件不存在或者读取的时候有问题而抛出异常&#xff0c;那么就不会执行流的…

Java IO 字符流

Java IO 字符流使用字符流读取文件使用字符流把字符串写入文件练习&#xff1a;文件加密练习&#xff1a;解密Reader字符输入流 Writer字符输出流 专门用于字符形式的读取和写入数据 使用字符流读取文件 FileReader是Reader的子类&#xff0c;用FileReader读取文件 package J…