Java反射 & 注解学习总结
反射
概念 & 作用
反射(reflection):运行时查看元数据,反观察、修改程序内部结构。
元数据(MetaData),指运行时的数据,如模块、类、函数、注解、源代码
运行时修改代码:一般指修改Bytecode,byte[],用工具
Javasist
【注意】如果在运行时分析泛型类的实例,将不会得到太多的信息,因为它们已经被擦除了。
反射实例
常用方法
- Class.forName():通过类全限定名获取一个class,要传入全路径
- getClass():获取一个对象的运行时类
- [class].getConstructor().newInstance():通过构造器创建一个类
- [class].getDeclaredFields():获取所有属性,包括private
- [class]..getDeclaredMethods():获取所有方法
- [class].getClassLoader():获取类加载器
- method.invoke([class]):调用一个类中的所有方法
获取类的属性、方法
实例:
1 | public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException { |
反射创建对象 & 调用方法
实例:
1 | public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException { |
这里注意一下 main 方法的调用
java1.4是没有可变参数的,即Method.invoke(Obeject objecr ,Object obj[]{“xx”,“yy”});用数组传递多个参数 。
java1.5有了可变参数,即Method.invoke(Objet objecr,Object…args);用可变参数传递多个参数。由于兼容性问题,在JDK1.4没有引入可变参数Object…类型,所以使用数组来表示,invoke函数接收到String数组后会进行拆分,得到两个String变量,但是没有main函数接受 两个String值,所以不行。
而且只要参数是new Object[]{“xxx”,”yyy”},javac 为了兼容1.4,只把它当作 jdk1.4 的语法进行理解,而不把它当作 jdk1.5 的语法解释,也就是说不能传递new String[]{};。
解决方法:
(1)、mainMethod.invoke(null,new Object[]{new String[]{“20190212”}});该方法还是会按1.4处理,会将参数打散,但没关系,里面就是对的参数new String[]{“xxx”}。(2)、mainMethod.invoke(null,(Object)new String[]{“20190212”}); 不会打散,因为强转为Object了,编译器会认为这不是一个数组,此时是一个完整的参数。但注意他本质还是一个数组,作用仅仅是骗过编译器而已。
实际应用 - AOP
实现AOP,面向切面编程(即Aspect Oriented Programming)的框架
关注点分离原则(SoC,即Seperation of Concern),强调把程序分成多个部分,每个部分负责独立的功能
程序有一个主关注点 + 多个其他关注点
- 什么时候触发:before、after、around
- 触发什么:调用对象
举例:订单系统中的核心能力(下单、支付);其他能力(日志、通知、抽奖等等)

实现1:代理模式(Java中的Proxy类)
- 代理的作用,相当于是把多个
Aspect和 一个核心方法,在运行时融合起来 
- 代理的作用,相当于是把多个
实现2:函数式:高阶编程
- f:核心方法
- g:方面包装方法
- x:核心输入方法
- 最后调用方法:h = fg(x)
实现3:Javassist库动态添加方法
- AspectJ等框架会使用
实现注解
手写AOP实例
Java 的代理 Proxy类

Proxy.newInstance(classloader, interfaces, InvocationHandler);
- classloader:用于创建InvocationHander和代理对象的ClassLoader
- interfaces: 代理哪些接口
- InvocationHandler: 代理方法触发后的实际执行者
本质上,Proxy是把多个模块化的 Aspect 和 一个核心方法,在运行时融合起来,当成是一个综合实现多个功能的类。
1.创建业务接口IOrder
1 | /** |
2.创建业务实现类Order
1 | /** |
3.创建切面接口Aspect,内置静态方法,用于创建Proxy
该类只能针对接口做的代理
Proxy.newInstance(classloader, interfaces, InvocationHandler);
- classloader:用于创建InvocationHandler和代理对象的ClassLoader
- interfaces: 代理哪些接口
- InvocationHandler: 代理方法触发后的实际执行者
思考:
为什么需要InvocationHandler?
- 用来衔接、融合代理对象和Aspect对象
为什么需要传入Interfaces?
- 接口可能会有多个,被代理的接口会被拦截
为什么需要ClassLoader?
- 可能会有多个ClassLoader做不同的事情,必须要指定
1 | /** |
4.创建一个切面实例,计算方法的执行时间
1 | /** |
5.新建测试类
1 | /** |
【注意1】静态方法返回值为泛型的时候为什么static后要加<T>?
这个是java声明泛型方法的固定格式,在方法的返回值声明之前的位置,
定义该方法所拥有的泛型标识符,个数可以是多个,比如
1 public static<T1,T2,T3> Response<T1> test(T2 t2,T3 t3){ }这样,在方法的返回值或者入参的地方,就可以使用“T”这个泛型。
泛型标识符的名字可以是任意符合java规范的变量名,比如Map就使用<K,V>作为它的key和value的泛型标识符。
【注意2】String...为可变参数,可以传入一个或多个String,在编译时会自动封装为String数组
注解
注解就像一个标签,是贴在程序代码上供另一个程序读取的。其实也可以理解为一些外部的通用功能,在运行时融合到业务类中,但是传递参数的时候变得更加容易。本质还是为了解耦和提供类能力的扩展。
一般而言,注解多由第三方库提供,通过动态代理proxy和反射实现,或者是Javassist动态修改类来达到目的。
基本流程:程序读取注解 -> 找到注解 -> 找到类并实例化

基本使用方法
注解声明,此时注解没有作用域,仅仅是一个定义。
按如下的定义,可以这样调用注解@Aspect(name = UsageAspect.class) ,相当于是给name()方法赋值,传入了一个名为 UsageAspect 的类,这个类可以通过反射获取。
同理,@Aspect(value = "测试")可以给value()方法传入一个字符串。这些属性可以通过反射获取
1 | public Aspect { |
【注意】本质上@interface相当于继承了一个Annotation接口,而注解里的方法被编译为Abstract方法
元注解
@Retention
声明注解的存活周期
一般开发都是通过反射获取注解,故使用@Retention(RetentionPolicy.RUNTIME)
- Retention英文意思有保留、保持的意思,它表示注解存在阶段是保留在源码(编译期),字节码(类加载)或者运行期(JVM中运行)。在@Retention注解中使用枚举RetentionPolicy来表示注解保留时期
- @Retention(RetentionPolicy.SOURCE),注解仅存在于源码中,在class字节码文件中不包含
- @Retention(RetentionPolicy.CLASS), 默认的保留策略,注解会在class字节码文件中存在,但运行时无法获得
- @Retention(RetentionPolicy.RUNTIME), 注解会在class字节码文件中存在,在运行时可以通过反射获取到
@Target
声明注解的作用范围
常用@Target(ElementType.TYPE)
- Target的英文意思是目标,这也很容易理解,使用@Target元注解表示我们的注解作用的范围就比较具体了,可以是类,方法,方法参数变量等,同样也是通过枚举类ElementType表达作用类型
- @Target(ElementType.TYPE) 作用接口、类、枚举、注解
- @Target(ElementType.FIELD) 作用属性字段、枚举的常量
- @Target(ElementType.METHOD) 作用方法
- @Target(ElementType.PARAMETER) 作用方法参数
- @Target(ElementType.CONSTRUCTOR) 作用构造函数
- @Target(ElementType.LOCAL_VARIABLE)作用局部变量
- @Target(ElementType.ANNOTATION_TYPE)作用于注解(@Retention注解中就使用该属性)
- @Target(ElementType.PACKAGE) 作用于包
其他的3个元注解,用到再写
- @Document
- @Inherited
- @Repeatable
注解实例
实例:注解实现AOP
1.定义注解
1 | // 注解存活时间 |
2.定义切面接口
1 | public interface IAspect { |
3.定义切面实现
1 | /** |
4.定义业务接口
1 | /** |
5.定义业务实现
1 | /** |
6.定义工厂方法 & 单元测试
1 | /** |
实例:注解实现属性注入
1.定义一个注解
1 |
|
2.定义一个实体类
1 | public class Hero { |
3.定义一个静态工厂,负责属性注入
1 | public class InjectionFactory { |
4.测试类
1 | public class TestInjection { |
CGLib/Javassist 实现动态修改字节码

元编程
#TODO# 元编程