什么是注解
Java的注解(Annotation)是一种元数据,它可以提供程序的额外信息,帮助程序员更好地管理程序。注解通常被用作代码的标记或者指定某些行为的方式。在Java中,注解以@符号开头,放在代码的各个位置,包括类、方法、成员变量、参数等地方。注解可以通过反射机制在程序运行时获取到,并能对程序的执行产生一定的影响。Java提供了一些系统注解,例如@Override、@Deprecated等等,同时也支持自定义注解。
注解和注释有什么区别
注解和注释看似相似,但它们在意义和使用方式上有很大的不同。
注释(Comment)是程序员在代码中添加的一些说明信息,它们并不会对程序的运行产生任何影响,仅仅是为了方便程序员对代码的理解和维护。注释可以是单行或多行,以 // 或 /.../ 形式添加。
注解(Annotation)则是一种用来标记程序元素和提供编译器和框架额外信息的元数据。注解以 @ 符号开头,可以加在类、方法、变量等各种程序元素上,它们能够为程序的开发、维护、测试和部署等各个环节提供很多便利。注解的作用不限于提供说明信息,它还可以通过反射机制在程序运行时动态地检查和操作程序元素。
因此,注解和注释的主要区别在于:注释只是为了代码的可读性和辅助程序员理解,没有实际的功能;而注解则具有明确的语义和作用,并能够为程序的开发和维护提供各种服务。
自定义注解怎么用
自定义注解需要使用@interface定义,其内容可以由编程人员自行定义。下面是一个简单的自定义注解的例子:
@Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface Log { String value() default ""; }
该注解被定义为@Log,可以用于方法上,它有一个可选的value属性供注解使用者添加一些描述信息。
注意,以上代码是不能直接运行的,因为还涉及到aop切面和反射的知识,完整示例程序看文末。
自定义注解在实际开发中可以有很多用途,下面举几个例子:
@ParamName注解
在Java中,方法的参数没有名称,只能通过索引来访问。有时候代码可读性会因为这个问题而受到影响,因此我们可以用自定义注解来为方法的参数添加名称:
@Retention(RetentionPolicy.RUNTIME) @Target(ElementType.PARAMETER) public @interface ParamName { String value(); }
使用方式:
public void foo(@ParamName("param1") int param1, @ParamName("param2") double param2) { // do something }
这样,在运行时我们就可以通过反射机制获取到每个参数的名称。
@Cacheable注解
在实际开发中,我们往往需要对一些计算量较大或者耗时较长的方法进行缓存,用自定义注解可以非常方便地实现这个功能:
@Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface Cacheable { String value(); }
然后我们可以用反射机制获取到被@Cacheable注解的方法,对其返回值做缓存。
@Encrypt注解
在实际开发中,我们可能需要对某些敏感数据进行加密,而不是直接存储明文。用自定义注解可以为某些变量添加自动加密的功能,例如:
@Retention(RetentionPolicy.RUNTIME) @Target(ElementType.FIELD) public @interface Encrypt { boolean enable() default true; }
然后我们可以通过反射机制获取到被@Encrypt注解的变量,如果该注解的enable属性为true,就对该变量进行加密操作。
SpringBoot中的常见注解
@SpringBootApplication:该注解为Spring Boot的入口注解,整合了@Configuration、@EnableAutoConfiguration和@ComponentScan三个注解。
@RestController:声明一个控制器类,并且该类中的所有方法都以JSON的形式返回。
@RequestBody:该注解用于接收请求体的数据,并将其转换为Java对象。
@RequestMapping:定义访问路由,可以设置请求方法、参数、请求头等。
@PathVariable:获取RESTful接口中的路径参数。
@RequestParam:获取请求参数。
@Autowired:该注解用于自动注入依赖,可以配合@Qualifier注解进行精确匹配。
@Value:该注解用于获取配置文件中的属性值。
@ConfigurationProperties:该注解用于将配置文件中的属性值注入到Java Bean中。
@EnableAutoConfiguration:该注解自动配置Spring应用程序,简化了Spring应用程序的配置。
@ConditionalOnProperty:该注解用于控制某个配置项是否启用,可以设置默认值、匹配规则等。
@EnableAsync:该注解启用异步调用。
@Async:该注解将标记的方法异步执行。
@Scheduled:该注解用于定时任务。
我们日常开发会怎么用到自定义注解
校验参数
我们可以通过自定义注解来对控制器方法的参数做参数校验,例如:
@Target(ElementType.PARAMETER) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface ValidParam { String value(); } public void update(@ValidParam("id") long id, @ValidParam("name") String name) { // do something }
然后在方法执行前,我们可以根据@ValidParam注解的值对参数进行校验。
日志输出
我们可以通过自定义注解来标注一些需要进行日志输出的方法,例如:
@Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface Log { String value(); } @Log("更新订单状态") public void updateOrderStatus() { // do something }
然后在方法执行时,我们可以根据@Log注解的值来输出相应的日志。
缓存管理
我们可以通过自定义注解来标注一些需要进行缓存的方法,例如:
@Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface Cache { long expire() default 3600; // 缓存失效时间,默认为3600秒 String key() default ""; // 缓存key } @Cache(expire = 1800, key = "user_{#id}") public User getUserById(long id) { // do something }
然后在方法执行前,我们可以根据@Cache注解的值来判断是否从缓存中获取数据。
Spring AOP
我们可以通过自定义注解来搭配Spring AOP完成一些需求,例如:
@Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface CheckLogin { boolean required() default true; // 该接口是否需要登录 }
@CheckLogin @RequestMapping("/getUserInfo") public UserInfo getUserInfo() { // do something }
然后在AOP切面中,我们可以通过@CheckLogin注解的值来判断是否需要进行登录验证。
完整实例代码
以第一个Log注解为例,让我们来看看怎么把程序跑起来。
@Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface Log { String value() default ""; }
用idea创建一个maven工程,打开pom.xml,添加spring的aop模块。除此之外,还需要引入Spring IoC容器和AspectJ依赖。
<dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>5.3.9</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-aop</artifactId> <version>5.3.9</version> </dependency> <dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjweaver</artifactId> <version>1.9.6</version> </dependency>
Log注解
@Retention(RetentionPolicy.RUNTIME) @Target(ElementType.METHOD) @interface Log { String value(); }
Log切面
@Aspect public class LogAspect { @Around("@annotation(com.zhujie.Log)") public Object log(ProceedingJoinPoint joinPoint) throws Throwable { MethodSignature signature = (MethodSignature) joinPoint.getSignature(); Method method = signature.getMethod(); Log logAnnotation = method.getAnnotation(Log.class); if (logAnnotation != null) { String value = logAnnotation.value(); System.out.println("Log: " + value); } return joinPoint.proceed(); } }
@Around
@Around("@annotation(com.zhujie.Log)")
这个@Around注解中的参数是切点表达式,用来匹配需要被切入的方法。其中"@annotation(com.example.Log)"这部分是一个注解切点,表示需要匹配所有被@Log注解修饰的方法。
具体来说,@Around表示在方法执行前后都执行一段代码,包裹着原本要执行的方法。在执行这段代码时,可以获取到方法和参数的信息,对其进行处理或者记录日志等操作。
@annotation表示对注解的切面,后面跟着注解的类型,比如@annotation(com.zhujie.Log)就是表示对@Log注解的切面。这个切面就是指定了需要拦截所有被@Log注解修饰的方法,用来完成相应的操作。
UserService
@Service class UserService { @Log("用户新增") public void add() { } }
给add方法加上了@Log注解,只要add方法一执行,就会打印用户新增。
启动类:
@Configuration @ComponentScan @EnableAspectJAutoProxy public class AppConfig { @Bean public LogAspect logAspect() { return new LogAspect(); } public static void main(String[] args) { // 使用Spring上下文来管理Bean ApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class); // 获取LogService Bean UserService userService = context.getBean(UserService.class); // 调用含有@Log注解的方法 userService.add(); } }
@ComponentScan注解是用来扫描指定包及其子包下的所有组件,自动将其注入到容器中。但是,对于一些比较特殊的组件,如AOP切面等,需要手动在配置类中将其注入到容器中。
在使用AOP时,有两种方式:一种是使用XML配置来创建切面,另一种是使用注解方式来创建切面。使用注解方式创建切面时,需要在配置类中使用@Bean注解来创建切面实例,并使用@EnableAspectJAutoProxy注解来开启AOP代理支持。
运行AppConfig,效果如预期。