网站首页 > 技术文章 正文
1.注解介绍
1.1注解的本质
Oracle官方对注解的定义为:
Annotations, a form of metadata, provide data about a program that is not part of the program itself. Annotations have no direct effect on the operation of the code they annotate.
注解是元数据的一种形式,它提供有关程序的数据,该数据不属于程序本身。 注解对其注释的代码操作没有直接影响。
而在 JDK 的Annotation接口中有一行注释如此写到:
/**
* The common interface extended by all annotation types.
* ...
*/
public interface Annotation {...}
这说明其他注解都扩展自 Annotation 这个接口,也就是说注解的本质就是一个接口。
以 Spring Boot 中的一个注解为例:
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Indexed
public @interface Component {
String value() default "";
}
它实际上相当于:
public interface Component extends Annotation{...}
而 @interface 可以看成是一个语法糖。
1.2注解的要素
依然来看 @Component 这个例子:
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Indexed
public @interface Component {
String value() default "";
}
在注解定义上有几个注解 @Target, @Retention, @Documented ,被称为 元注解 。
所谓元注解就是说明注解的注解
Java 中的元注解共有以下几个:
1.2.1 @Target
@Target 顾名思义,这个注解标识了被修饰注解的作用对象。我们看看它的源码:
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Target {
/**
* Returns an array of the kinds of elements an annotation type
* can be applied to.
* @return an array of the kinds of elements an annotation type
* can be applied to
*/
ElementType[] value();
}
可以看到,这个注解的 value 值是一个数组,这也就意味着注解的作用对象可以有多个。 其取值范围都在 ElementType 这个枚举之中:
public enum ElementType {
/** 类、接口、枚举定义 */
TYPE,
/** 字段,包括枚举值 */
FIELD,
/** 方法 */
METHOD,
/** 参数 */
PARAMETER,
/** 构造方法 */
CONSTRUCTOR,
/** 局部变量 */
LOCAL_VARIABLE,
/** 元注解 */
ANNOTATION_TYPE,
/** 包定义 */
PACKAGE...
}
不同的值代表被注解可修饰的范围,例如 TYPE 只能修饰类、接口和枚举定义。这其中有个很特殊的值叫做 ANNOTATION_TYPE , 是专门表示元注解的。
在回过头来看 @Component 这个例子, Target 取值为 TYPE 。熟悉 Spring Boot 的同学也一定知道, @Component 确实是不能放到方法或者属性前面的。
1.2.2@Retention
@Retention 注解指定了被修饰的注解的生命周期。定义如下:
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Retention {
/**
* Returns the retention policy.
* @return the retention policy
*/
RetentionPolicy value();
}
可以看到这个注解带一个 RetentionPolicy 的枚举值:
public enum RetentionPolicy {
SOURCE,
CLASS,
RUNTIME
}
SOURCE
CLASS
RUNTIME
1.2.3 @Documented
这个注解比较简单,表示是否添加到 java doc 中。
1.2.4 @Inherited
这个也比较简单,表示注解是否被继承。这个注解不是很常用。
注意:元注解只在定义注解时被使用!
1.3 注解的构成
从上面的元注解可以了解到,一个注解可以关联多个 ElementType ,但只能有一个 RetentionPolicy :
Java 中有三个常用的内置注解,其实相信大家都用过或者见过。不过在了解了注解的真实面貌以后,不妨重新认识一下吧!
1.4 Java内置注解
1.4.1 @Override
@Override 它的定义为:
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.SOURCE)
public @interface Override {
}
可见这个注解没有任何取值,只能修饰方法,而且RetentionPolicy 为 SOURCE,说明这是一个仅在编译阶段起作用的注解。
它的真实作用想必大家一定知道,就是在编译阶段,如果一个类的方法被 @Override 修饰,编译器会在其父类中查找是否有同签名函数,如果没有则编译报错。可见这确实是一个除了在编译阶段就没什么用的注解。
1.4.2 @Deprecated
@Deprecated 它的定义为:
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(value={CONSTRUCTOR, FIELD, LOCAL_VARIABLE, METHOD, PACKAGE, PARAMETER, TYPE})
public @interface Deprecated {
}
这个注解也没有任何取值,能修饰所有的类型,永久存在。这个注解的作用是,告诉使用者被修饰的代码不推荐使用了,可能会在下一个软件版本中移除。这个注解仅仅起到一个通知机制,如果代码调用了被@Deprecated 修饰的代码,编译器在编译时输出一个编译告警。
1.4.3 @SuppressWarnings
@SuppressWarnings 它的定义为:
@Target({TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE})
@Retention(RetentionPolicy.SOURCE)
public @interface SuppressWarnings {
/**
* The set of warnings that are to be suppressed by the compiler in the
* annotated element. Duplicate names are permitted. The second and
* successive occurrences of a name are ignored. The presence of
* unrecognized warning names is <i>not</i> an error: Compilers must
* ignore any warning names they do not recognize. They are, however,
* free to emit a warning if an annotation contains an unrecognized
* warning name.
*
* <p> The string {@code "unchecked"} is used to suppress
* unchecked warnings. Compiler vendors should document the
* additional warning names they support in conjunction with this
* annotation type. They are encouraged to cooperate to ensure
* that the same names work across multiple compilers.
* @return the set of warnings to be suppressed
*/
String[] value();
}
这个注解有一个字符串数组的值,需要我们使用注解的时候传递。可以在类型、属性、方法、参数、构造函数和局部变量前使用,声明周期是编译期。
这个注解的主要作用是压制编译告警的。
2.AOP介绍(AspectJ暂不讨论)
2.1 Spring AOP基本概念
- 是一种动态编译期增强性AOP的实现
- 与IOC进行整合,不是全面的切面框架
- 与动态代理相辅相成
- 有两种实现:基于jdk动态代理、cglib
2.2 Spring AOP与AspectJ区别
- Spring的AOP是基于动态代理的,动态增强目标对象,而AspectJ是静态编译时增强,需要使用自己的编译器来编译,还需要织入器
- 使用AspectJ编写的java代码无法直接使用javac编译,必须使用AspectJ增强的ajc增强编译器才可以通过编译,写法不符合原生Java的语法;而Spring AOP是符合Java语法的,也不需要指定编译器去编译,一切都由Spring 处理。
2.3 使用步骤
- 定义业务组件
- 定义切点(重点)
- 定义增强处理方法(切面方法)
这边用下面例子的AOP类来进行说明 (基于Spring AOP的)
/**
* @Author Song
* @Date 2020/5/26 9:50
* @Version 1.0
*/
@Slf4j
@Aspect
@Component
public class EagleEyeAspect {
@Pointcut("@annotation(com.ctgu.song.plantfactory.v2.annotation.EagleEye)")
public void eagleEye() {
}
@Around("eagleEye()")
public Object around(ProceedingJoinPoint pjp) throws Throwable {
long begin = System.currentTimeMillis();
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
HttpServletRequest request = attributes.getRequest();
Signature signature = pjp.getSignature();
MethodSignature methodSignature = (MethodSignature) signature;
Method method = methodSignature.getMethod();
EagleEye eagleEye = method.getAnnotation(EagleEye.class);
String desc = eagleEye.desc();
log.info("============请求开始==========");
log.info("请求链接:{}", request.getRequestURI().toString());
log.info("接口描述:{}", desc);
log.info("请求类型:{}", request.getMethod());
log.info("请求方法:{}.{}", signature.getDeclaringTypeName(), signature.getName());
log.info("请求IP:{}", request.getRemoteAddr());
log.info("请求入参:{}", JSON.toJSONString(pjp.getArgs()));
Object result = pjp.proceed();
long end = System.currentTimeMillis();
log.info("请求耗时:{}ms", end - begin);
log.info("请求返回:{}", JSON.toJSONString(result));
log.info("=============请求结束===========");
return result;
}
}
这边先不看代码的具体内容,先简单介绍一下用到AOP中常用的注解
- @Aspect : 指定切面类;
- @Pointcut :公共切入点表达式
- 通知方法前置通知(@Before) 目标方法执行之前,执行注解的内容后置通知(@After)目标方法执行之后,执行注解的内容返回通知 (@AfterReturning)目标方法返回后,执行注解的内容异常通知 (@AfterThrowing)目标方法抛出异常后,执行注解的内容环绕通知 (@Around)目标方法执行前后,分别执行一些代码
注意 定义好切片类后要将其加入Spring容器内才能使用哦 (可以使用@Component注解)
3. 具体实现(一个例子)
1.首先定义一个注解,代码如下
/**
* @Author Song
* @Date 2020/5/26 9:44
* @Version 1.0
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@Documented
public @interface EagleEye {
/**
* @Retention(RetentionPolicy.RUNTIME)
* 定义了注解的生命周期为运行时
* <p>
* @Target(ElementType.METHOD)
* 定义了注解的作用域为方法
* <p>
* Documented
* 标识该注解可以被JavaDoc记录
* <p>
* 定义注解名称为EagleEye(鹰眼,哈哈~~)
* <p>
* 定义一个元素desc,用来描述被修饰的方法
* <p>
* 接口描述
*
* @return
*/
String desc() default "";
}
2.定义切片内并写好自己想要增强的方法
直接贴代码了~~
/**
* @Author Song
* @Date 2020/5/26 9:50
* @Version 1.0
*/
@Slf4j
@Aspect
@Component
public class EagleEyeAspect {
@Pointcut("@annotation(com.ctgu.song.plantfactory.v2.annotation.EagleEye)")
public void eagleEye() {
}
@Around("eagleEye()")
public Object around(ProceedingJoinPoint pjp) throws Throwable {
long begin = System.currentTimeMillis();
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
HttpServletRequest request = attributes.getRequest();
Signature signature = pjp.getSignature();
MethodSignature methodSignature = (MethodSignature) signature;
Method method = methodSignature.getMethod();
EagleEye eagleEye = method.getAnnotation(EagleEye.class);
String desc = eagleEye.desc();
log.info("============请求开始==========");
log.info("请求链接:{}", request.getRequestURI().toString());
log.info("接口描述:{}", desc);
log.info("请求类型:{}", request.getMethod());
log.info("请求方法:{}.{}", signature.getDeclaringTypeName(), signature.getName());
log.info("请求IP:{}", request.getRemoteAddr());
log.info("请求入参:{}", JSON.toJSONString(pjp.getArgs()));
Object result = pjp.proceed();
long end = System.currentTimeMillis();
log.info("请求耗时:{}ms", end - begin);
log.info("请求返回:{}", JSON.toJSONString(result));
log.info("=============请求结束===========");
return result;
}
}
在@Pointcut里通过@annotation来配置切点,代表我们的AOP切面会切到所有用EagleEye注解修饰的类。
然后使用@Around环绕通知在被注解的方法前后执行一些代码
Object result = pjp.proceed();
这行代码之前就是 执行目标方法之前需要执行的代码 ,这行代码之后就是 执行目标方法之后需要执行的代码
3. 注解的使用
只需要在需要被注解的方法上面使用自己的注解就行了 这里拿我自己项目中的一个Controller中的方法举例
@EagleEye(desc = "分页查询实验")
@GetMapping("/experiment")
@ApiOperation("分页查询实验")
public RsBody<Page<ExperimentVO2>> pageExperiment(ExperimentQueryDTO queryDTO) {
log.info("请求分页查询实验的方法pageExperiment,请求参数为{}", queryDTO.toString());
RsBody<Page<ExperimentVO2>> rsBody = new RsBody<>();
IPage<Experiment> page = experimentV2Service.page(new Page<>(queryDTO.getCurrent() - 1, queryDTO.getSize()), new LambdaQueryWrapper<Experiment>()
.like(queryDTO.getExperimentId() != null, Experiment::getExperimentId, queryDTO.getExperimentId())
.eq(queryDTO.getExperimentStatus() != null, Experiment::getExperimentStatus, queryDTO.getExperimentStatus())
.between(queryDTO.getStartTime() != null && queryDTO.getEndTime() != null, Experiment::getStartTime, queryDTO.getStartTime(), queryDTO.getEndTime())
.orderBy(true, false, Experiment::getExperimentId));
//组装Vo
List<ExperimentVO2> experimentVOList = new ArrayList<>();
for (Experiment experiment : page.getRecords()) {
ExperimentVO2 experimentVO = new ExperimentVO2();
experimentVO.setExperimentId(experiment.getExperimentId());
PlantInfo byPlantId = plantService.findByPlantId(experiment.getPlantId());
if (byPlantId != null) {
experimentVO.setPlantName(byPlantId.getPlantName());
} else {
experimentVO.setPlantName("植物被删除");
}
experimentVO.setStartTime(experiment.getStartTime());
experimentVO.setEndTime(experiment.getEndTime());
experimentVO.setExperimentPurpose(experiment.getExperimentPurpose());
experimentVO.setExperimentDescription(experiment.getExperimentDescription());
experimentVO.setExperimentAddress(experiment.getExperimentAddress());
experimentVO.setExperimentPersonName(userService.findById(experiment.getExperimentPersonId()).getUserName());
experimentVO.setCronType(experiment.getCronType());
experimentVO.setExperimentStatus(experiment.getExperimentStatus());
experimentVO.setExperimentResult(experiment.getExperimentResult());
experimentVOList.add(experimentVO);
}
Page<ExperimentVO2> pageVo = new Page<ExperimentVO2>();
pageVo.setPages(page.getPages());
pageVo.setRecords(experimentVOList);
pageVo.setTotal(page.getTotal());
pageVo.setSize(page.getSize());
pageVo.setCurrent(page.getCurrent());
return rsBody.setBody(true).setData(pageVo);
}
4.测试情况
好的 万事俱备 让我们运行一下程序 并访问这个方法 (过程略过)
很有意思吧~~
原文链接:http://www.cnblogs.com/songsongsun/p/14517977.html
猜你喜欢
- 2025-07-27 JPA实体类注解,看这篇就全会了(java实体类注解)
- 2025-07-27 Java反射机制最全详解(图文全面总结)
- 2025-07-27 javaEE 新闻管理系统 oracle11+tomcat6
- 2025-07-27 SpringBoot 注解最全详解,建议收藏!
- 2024-10-28 从源码里的一个注释,我追溯到了12年前,有点意思
- 2024-10-28 Oracle数据库下使用PL/SQL编程 oracle数据库中,如何在sqlplus中执行sql脚本
- 2024-10-28 Spring注解驱动之后再说事务啊 spring事物注解失效
- 2024-10-28 让清华毕业大牛带你:深入了解Java中的注解,你能吸收到多少呢?
- 2024-10-28 sql注入fuzz bypass waf SQL注入对于所有数据库的注入方法是一样的
- 2024-10-28 Java 深度历险:Java 注解 java注解是什么意思
你 发表评论:
欢迎- 633℃几个Oracle空值处理函数 oracle处理null值的函数
- 626℃Oracle分析函数之Lag和Lead()使用
- 614℃0497-如何将Kerberos的CDH6.1从Oracle JDK 1.8迁移至OpenJDK 1.8
- 609℃Oracle数据库的单、多行函数 oracle执行多个sql语句
- 606℃Oracle 12c PDB迁移(一) oracle迁移到oceanbase
- 599℃【数据统计分析】详解Oracle分组函数之CUBE
- 588℃最佳实践 | 提效 47 倍,制造业生产 Oracle 迁移替换
- 574℃Oracle有哪些常见的函数? oracle中常用的函数
- 最近发表
-
- CVE-2025-30762|Oracle(java oracle)
- 低代码可能铲不掉“屎山”,但能让这个它更有「型」
- 科技大事件:新苹果手表可通过击掌或握手来传递信息
- 你的百万级上下文窗口大模型,可能并没有你想象中那么强
- DApp 开发中的安全测试(软件测试过程中安全测试的具体应用场景和测试思路)
- 盘点Java中最没用的知识⑧:这3个过时套路,你还在代码里硬撑?
- 机房硬件设备及Oracle数据库软件维护服务项目竞争性磋商公告
- 微软与甲骨文扩大合作关系,推出Oracle Database@Azure
- JPA实体类注解,看这篇就全会了(java实体类注解)
- Java反射机制最全详解(图文全面总结)
- 标签列表
-
- 前端设计模式 (75)
- 前端性能优化 (51)
- 前端模板 (66)
- 前端跨域 (52)
- 前端缓存 (63)
- 前端aes加密 (58)
- 前端脚手架 (56)
- 前端md5加密 (54)
- 前端路由 (61)
- 前端数组 (73)
- 前端js面试题 (50)
- 前端定时器 (59)
- 前端获取当前时间 (50)
- Oracle RAC (76)
- oracle恢复 (77)
- oracle 删除表 (52)
- oracle 用户名 (80)
- oracle 工具 (55)
- oracle 内存 (55)
- oracle 导出表 (62)
- oracle约束 (54)
- oracle 中文 (51)
- oracle链接 (54)
- oracle的函数 (58)
- 前端调试 (52)
本文暂时没有评论,来添加一个吧(●'◡'●)