简介
- 一种特殊的类, 相当于“标签”,用于注释说明,可修饰类、接口、方法、参数等。
- 注解本身不直接影响代码的操作,但可以被编译器或运行时环境用来生成额外的代码、文件等,或用来控制代码的行为。
实现原理
Java注解是通过反射(Reflection)API和注解处理器(Annotation Processors,在Java 6中引入)来实现的。注解处理器可以在编译时处理注解,生成新的源代码、文件或其他输出。而在运行时,可以通过反射API查询注解信息,并根据这些信息执行相应的逻辑。
自定义注解
注解的属性也叫做成员变量。注解只有成员变量,没有方法。注解的成员变量在注解的定义中以“无形参的方法”形式来声明,其方法名定义了该成员变量的名字,其返回值定义了该成员变量的类型。
@Documented
@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
public @interface Description {
String text() default "";
}
分类
元注解
元注解是可以注解到注解上的注解,或者说元注解是一种基本注解,但是它能够应用到其它的注解上面。
可以这样理解:元注解也是一张标签,但是它是一张特殊的标签,它的作用和目的就是给其他普通的标签进行解释说明的。
元标签有 @Retention、@Documented、@Target、@Inherited、@Repeatable 5 种。
元注解中的RetentionPolicy、ElementType
@Documented
@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
public @interface Description {
String text() default "";
}
RetentionPolicy
RetentionPolicy
定义了注解的保留策略,即注解在何时有效。它有三个分类:
-
SOURCE:注解只在源码中有效,在编译成.class文件时会被丢弃,不会被加载到JVM中。
- 示例:虽然
@Override
注解实际上使用的是CLASS
保留策略(因为它在编译时需要被检查),但如果你自定义一个只在源码中使用的注解,可以想象它是为了标记某些代码段或提供IDE中的特定功能,如@Todo("未完成的功能")
。
- 示例:虽然
-
CLASS:注解在编译时会被保留在.class文件中,但JVM加载类时不会保留注解,默认情况下注解会使用此策略。
- 示例:
@Deprecated
注解,它用于标记某个类或方法已过时,这个信息在编译时会被编译器读取,并可能生成警告,但不会被加载到JVM运行时环境中。
- 示例:
-
RUNTIME:注解在运行时可通过反射API被读取。
- 示例:
@Autowired
(在Spring框架中),这个注解在运行时由Spring容器通过反射机制查找并注入依赖。
- 示例:
ElementType
ElementType
定义了注解可以应用的Java元素类型。它包含以下分类:
-
TYPE:类、接口(包括注解类型)或枚举声明。
- 示例:
@Component
注解应用于类上,表示该类是一个Spring组件。
- 示例:
-
FIELD:字段声明(包括枚举常量)。
- 示例:
@Inject
注解应用于字段上,表示该字段需要被注入依赖。
- 示例:
-
METHOD:方法声明。
- 示例:
@Test
注解(JUnit框架中),用于标记某个方法是测试方法。
- 示例:
-
PARAMETER:参数声明。
- 示例:
@RequestParam
注解(Spring MVC中),用于将HTTP请求参数绑定到控制器方法的参数上。
- 示例:
-
CONSTRUCTOR:构造器声明。
- 示例:虽然Java标准库中可能没有直接应用于构造器的注解示例,但在自定义注解时,你可以将其应用于构造器上,以表示某种特殊的构造器用途。
-
LOCAL_VARIABLE:局部变量声明。
- 示例:
@Var
(假设的注解),用于标记局部变量具有某种特殊含义或需要特殊处理(注意:在Java标准库中,没有直接应用于局部变量的注解,这主要是出于实用性和性能考虑)。
- 示例:
-
ANNOTATION_TYPE:注解类型声明。
- 示例:
@Retention
注解自身就是一个注解,它被应用于其他注解上以指定其保留策略。
- 示例:
-
PACKAGE:包声明。
- 示例:
@java.lang.annotation.PackageMarker
(注意:Java标准库中并没有直接使用@PackageMarker
注解,但你可以自定义一个注解来标记包,并通过反射或注解处理器在编译时读取这些信息)。
- 示例:
-
TYPE_PARAMETER(Java 8 引入):类型参数声明。
- 示例:
@javax.inject.Qualifier
(虽然它本身不直接应用于类型参数,但可以用于创建特定于类型参数的注解,以便在依赖注入时区分不同类型的实例)。
- 示例:
-
TYPE_USE(Java 8 引入):使用类型的任何声明。
- 示例:
@java.lang.SuppressWarnings
注解可以用于类、方法、字段、参数等几乎所有使用类型的地方,以抑制编译器警告。
- 示例:
使用
@Description可修饰类的成员变量
/**
* 医院编码
*/
@Description(text = "医院编码")
@Column(name="hospital_code")
private String hospitalCode;
注解内容的提取
在需要获取注解内容的时候,可通过反射提取。
成员变量注解
public static <T, U> List<ChangeContentDto> generateChangeContent(T source, U target) {
List<Field> sourceFields = Arrays.asList(source.getClass().getDeclaredFields());
for (Field sourceField : sourceFields) {
Description descAnno = sourceField.getAnnotation(Description.class);
Column columnAnno = sourceField.getAnnotation(Column.class);
if (descAnno == null || columnAnno == null) {
continue;
}
if (descAnno.text().contains("授权范围")) {
//todo
}
}
}
类注解
//首先可以通过 Class 对象的 isAnnotationPresent() 方法判断它是否应用了某个注解
public boolean isAnnotationPresent(Class<? extends Annotation> annotationClass) {}
//获取 Annotation 对象。
public <A extends Annotation> A getAnnotation(Class<A> annotationClass) {}
public Annotation[] getAnnotations() {}
@TestAnnotation()
public class Test {
public static void main(String[] args) {
boolean hasAnnotation = Test.class.isAnnotationPresent(TestAnnotation.class);
if ( hasAnnotation ) {
TestAnnotation testAnnotation = Test.class.getAnnotation(TestAnnotation.class);
System.out.println("id:"+testAnnotation.id());
System.out.println("msg:"+testAnnotation.msg());
}
}
}
常见注解示例
在Spring框架中,这些注解扮演着重要的角色,它们用于开启或启用特定的Spring特性。下面是对您提到的每个注解的详细解释:
@EnableAspectJAutoProxy(proxyTargetClass = true)
- 作用:这个注解用于启用Spring对AspectJ的自动代理支持。AspectJ是一个面向切面的编程(AOP)框架,它允许开发者定义横切关注点(如日志、事务管理等)作为“切面”,并将这些切面织入到业务逻辑中。
proxyTargetClass = true
:这个属性指定了代理的创建方式。当设置为true
时,Spring会使用CGLIB库来生成基于类的代理(class-based proxies),而不是默认的基于接口的代理(interface-based proxies)。这意味着,即使目标类没有实现任何接口,也可以被代理。这对于那些需要代理非接口类的场景非常有用。
@EnableTransactionManagement(proxyTargetClass = true)
- 作用:这个注解用于启用Spring的声明式事务管理。声明式事务管理允许开发者通过注解(如
@Transactional
)来声明事务的边界和属性,而不是通过编程方式(如通过编程式事务API)来管理事务。 proxyTargetClass = true
:与@EnableAspectJAutoProxy
类似,这个属性也指定了代理的创建方式。设置为true
时,Spring将使用CGLIB来创建代理,这对于需要代理非接口类的事务管理场景非常有用。
@EnableConfigurationProperties
- 作用:这个注解用于启用对
@ConfigurationProperties
注解的支持。@ConfigurationProperties
注解允许将配置文件(如application.properties
或application.yml
)中的属性绑定到JavaBean上。这样做的好处是可以减少配置项的硬编码,使得配置更加灵活和易于管理。 - 使用方式:通常,这个注解会结合
@Configuration
注解的类一起使用,并且会指定一个或多个@ConfigurationProperties
注解的类的prefix
属性,以便Spring能够找到并绑定对应的配置项。
@Autowired 和 @Resource 区别
来源不同:@Autowired 来自 Spring 框架,而 @Resource 来自于(Java)JSR-250;
依赖查找的顺序不同:@Autowired 根据类型查询,多个实现类时需结合@Qualifier(“person1”) 使用; 而 @Resource 先根据名称再根据类型查询,多个时需要指定name@Resource(name = “renlei”) ;
支持的参数不同:@Autowired 只支持设置 1 个参数,而 @Resource 支持设置 7 个参数;
依赖注入的用法支持不同:
@Autowired 既支持构造方法注入,又支持属性注入和 Setter 注入,而 @Resource 只支持属性注入和 Setter 注入;
编译器 IDEA 的提示不同:当注入 Mapper 对象时,使用 @Autowired 注解编译器会提示错误,而使用 @Resource 注解则不会提示错误。
@Autowired 使用示例
构造方法注入
@Component
public class SomeBean {
private final AnotherBean anotherBean;
@Autowired
public SomeBean(AnotherBean anotherBean) {
this.anotherBean = anotherBean;
}
// ...
}
属性注入(字段注入)
@Component
public class SomeBean {
@Autowired
private AnotherBean anotherBean;
// ...
}
Setter 方法注入
@Component
public class SomeBean {
private AnotherBean anotherBean;
@Autowired
public void setAnotherBean(AnotherBean anotherBean) {
this.anotherBean = anotherBean;
}
// ...
}
@Autowired与@Qualifier结合使用
当Spring容器中存在多个相同类型的Bean时,我们可以使用@Autowired注解与@Qualifier注解结合来指定注入哪一个Bean。
// Person类,一个Bean
@Component("person1")
public class Person {
// ...
}
// 另一个Person类实例,同一个接口的不同实现
@Component("person2")
public class PersonImpl2 implements Person {
// ...
}
// House类,需要指定注入哪一个Person实例
@Component
public class House {
// 使用@Autowired和@Qualifier注解结合来指定注入哪一个Person实例
@Autowired
@Qualifier("person1")
private Person person;
// ...
}
@Resource 使用示例
属性注入(字段注入)
@Component
public class SomeBean {
@Resource
private AnotherBean anotherBean;
// ...
}
在这个例子中,@Resource
会首先尝试按照 anotherBean
字段的名称来查找 Bean,如果没有找到,则会尝试按照 AnotherBean
的类型来查找。
Setter 方法注入
@Component
public class SomeBean {
private AnotherBean anotherBean;
@Resource
public void setAnotherBean(AnotherBean anotherBean) {
this.anotherBean = anotherBean;
}
// ...
}
基于名称的注入(@Resource)
// Person类,一个Bean
@Component(value = "renlei")
public class Person {
// ...
}
// House类,需要注入Person类的实例
@Component
public class House {
// 使用@Resource注解通过名称注入Person实例
// 注意:这里的name属性值与Person类的@Component注解的value值相对应
@Resource(name = "renlei")
private Person person;
// ...
}