注解
注解是程序中的一种特殊标记。通过注解,编译器可以了解到该部分程序被保留的阶段、赋值传递等特殊用途。格式:@注解名
一、内置注解
在Java中常见的内置注解的如下:
@Override:表示被标记的方法继承了父类同名同参的方法,当检测到父类不存在该方法时将报编译时异常。
@Deprecated:表示被标记的方法已不推荐使用。
@SuppressWarnings():将提示编译器忽略当前被标记方法中的Warnings
二、注解本质
当我们点开@Override注解时,可以看到内容如下:
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.SOURCE)
public @interface Override {
}
那么注解是否是被@interface修饰符修饰的类呢?
我们可以仿造这个格式自己创建一个注解Anno1
public @interface Anno1 {
}
在Anno1.java所在的文件夹中,使用javac命令编译成Anno1.class文件,再使用javap命令反编译,看到控制台输出如下:
可见,注解实际上是一个继承了Annotation类的接口
三、自定义注解
下面我们开始尝试自定义并使用注解:
从(二)可知,注解实际上是一个继承了Annotation类的接口既然是接口。接口中一般含有抽象方法,在注解中,抽象方法也被称为属性。我们创建一个注解如下:(属性的修饰符默认为public abstract,可以不写)
public @interface Anno1 {
String name();
int age();
}
此时,我们就可以使用该注解
@Anno1(name="James",age=10)
public class Father {
}
通过反射应用注解实例:Test.java:
public static void main(String[] args) {
Father father = new Father();
Anno1 anno1 = father.getClass().getAnnotation(Anno1.class);
System.out.println("name = " + anno1.name());
System.out.println("age = " + anno1.age());
}
此时,会报空指针异常
这是为什么呢?其实,当我们自定义Anno1注解时,并没有告诉编译器该注解的使用范围和保留阶段。因此编译器无法读取到它。此时只需要在Anno1上添加一行@Retention(RetentionPolicy.RUNTIME)
即可。
@Retention(RetentionPolicy.RUNTIME)
public @interface Anno1 {
String name();
int age();
}
四、元注解
抽象的说,作用在注解上的注解就是元注解…上文Anno1中添加的@Rentention就是一个常见元注解。
常见的元注解如下:
-
@Retention:
表示被修饰的注解将要保留的阶段。它有三个取值
-
RententionPolicy.SOURCE:将保留至源码阶段。编译时将隐藏
-
RententionPolicy.CLASS:将保留至class文件阶段。但对运行对虚拟机隐藏
-
RententionPolicy.RUNTIME:可保留至运行时。
一般我们都选择保留至RUNTIME
-
-
@Document:表示被修饰的注解将会记入api文档
-
@Inherited:表示被修饰的注解将会被自动继承
-
@Target
表示被修饰的注解将要被作用的位置。它有三个常见取值
- ElementType.TYPE:可作用于类
- ElementType.FIELD:可作用于成员变量
- ElementType.METHOD:可作用于成员方法
五、简单框架实战
目标:简单模拟Aop,在一个类中定义三个方法,分别用自定义的@MyBefore、@MyTest、@MyAfter注解修饰,当执行被@MyTest标记的方法时,将自动执行前置和后置方法
1.自定义注解
@MyBefore
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface MyBefore {
}
@MyAfter
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface MyAfter {
}
@MyTest
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface MyTest {
}
2.测试类DaoTest.java
public class DaoTest {
@MyBefore
public void init(){
System.out.println("init...");
}
@MyTest
public void test(){
System.out.println("test...");
}
@MyAfter
public void destroy(){
System.out.println("destroy...");
}
@MyTest
public void eat(){
System.out.println("now eating...");
}
@MyTest
public void cook(){
System.out.println("now cooking...");
}
}
3.执行测试类Test.java
public class Test {
public static void main(String[] args) throws InvocationTargetException, IllegalAccessException, InstantiationException {
Class<DaoTest> daoTestClass = DaoTest.class;
DaoTest obj = daoTestClass.newInstance();
Method[] methods = daoTestClass.getMethods();
List<Method> beforeMethodList = new ArrayList<>();
List<Method> testMethodList = new ArrayList<>();
List<Method> afterMethodList = new ArrayList<>();
for(Method method:methods){
if(method.isAnnotationPresent(MyBefore.class)){
beforeMethodList.add(method);
}
if(method.isAnnotationPresent(MyTest.class)){
testMethodList.add(method);
}
if(method.isAnnotationPresent(MyAfter.class)){
afterMethodList.add(method);
}
}
for(Method m:testMethodList){
for(Method beforeMethod:beforeMethodList){
beforeMethod.invoke(obj);
}
m.invoke(obj);
for(Method afterMethod:afterMethodList){
afterMethod.invoke(obj);
}
}
}
}
4.执行结果