注解
什么是注解
- 注解(Annotation),就是 Java 代码里面的特殊标记,例如 @Override、@Test 等
- 作用:让其他程序根据注解信息来决定怎么执行该程序
- 注意:注解可以用在类上、构造器上、方法上、成员变量上、参数上…
- 注解的本质是一个接口,Java 中所有注解都是继承了 Annotation 接口的
- @注解(…) 其实就是一个实现类对象,实现了该注解以及 Annotation 接口
自定义注解
// 自定义注解
public @interface MyTest {String aaa();boolean bbb() default true;String[] ccc();
}
// 方法部分
@MyTest(aaa = "Jack", ccc = {"Java", "HTML"})
public void run() {}
特殊属性名:value——如果注解中只有一个 value 属性,使用注解时,value 名称可以不写!
public @interface MyTest {String value(); // 特殊属性
}
public @interface MyTest {String value(); // 特殊属性int age() default 23; // 当有默认值时候,可以不删除此行
}
@MyTest("Jack")
public void run() {}
元注解
- 元注解指的是:修饰注解的注解
@Target(ElemeElenntType.TYPE) | 作用:声明被修饰的注解只能在哪些位置使用 |
---|---|
TYPE | 类,接口 |
FIELD | 成员变量 |
METHOD | 成员方法 |
PARAMETER | 方法参数 |
CONSTRUCTOR | 构造器 |
LOCAL_VARIABLE | 局部变量 |
import java.lang.annotation.ElementType;
import java.lang.annotation.Target;@Target({ElemeElenntType.TYPE,ElementType.METHOD}) // 当前被修饰的注解只能用在类和成员方法上
public @interface MyTest {
}
@Retention(RetentionPolicy.RUNTIME) | 作用:声明注解的保留周期 |
---|---|
SOURCE | 只作用在源码阶段,字节码文件中不存在 |
CLASS(默认值) | 保留到字节码文件阶段,运行阶段不存在 |
RUNTIME(开发常用) | 一直保留到运行阶段 |
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;@Target({ElementType.TYPE, ElementType.METHOD}) // 当前被修饰的注解只能用在类和成员方法上
@Retention(RetentionPolicy.RUNTIME) // 控制下面的注解一直保留到运行时
public @interface MyTest {
}
注解的解析
什么是注解的解析?
- 就是判断类上、方法上、成员变量上是否存在注解,并把注解里的内容给解析出来
如何解析注解?
- 思想:要解析谁上面的注解,就应该先拿到谁
// 自定义注解import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface MyTest {String value();double aaa() default 100;String[] bbb();
}
// 在类和方法上使用注解@MyTest(value = "Jack", aaa = 99.8, bbb = {"Java", "HTML"})
public class Demo {public static void main(String[] args) {}@MyTest(value = "Tony", aaa = 88.6, bbb = {"java", "html"})public void run() {}
}
// 解析类上的注解import org.junit.Test;
import java.util.Arrays;
public class AnnotationTest {@Testpublic void parseClass() {// 1. 先得到 Class 对象Class c = Demo.class;// 2. 解析类上的注解// 判断这个类上是否包含了某个注解if (c.isAnnotationPresent(MyTest.class)) {MyTest myTest = (MyTest) c.getDeclaredAnnotation(MyTest.class);System.out.println(myTest.value());System.out.println(myTest.aaa());System.out.println(Arrays.toString(myTest.bbb()));}}
}
/*
运行结果:Jack99.8[Java, HTML]*/
// 解析方法上的注解import org.junit.Test;
import java.lang.reflect.Method;
import java.util.Arrays;public class AnnotationTest {@Testpublic void parseMethod() throws Exception {// 1. 先得到 Class 对象,然后通过 Class 对象获取方法对象Class c = Demo.class;Method m = c.getDeclaredMethod("run");// 2. 解析方法上的注解// 判断这个方法上是否包含了某个注解if (m.isAnnotationPresent(MyTest.class)) {MyTest myTest = (MyTest) m.getDeclaredAnnotation(MyTest.class);System.out.println(myTest.value());System.out.println(myTest.aaa());System.out.println(Arrays.toString(myTest.bbb()));}}
}
/*
运行结果:Tony88.6[java, html]*/
应用场景
案例:模拟 Junit 框架
// 需求:定义若干个方法,只要加了MyTest注解,就会触发该方法的执行
/* 分析:1.定义一个自定义注解 MyTest,只能注解方法,存活范围是一直都在2.定义若干个方法,部分方法加上 @MyTest 注解修饰,部分方法不加3.模拟一个 Junit 程序,可以触发加了 @MyTest 注解的方法执行
*/
// 自定义注解部分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 {}
// 模拟框架部分import java.lang.reflect.Method;public class AnnotationTest {public void test1() {System.out.println("===test1===");}@MyTestpublic void test2() {System.out.println("===test2===");}public void test3() {System.out.println("===test3===");}@MyTestpublic void test4() {System.out.println("===test4===");}public static void main(String[] args) throws Exception {AnnotationTest a = new AnnotationTest();// 启动程序!// 1. 得到 Class 对象Class c = AnnotationTest.class;// 2. 提取这个类中的全部成员方法Method[] methods = c.getDeclaredMethods();// 3. 遍历,获取到每个方法对象,看看其方法上是否存在 @MyTest 注解// 如果存在,则触发该方法执行for (Method method : methods) {if (method.isAnnotationPresent(MyTest.class)) {method.invoke(a); // 触发当前方法执行}}}
}
/*
运行结果:===test2======test4===*/
动态代理
代理:将一些通用部分的代码提取出来,交给代理去实现(类比 Python 的装饰器)
// 创建一个明星类(前置工作)// 明星(类)
public class Star implements Agent {private String name;public Star(String name) {this.name = name;}// 唱歌方法public String sing(String title) {System.out.println(this.name + "正在唱" + title);return "Think everyone!";}// 跳舞方法public void dance() {System.out.println(this.name + "正在跳舞");}
}
// 创建一个代理人接口(前置工作)// 代理人(接口)
public interface Agent {String sing(String name);void dance();
}
// 创建一个工具类,即:能制作出"代理人"的公司(前置工作)import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;// 中介公司(类)
public class ProxyUtil {// 创建代理对象的静态方法public static Agent createProxy(Star sss) {/*public static Object newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h)参数一:用于指定一个类加载器参数二:指定生成的代理长什么样子,也就是有哪些方法参数三:用来指定生成的代理对象要干什么事情*/Agent starProxy = (Agent) Proxy.newProxyInstance(ProxyUtil.class.getClassLoader(), // 参数一new Class[]{Agent.class}, // 参数二new InvocationHandler() { // 参数三@Override // 回调方法public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {// 代理对象要做的事情,请在这里写代码if (method.getName().equals("sing")) {System.out.println("准备话筒,收钱20W");} else if (method.getName().equals("dance")) {System.out.println("准备场地,收钱99W");}return method.invoke(sss, args);}});return starProxy; // 本静态方法的最后是————返回创建好的"代理对象"}
}
// 调用工具类,让公司为某个明星对象去定制一个专属代理人(正式部分)public class Test {public static void main(String[] args) {// 创建一个明星,叫"迈克尔-杰克逊"Star star = new Star("Michael-Jackson");// 为"迈克尔-杰克逊"创建一个代理人Agent agent = ProxyUtil.createProxy(star);System.out.println(agent.sing("新年好")); // 让代理人安排他唱歌System.out.println("-----------------");agent.dance(); // 让代理人安排他跳舞}
}/* 运行结果:准备话筒,收钱20WMichael-Jackson正在唱新年好Think everyone!-----------------准备场地,收钱99WMichael-Jackson正在跳舞*/