🍺满怀忧思,不如先干再说!
注解是一个很重要的技术,通过注解可以将一些细节代码写到第三方类中,在业务代码内不会存在业务之外的"冗余代码"
,如常用的Spring,SpringBoot,MyBatis,JDK也自带了很多主机供我们使用。但是同时它也存在很多争议
- 大量的注解不好调试代码
- 往往需要通过反射配合才能实现功能,而反射执行效率又比较低
- 有的小伙伴还认为注解往往只是关注具备什么功能,而不关注怎么实现的,知其然不知所以然不利于技术提升
总之,各有各的道理,但是请记住,一个技术不会适用于任何场景,注解的正确使用方法往往是站在一个架构的高度来审视,用途基本在于:架构师或者高级开发对系统中常用的功能进行封装,之后在开发过程中如果需要使用该功能则直接使用注解修饰即可。注解往往适用于通用而非专用的功能之上
。
这里写目录标题
注解概念
注解【Annotation】是Java5引入的新机制,也被称为元数据,在代码中添加信息提供了一种形式化的方法,可以理解为给代码打一个标记,允许我们可以在稍后的某个时刻非常方便地使用这些数据。
注解可以使用在类、方法和属性【成员变量】上,注解也是对来自像C#之类的其他语言对Java造成的语言特性压力所做出的一种回应
注解特点
Java中的注解可以使用在类、构造器、方法、成员变量、参数等位置上。和 Javadoc 不同,Java 注解可以通过反射获取注解内容。在编译器生成类文件时,注解可以存在于字节码中【注意是可以,不是一定,是可以控制的】。Java 虚拟机可以保留注解内容,在运行时可以通过反射机制获取到注解内容 。 当然它也支持自定义 Java 注解。
注解重要的是现有的Java注解和第三方注解的作用和使用方法,以及自定义注解
注解的好处:
- 可以读懂别人的代码,特别是框架的代码中使用了很多注解
- 让编程更加简洁,代码更加清晰,比如使用Spring、MyBatis等框架都要使用大量注解
- 让别人直呼内行,实现该功能只需要调用这个注解就行啦,真的很方便
注解分类
Java定义了一套注解,元注解在 java.lang.annotation 包,标准注解在 java.lang中:
- @Override:使用在方法上,判断是否是子类重写父类的方法
- @Deprecated:使用在方法或类上,标记为过时或弃用方法【类】,被该注解标识的方法【类】一般都不建议再使用,老的API中有许多过时方法
- @SuppressWarnings:使用在方法上,禁止警告注解,代码不规范或者某变量未使用,代码冗余,过长等编辑器都会警告,有些洁癖公司不允许出现警告,这时可以利用该注解清除
作用在其他注解的注解(或者说 元注解)是:自定义注解需要到这四个
- @Retention:标识这个注解怎么保存,是只在代码中,还是编入class文件中,或者是在运行时可以通过反射访问。
- @Documented:标记这些注解是否包含在用户文档中。
- @Target:标记这个注解应该是哪种 Java 成员。
- @Inherited:标记这个注解是继承于哪个注解类(默认 注解并没有继承于任何子类)
从 Java 7 开始,额外添加了 3 个注解:
- @SafeVarargs:Java 7 开始支持,忽略任何使用参数为泛型变量的方法或构造函数调用产生的警告。
- @FunctionalInterface:Java 8 开始支持,标识一个匿名函数或函数式接口。
- @Repeatable:Java 8 开始支持,标识某注解可以在同一个声明上使用多次。自定义注解需要使用到,也就是重复注解
Java内置注解
简单使用一下常见的三个标准注解:@Override、@Deprecated、@SuppressWarnings
声明父类:
public class Person {
private String name;
private Integer age;
// 说话方法
public void say() {
}
}
子类:
- 继承父类,通过 @Override 标记
- 部分技能已经生疏,可以通过 @Deprecated 标记为过时,不建议使用,可能【翻车】
- 声明 str变量但是没有使用,可以通过 @SuppressWarnings 注解,该注解有值,需要填入警告类型,unused就是未使用的意思,可以填写多个值
public class Student extends Person{
// 重写注解
@Override
public void say() {
System.out.println("我是学生");
}
// 过时注解,大学毕业之后篮球技能过时了不会了
@Deprecated
public String skill() {
return "篮球";
}
// 排除警告注解,比如声明变量没有使用,编辑器就会报黄【警告提示】,添加该注解写上 unused,就是禁止【未使用】警告
@SuppressWarnings({"unused","rawtypes"})
public void warning() {
String str;
}
}
SuppressWarnings所有值:
值 | 作用 |
---|---|
all | 抑制所有警告 |
boxing | 抑制装箱、拆箱操作时候的警告 |
cast | 抑制映射相关的警告 |
dep-ann | 抑制启用注释的警告 |
deprecation | 抑制过期方法警告 |
fallthrough | 抑制确在switch中缺失breaks的警告 |
finally | 抑制finally模块没有返回的警告 |
hiding | 抑制相对于隐藏变量的局部变量的警告 |
incomplete-switch | 忽略不完整的 switch 语句 |
nls | 忽略非nls格式的字符 |
null | 忽略对null的操作 |
rawtypes | 使用generics时忽略没有指定相应的类型 |
restriction | 抑制禁止使用劝阻或禁止引用的警告 |
serial | 忽略在serializable类中没有声明serialVersionUID变量 |
static-access | 抑制不正确的静态访问方式警告 |
synthetic-access | 抑制子类没有按最优方法访问内部类的警告 |
unchecked | 抑制没有进行类型检查操作的警告 |
unqualified-field-access | 抑制没有权限访问的域的警告 |
unused | 抑制没被使用过的代码的警告 |
小贴士:合理使用 @SuppressWarnings 注解可以方便调试和运维
自定义注解
自定义注解并可以使用自定义注解是迈向更高一层的重要表现,我们要明白为什么要自定义注解,什么时候自定义什么样的注解比较合适才是重中之重。
自定义注解的使用场景
自定义注解步骤
- 通过 @interface 关键字创建一个注解类,注解类中可以包含方法也可以不包含方法,这个方法可以认为是注解的参数
- 使用元注解对自定义注解的功能和范围进行限制
元注解:
元注解,就是定义注解的注解,也就是说这些元注解是的作用就是专门用来约束其它注解的注解。
元注解主要有五个:@Target,@Retention,@Documented,@Inherited,@Repeatable,其中 @Repeatable 是Java8新增的可重复注解。
@Target:用于指定注解的使用范围,通过 ElemenetType 枚举值决定
- ElementType.TYPE:类、接口、注解、枚举
- ElementType.FIELD:字段、枚举常量
- ElementType.METHOD:方法
- ElementType.PARAMETER:形式参数
- ElementType.CONSTRUCTOR:构造方法
- ElementType.LOCAL_VARIABLE:局部变量
- ElementType.ANNOTATION_TYPE:注解
- ElementType.PACKAGE:包
- ElementType.TYPE_PARAMETER:类型参数
- ElementType.TYPE_USE:类型使用
@Retention:用于指定注解的保留策略,通过 RetentionPolicy 枚举值决定
- RetentionPolicy.SOURCE:注解只保留在源码中,在编译时会被编译器丢弃
- RetentionPolicy.CLASS:(默认的保留策略) 注解会被保留在Class文件中,但不会被加载到虚拟机中,运行时无法获得
- RetentionPolicy.RUNTIME:注解会被保留在Class文件中,且会被加载到虚拟机中,可以在运行时通过反射获得
@Documented:用于将注解包含在javadoc中,默认情况下,javadoc是不包括注解的,但如果使用了@Documented注解,则相关注解类型信息会被包含在生成的文档中,该注解没有参数
@Inherited: 允许子类继承父类中的注解
@Repeatable:Java8中引入的元注解,用于声明标记的注解为可重复类型注解,可以在同一个地方多次使用
发现所有的元注解上的@Target注解值为 ElementType.ANNOTATION_TYPE,意为该注解的作用范围在注解上
案例:
需求:自定义一个名为 MyClassAnnonation 类级别的无参注解,定义一个使用在属性上名为 Name 的带属性注解
代码实现:
MyClassAnnonation:
package com.stt.annontation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
// ElementType.TYPE:这个注解可以使用在 类、接口、注解、枚举上
@Target(ElementType.TYPE)
// RetentionPolicy.RUNTIME:注解被保留在Class文件中,也会被加载到JVM中
@Retention(RetentionPolicy.RUNTIME)
public @interface MyClassAnnonation {
}
Name:
package com.stt.annontation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Target;
// 可以使用在字段上
@Target(ElementType.FIELD)
public @interface Name {
// value带小括号,是一个方法,有些文章写value是参数,我感觉是不准确的
String value();
// 因为注解中可以写成员变量【属性】
String job = "程序猿";
}
Student类测试自定义注解:
package com.stt.annontation;
// 使用类注解
@MyClassAnnonation
public class Student {
// 使用Name注解
@Name(value = "添甄")
private String name;
public String getName() {
return name;
}
}
错误应用1:比如@Name注解我们限制只能使用在字段上,应用在方法上就会报错
错误应用2:比如@Name注解需要传参数,未传参数也会报错
深入一下:
这里介绍注解的三个小知识点:
- 巧用value方法
- 默认值
- 基本数据类型不能使用包装类
value属性:当注解中有value方法时,使用注解赋值时,value方法可以不写,直接写具体的值即可
不禁习惯性的思考,是不是只有一个方法时就可以省略属性名直接填值,所以我们将value属性名改成name试一下:
发现提示找不到value方法,说明默认会根据value进行匹配,这里说巧用value方法
默认值:我们可以通过default给注解参数默认值,如给value默认值为添甄:
建议在自定义注解时,合理分配默认值,spring、MyBatis等框架的注解每个属性都设置了默认值
基本数据类型不能使用包装类:比如声明一个年龄,Integer无效,只能使用int
以上就是自定义注解时的三个小知识点,大家知道,使用时避免踩坑。说到这不禁觉得我是真滴细!
注解应用
案例1:实现Junit的单元测试
需求:通过注解实现类似于Junit框架的@Test注解类似的功能,在程序运行时有@MyTest的注解的方法都执行
分析:
- 创建名为MyTest的注解,限制使用在方法上
- 因为程序运行时要检测哪些方法上有@MyTest注解,所以该注解的保留机制应该是运行时期仍然保存
- 运行时通过反射扫描哪些方法上包含@MyTest注解,获取到之后逐一执行
代码实现:
注解:
package com.stt.annontation;
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)
public @interface MyTest {
}
方法标记:
package com.stt.annontation;
import java.lang.reflect.Method;
public class MyTestMain {
@MyTest
public void test1() {
System.out.println("==========test1==========");
}
@MyTest
public void test2() {
System.out.println("==========test2==========");
}
@MyTest
public void test3() {
System.out.println("==========test3==========");
}
public void test4() {
System.out.println("==========test4==========");
}
public static void main(String[] args) {
// 1、创建测试类对象,通过反射运行方法时需要使用
MyTestMain testMain = new MyTestMain();
// 2、获取类对象
Class<MyTestMain> clazz = MyTestMain.class;
// 3、获取所有方法
Method[] methods = clazz.getMethods();
// 4、遍历所有的方法
for (Method method : methods) {
// 5、判断哪些方法上有 @MyTest 注解
if(method.isAnnotationPresent(MyTest.class)) {
// 6、触发执行,指明通过(1)创建的对象调用
try {
method.invoke(testMain);
} catch (Exception e) {
System.out.println("运行出错!");
}
}
}
}
}
运行结果:
test4方法没有添加注解,所以就没有运行,发现运行顺序1、3、2,其实这个运行顺序是随机的,如果想要控制执行顺序,可以在注解中添加一个order值来实现,如下:
修改注解:添加order方法,值为int类型,默认值为0
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface MyTest {
int order() default 0;
}
修改测试:在使用注解时添加顺序,在使用时获取到该注解,通过Stream根据order值排序之后再注意调用执行,就可以控制顺序
public class MyTestMain {
@MyTest(order = 1)
public void test1() {
System.out.println("==========test1==========");
}
@MyTest(order = 2)
public void test2() {
System.out.println("==========test2==========");
}
@MyTest(order = 3)
public void test3() {
System.out.println("==========test3==========");
}
public void test4() {
System.out.println("==========test4==========");
}
public static void main(String[] args) {
// 1、创建测试类对象,通过反射运行方法时需要使用
MyTestMain testMain = new MyTestMain();
// 2、获取类对象
Class<MyTestMain> clazz = MyTestMain.class;
// 3、获取所有方法
Method[] methods = clazz.getMethods();
List<Method> invokeMethods = new ArrayList<>();
// 4、遍历所有的方法
for (Method method : methods) {
// 5、判断哪些方法上有 @MyTest 注解
if(method.isAnnotationPresent(MyTest.class)) {
// 将方法添加到集合中
invokeMethods.add(method);
}
}
// 6、根据order值排序
List<Method> methodList = invokeMethods.stream()
.sorted(((o1, o2) -> o1.getAnnotation(MyTest.class).order() - o2.getAnnotation(MyTest.class).order()))
.collect(Collectors.toList());
// 7、根据排序后的方法执行,结果永远是1,2,3
methodList.forEach(method -> {
try {
method.invoke(testMain);
} catch (Exception e) {
System.out.println("运行错误!");
}
});
}
}
运行结果:
这样是不就非常哇塞!
Spring中的类似注解:
@Order:就可以控制配置类的加载顺序
@DependsOn:指定当前bean所依赖的beans。任何被指定依赖的bean都由Spring保证在当前bean之前创建
案例2:实现日志记录
需求:在SpringBoot程序中,通过自定义注解 + aop实现日志记录功能,在请求接口时,自动将操作日志记录到数据库
分析:
- 自定义Log注解,使用在类和方法上,并在运行时可以获取到
- 通过aop配置获取哪些方法上定义了Log注解,并获取到相关信息
- 将信息整合到日志实体类中,存储到MySQL
- 为方便操作数据库和实体类,引入mybatis-plus和lombok
- 贴出核心代码,springboot配置文件和service、mapper就不贴出了
依赖:
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>2.7.5</version>
</dependency>
<!-- SpringBoot 拦截器 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
<version>2.7.5</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.24</version>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.5.2</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.7.36</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.31</version>
</dependency>
<dependency>
<groupId>com.alibaba.fastjson2</groupId>
<artifactId>fastjson2</artifactId>
<version>2.0.19</version>
</dependency>
</dependencies>
数据库:
CREATE TABLE `sys_log` (
`oper_id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '日志主键',
`title` varchar(50) DEFAULT '' COMMENT '模块标题',
`business_type` int(11) DEFAULT '0' COMMENT '业务类型(0其它 1新增 2修改 3删除)',
`method` varchar(100) DEFAULT '' COMMENT '方法名称',
`request_method` varchar(10) DEFAULT '' COMMENT '请求方式',
`operator_type` int(11) DEFAULT '0' COMMENT '操作类别( 0、用户端 1、平台管理端)',
`oper_name` varchar(50) DEFAULT '' COMMENT '操作人员',
`oper_url` varchar(255) DEFAULT '' COMMENT '请求URL',
`oper_ip` varchar(128) DEFAULT '' COMMENT '主机地址',
`oper_location` varchar(255) DEFAULT '' COMMENT '操作地点',
`oper_param` varchar(2000) DEFAULT '' COMMENT '请求参数',
`json_result` varchar(2000) DEFAULT '' COMMENT '返回参数',
`status` int(11) DEFAULT '0' COMMENT '操作状态(1正常 0异常)',
`error_msg` varchar(2000) DEFAULT '' COMMENT '错误消息',
`oper_time` datetime DEFAULT NULL COMMENT '操作时间',
PRIMARY KEY (`oper_id`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8 ROW_FORMAT=COMPACT COMMENT='操作日志记录';
实体类:
package com.stt.entity;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import lombok.Data;
import java.time.LocalDateTime;
@Data
public class SysLog {
@TableId(value = "oper_id", type = IdType.AUTO)
private Long operId;
private String title;
private Integer businessType;
private String method;
private String requestMethod;
private String operName;
private String operUrl;
private String operIp;
private String operLocation;
private String operParam;
private String jsonResult;
private Integer status;
private String errorMsg;
private LocalDateTime operTime;
}
自定义注解:
package com.stt.annontation;
import java.lang.annotation.*;
@Target({ElementType.METHOD,ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Log {
/**
* 日志标题
*/
public String title() default "";
/**
* 操作类型
*/
public BusinessTypeEnum businessType() default BusinessTypeEnum.OTHER;
}
BusinessTypeEnum枚举:记录什么类型的操作
package com.stt.annontation;
public enum BusinessTypeEnum {
/**
* 其它
*/
OTHER,
/**
* 新增
*/
INSERT,
/**
* 修改
*/
UPDATE,
/**
* 删除
*/
DELETE,
/**
* 授权
*/
GRANT,
}
AOP配置:
package com.stt.annontation;
import com.alibaba.fastjson2.JSON;
import com.stt.entity.SysLog;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.servlet.HandlerMapping;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.time.LocalDateTime;
import java.util.Collection;
import java.util.Map;
@Component
@Aspect
public class LogConfig {
private static final Logger log = LoggerFactory.getLogger(LogConfig.class);
// 引入日志Service,用于存储数据进数据库
@Autowired
private ISysLogService sysLogService;
// 配置织入点-xxx代表自定义注解的存放位置,如:com.stt.annontation.Log
@Pointcut("@annotation(com.stt.annontation.Log)")
public void logPointCut() {}
/**
* 处理完请求后执行此处代码
*
* @param joinPoint 切点
*/
@AfterReturning(pointcut = "@annotation(controllerLog)", returning = "jsonResult")
public void doAfterReturning(JoinPoint joinPoint, Log controllerLog, Object jsonResult)
{
handleLog(joinPoint, controllerLog, null, jsonResult);
}
/**
* 如果处理请求时出现异常,在抛出异常后执行此处代码
*
* @param joinPoint 切点
* @param e 异常
*/
@AfterThrowing(value = "@annotation(controllerLog)", throwing = "e")
public void doAfterThrowing(JoinPoint joinPoint, Log controllerLog, Exception e)
{
handleLog(joinPoint, controllerLog, e, null);
}
/**
* 日志处理
*/
protected void handleLog(final JoinPoint joinPoint, Log controllerLog, final Exception e, Object jsonResult)
{
try {
MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
// 获取当前的用户
String userName = "添甄";
// *========数据库日志=========*//
SysLog sysLog = new SysLog();
sysLog.setStatus(1);
// 请求的地址
ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
assert requestAttributes != null;
HttpServletRequest request = requestAttributes.getRequest();
String ip = getIpAddr(request);
sysLog.setOperIp(ip);
sysLog.setOperUrl(request.getRequestURI());
sysLog.setOperName(userName);
if (e != null) {
sysLog.setStatus(0);
sysLog.setErrorMsg(e.getMessage().substring(0,2000));
}
// 设置方法名称
String className = joinPoint.getTarget().getClass().getName();
String methodName = joinPoint.getSignature().getName();
sysLog.setMethod(className + "." + methodName + "()");
// 设置请求方式
sysLog.setRequestMethod(request.getMethod());
// 处理设置注解上的参数
getControllerMethodDescription(joinPoint, controllerLog, sysLog, jsonResult, request);
// 保存数据库
sysLog.setOperTime(LocalDateTime.now());
// 将处理好的日至对象存储进数据库
sysLogService.save(sysLog);
} catch (Exception exp) {
// 记录本地异常日志
log.error("==前置通知异常==");
log.error("异常信息:{}", exp.getMessage());
exp.printStackTrace();
}
}
// 获取操作ip地址
public static String getIpAddr(HttpServletRequest request) {
if (request == null) {
return "unknown";
}
String ip = request.getHeader("x-forwarded-for");
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("Proxy-Client-IP");
}
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("X-Forwarded-For");
}
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("WL-Proxy-Client-IP");
}
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("X-Real-IP");
}
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getRemoteAddr();
}
return ip;
}
// 获取注解信息
public void getControllerMethodDescription(JoinPoint joinPoint, Log log, SysLog sysLog, Object jsonResult, HttpServletRequest request) throws Exception {
// 设置action动作
sysLog.setBusinessType(log.businessType().ordinal());
sysLog.setTitle(log.title());
}
private void setRequestValue(JoinPoint joinPoint, SysLog sysLog, HttpServletRequest request) throws Exception {
String requestMethod = sysLog.getRequestMethod();
if (RequestMethod.PUT.name().equals(requestMethod) || RequestMethod.POST.name().equals(requestMethod)) {
String params = argsArrayToString(joinPoint.getArgs());
sysLog.setOperParam(params.substring(0,2000));
} else {
Map<?, ?> paramsMap = (Map<?, ?>) request.getAttribute(HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE);
sysLog.setOperParam(paramsMap.toString().substring(0,2000));
}
}
// 解析方法参数信息
private String argsArrayToString(Object[] paramsArray) {
StringBuilder params = new StringBuilder();
if (paramsArray != null && paramsArray.length > 0) {
for (Object o : paramsArray) {
if (o != null && !isFilterObject(o)) {
try {
Object jsonObj = JSON.toJSON(o);
params.append(jsonObj.toString()).append(" ");
} catch (Exception e) {
log.error(e.getMessage());
}
}
}
}
return params.toString().trim();
}
@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 (Object value : collection) {
return value instanceof MultipartFile;
}
} else if (Map.class.isAssignableFrom(clazz)) {
Map map = (Map) o;
for (Object value : map.entrySet()) {
Map.Entry entry = (Map.Entry) value;
return entry.getValue() instanceof MultipartFile;
}
}
return o instanceof MultipartFile || o instanceof HttpServletRequest || o instanceof HttpServletResponse
|| o instanceof BindingResult;
}
}
Controller:在需要记录日志的请求方法上添加Log注解
package com.stt.controller;
import com.stt.annontation.BusinessTypeEnum;
import com.stt.annontation.Log;
import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping("test")
public class TestController {
@Log(title = "查询列表",businessType = BusinessTypeEnum.OTHER)
@GetMapping("list")
public String list() {
return "返回数据列表";
}
@Log(title = "添加数据",businessType = BusinessTypeEnum.INSERT)
@PostMapping
public String save() {
return "数据添加成功";
}
@Log(title = "修改数据",businessType = BusinessTypeEnum.UPDATE)
@PutMapping
public String update() {
return "修改数据成功";
}
@Log(title = "删除数据",businessType = BusinessTypeEnum.DELETE)
@DeleteMapping
public String delete() {
return "删除数据成功";
}
}
启动项目之后请求接口效果如下:
请求对应接口,就会添加一条日志进库,这就是通过自定义注解+aop实现的,把这个注解给公司小伙伴使用都直呼哇塞呢!
重复注解
自JDK5以来注解开始变得越来越流行,在各个框架中广泛应用,在上边我们也通过两个案例实现了自定义注解,并投入使用,不过注解有一个很大的限制是:在同一个地方不能多次使用同一个注解。比如:
我有两组过滤方式,一个方法上不能写两个相同的注解
就有老程序员绕开这个机制,写一个大注解,里边包含一个小注解数组实现,如下
JDK 8引入了重复注解的概念,允许在同一个地方多次使用同一个注解。在JDK 8中使用@Repeatable注解定义重复注解
所以发现,其实和原本解决可重复注解的原理基本一致,将多个注解放到一个容器中,只不过Java8的这种方式将放入容器的动作隐藏起来了,编码使用注解时更方便,更直观,当我们通过反射获取注解时,发现获取到的其实是MyFilters注解
总结:
- 如果需要两个相同的注解,有两种方式
- 使用Java8之前的写法,声明一个大注解,包含需要重复的注解数组
- 使用Java8之后的写法,仍然需要声明一个大注解,不过通过 @Repeatable 注解在需要重复的注解声明上指定大注解
- 两者原理相同,通过反射获取的注解类型都是大注解类型
类型注解
类型注解在开发时使用较少,从Java 8开始,注解已经能应用于任何类型。这其中包括new操作符、类型转换、instanceof检查、泛型类型参数,以及implements和throws子句。这里,我们举了一个例子,这个例子中类型为String的变量name不能为空,所以我们使用了@NonNull对其进行注解
@NotNull String name = "石添的编程哲学";
我们也可以在集合的泛型上使用类型注解
List<@NotNull String> names = new ArrayList();
利用好对类型的注解非常有利于我们对程序进行分析。这两个例子中,通过这一工具我们可以确保getName不返回空,names列表中的元素总是非空值。这会极大地帮助你减少代码中不期而至的错误,不过用的比较少,增加了代码量你懂的
总结
- 注解如同标记,为类,方法,变量,参数打上不同的标记会对应添加上不同的功能或约束
- 注解的创建就是
@interface
,比接口多个个@符号 - 注解主要给编译器及工具类型的软件用,在运行时class文件中不存在注解
- 程序运行时需要依靠反射识别注解,由于反射比较慢,所以注解使用时也需要谨慎计较时间成本。涉及到算法的代码不建议使用注解