反射是指程序能够访问,检测,修改它自己状态或行为的一种能力。java
java的反射机制是指在程序运行状态中,给定任意一个类,均可以获取到这个类的属性和方法;给定任意一个对象均可以调用这个对象的属性和方法,这种动态的获取类的信息和调用对象的方法的功能称之为java的反射机制。 一言以蔽之:反射机制可让你在程序运行时,拿到任意一个类的属性和方法并调用它。spring
想要理解反射首先须要知道Class这个类,它的全称是java.lang.Class类。java是面向对象的语言,讲究万物皆对象,即便强大到一个类,它依然是另外一个类(Class类)的对象,换句话说,普通类是Class类的对象,即Class是全部类的类(There is a class named Class)。 对于普通的对象,咱们通常会这样建立:数据库
Code code1 = new Code();
复制代码
上面说了,全部的类都是Class的对象,那么如何表示呢,可不能够经过以下方式呢:数组
Class c = new Class();
复制代码
可是咱们查看Class的源码时,是这样写的:bash
private Class(ClassLoader loader) {
classLoader = loader;
}
复制代码
能够看到构造器是私有的,只有JVM才能够调用这个构造函数建立Class的对象,所以不能够像普通类同样new一个Class对象,虽然咱们不能new一个Class对象,可是却能够经过已有的类获得一个Class对象,共有三种方式,以下:微信
Class c1 = Test.class; 这说明任何一个类都有一个隐含的静态成员变量class,这种方式是经过获取类的静态成员变量class获得的
Class c2 = test.getClass(); test是Test类的一个对象,这种方式是经过一个类的对象的getClass()方法得到的
Class c3 = Class.forName("com.catchu.me.reflect.Test"); 这种方法是Class类调用forName方法,经过一个类的全量限定名得到
复制代码
这里,c一、c二、c3都是Class的对象,他们是彻底同样的,并且有个学名,叫作Test的类类型(class type)。 这里就让人奇怪了,前面不是说Test是Class的对象吗,而c一、c二、c3也是Class的对象,那么Test和c一、c二、c3不就同样了吗?为何还叫Test什么类类型?这里不要纠结于它们是否相同,只要理解类类型是干什么的就行了,顾名思义,类类型就是类的类型,也就是描述一个类是什么,都有哪些东西,因此咱们能够经过类类型知道一个类的属性和方法,而且能够调用一个类的属性和方法,这就是反射的基础。 示例代码:app
public class Test {
public static void main(String[] args) throws ClassNotFoundException {
Class<Test> class1 = Test.class;
System.out.println("类名1:"+class1.getName());
Test Test = new Test();
Class<? extends Test> class2 = Test.getClass();
System.out.println("类名2:"+class2.getName());
Class<?> class3 = Class.forName("com.catchu.me.reflect.Test");
System.out.println("类名3:"+class3.getName());
if(class1==class2){
System.out.println("class1==class2");
}
if(class1==class3){
System.out.println("class1==class3");
}
}
}
复制代码
输出结果:框架
类名1:com.catchu.me.reflect.Test
类名2:com.catchu.me.reflect.Test
类名3:com.catchu.me.reflect.Test
class1==class2
class1==class3
复制代码
java的反射操做主要是用到了java.lang.Class类和java.lang.reflect反射包下的类,上面说到咱们已经能够拿到一个类的Class信息,根据这个Class咱们就可使用某些方法来操做(获取)类的如下信息:ide
万物皆对象,类的构造函数是java.lang.reflect.Constructor类的对象,经过Class的下列方法能够获取构造函数对象:函数
public Constructor<T> getDeclaredConstructor(Class<?>... parameterTypes) // 得到该类全部的构造器,不包括其父类的构造器
public Constructor<T> getConstructor(Class<?>... parameterTypes) // 得到该类全部public构造器,包括父类
//具体
Constructor<?>[] allConstructors = class1.getDeclaredConstructors();//获取class对象的全部声明构造函数
Constructor<?>[] publicConstructors = class1.getConstructors();//获取class对象public构造函数
Constructor<?> constructor = class1.getDeclaredConstructor(String.class);//获取指定声明构造函数(局部变量是一个字符串类型的)
Constructor publicConstructor = class1.getConstructor(String.class);//获取指定声明的public构造函数
复制代码
测试代码以下:
public class TestConstructor {
public static void main(String[] args) throws Exception{
Class<?> personClass = Class.forName("com.catchu.me.reflect.Person");
//获取全部的构造函数,包括私有的,不包括父类的
Constructor<?>[] allConstructors = personClass.getDeclaredConstructors();
//获取全部公有的构造函数,包括父类的
Constructor<?>[] publicConstructors = personClass.getConstructors();
System.out.println("遍历以后的构造函数:");
for(Constructor c1 : allConstructors){
System.out.println(c1);
}
Constructor<?> c2 = personClass.getDeclaredConstructor(String.class);
c2.setAccessible(true); //设置是否可访问,由于该构造器是private的,因此要手动设置容许访问,若是构造器是public的就不用设置
Object person = c2.newInstance("刘俊重"); //使用反射建立Person类的对象,并传入参数
System.out.println(person.toString());
}
}
复制代码
Person类以下,为测出效果包含一个私有构造函数:
public class Person {
private int age;
private String name;
public Person() {
}
private Person(String name){
this.name = name;
}
public Person(int age,String name){
this.age = age;
this.name = name;
}
//省略set/get方法
@Override
public String toString() {
return "Person{" +
"age=" + age +
", name='" + name + '\'' + '}'; } } 复制代码
测试结果以下:
遍历以后的构造函数:
public com.catchu.me.reflect.Person(int,java.lang.String)
private com.catchu.me.reflect.Person(java.lang.String)
public com.catchu.me.reflect.Person()
Person{age=0, name='刘俊重'}
复制代码
由上面能够看到咱们在得到某个类的Class类类型以后,能够经过反射包中的方法获取到这个类的构造函数,进而能够建立该类的对象。
万物皆对象,类的成员变量是java.lang.reflect.Field类的对象,经过Class类的如下方法能够获取某个类的成员变量,值得一提的是变量是包含两部分的,变量类型和变量名:
public Field getDeclaredField(String name) // 得到该类自身声明的全部变量,不包括其父类的变量
public Field getField(String name) // 得到该类自全部的public成员变量,包括其父类变量
//具体实现
Field[] allFields = class1.getDeclaredFields();//获取class对象的全部属性
Field[] publicFields = class1.getFields();//获取class对象的public属性
Field ageField = class1.getDeclaredField("age");//获取class指定属性
Field desField = class1.getField("des");//获取class指定的public属性
复制代码
示例代码以下:
public class TestField {
public static void main(String[] args) throws Exception{
Class<Person> personClass = Person.class;
//获取全部的成员变量,包含私有的
Field[] allFields = personClass.getDeclaredFields();
//获取全部公有的成员变量,包含父类的
Field[] publicFields = personClass.getFields();
System.out.println("全部的成员变量:");
for(Field f : allFields){
System.out.println(f);
}
//获取某个变量的值
//建立对象的实例
Constructor<Person> c = personClass.getDeclaredConstructor(String.class);
c.setAccessible(true); //由于该构造函数时私有的,须要在这里设置成可访问的
Person person = c.newInstance("刘俊重");
//获取变量name对象
Field field = personClass.getDeclaredField("name");
field.setAccessible(true); //由于变量name是私有的,须要在这里设置成可访问的
//注意对比下面这两行,官方对field.get(Object obj)方法的解释是返回对象obj字段field的值
Object value = field.get(person);
//String name = person.getName();
System.out.println("获取的变量的值是:"+value);
}
}
复制代码
输出结果以下:
全部的成员变量:
private int com.catchu.me.reflect.Person.age
private java.lang.String com.catchu.me.reflect.Person.name
获取的变量的值是:刘俊重
复制代码
这里要注意field.get(person)方法,咱们根据对象获取属性的常规方法是经过:String name = person.getName(),反射中能够经过:字段.get(对象),这也是获取对象的某个字段,有点相似于invoke方法。
万物皆对象,类的成员方法是java.lang.reflect.Method的对象,经过java.lang.Class类的如下方法能够获取到类的成员方法,经过方法类Method提供的一些方法,又能够调用获取到的成员方法。
public Method getDeclaredMethod(String name, Class<?>... parameterTypes) // 获得该类全部的方法,不包括父类的
public Method getMethod(String name, Class<?>... parameterTypes) // 获得该类全部的public方法,包括父类的
//具体使用
Method[] methods = class1.getDeclaredMethods();//获取class对象的全部声明方法
Method[] allMethods = class1.getMethods();//获取class对象的全部public方法 包括父类的方法
Method method = class1.getMethod("info", String.class);//返回此class1对应的public修饰的方法名是info的,包含一个String类型变量的方法
Method declaredMethod = class1.getDeclaredMethod("info", String.class);//返回此Class对象对应类的、带指定形参列表的方法
复制代码
测试代码以下:
public class TestMethod {
public static void main(String[] args) throws Exception {
Person person = new Person();
Class<? extends Person> personClass = person.getClass();
Method[] allMethods = personClass.getDeclaredMethods();
Method[] publicMethods = personClass.getMethods();
System.out.println("遍历全部的方法:");
for(Method m : allMethods){
System.out.println(m);
}
//下面是测试经过反射调用函数
//经过反射建立实例对象,默认调无参构造函数
Person person2 = personClass.newInstance();
//获取要调用的方法,要调用study方法,包含int和String参数,注意int和Integer在这有区别
Method method = personClass.getMethod("study", int.class, String.class);
Object o = method.invoke(person2, 18, "刘俊重");
}
}
复制代码
测试结果:
遍历全部的方法:
public java.lang.String com.catchu.me.reflect.Person.toString()
public java.lang.String com.catchu.me.reflect.Person.getName()
public void com.catchu.me.reflect.Person.setName(java.lang.String)
public void com.catchu.me.reflect.Person.study(int,java.lang.String)
public int com.catchu.me.reflect.Person.getAge()
public void com.catchu.me.reflect.Person.setAge(int)
我叫刘俊重,我今年18,我在学习反射
复制代码
注意:Object o = method.invoke(person2, 18, "刘俊重");就是调用person2对象的method方法,格式是:方法名.invoke(对象,参数),相似于获取成员变量值时的get方法。 由上面能够看出反射的强大:经过反射咱们能够获取到类类型,经过Class类型咱们能够获取到构造函数,进而实例化new出一个对象;经过反射咱们能够获取到成员变量和成员方法,经过实例出的对象又能够获取到这些成员变量的值或调用成员方法。这才只是反射的一部分,经过反射咱们还能够判断类,变量,方法,是否包含某些特定注解,还能够经过反射来动态代理去调用其它方法,跟注解和动态代理挂起勾会有无限的想象空间,好比spring框架,底层就是经过这些原理。下面在说几个反射经常使用的API,最后会介绍反射跟注解和动态代理的结合使用。
Annotation[] annotations = (Annotation[]) class1.getAnnotations();//获取class对象的全部注解
Annotation annotation = (Annotation) class1.getAnnotation(Deprecated.class);//获取class对象指定注解
Type genericSuperclass = class1.getGenericSuperclass();//获取class对象的直接超类的
Type Type[] interfaceTypes = class1.getGenericInterfaces();//获取class对象的全部接口的type集合
复制代码
boolean isPrimitive = class1.isPrimitive();//判断是不是基础类型
boolean isArray = class1.isArray();//判断是不是集合类
boolean isAnnotation = class1.isAnnotation();//判断是不是注解类
boolean isInterface = class1.isInterface();//判断是不是接口类
boolean isEnum = class1.isEnum();//判断是不是枚举类
boolean isAnonymousClass = class1.isAnonymousClass();//判断是不是匿名内部类
boolean isAnnotationPresent = class1.isAnnotationPresent(Deprecated.class);//判断是否被某个注解类修饰
String className = class1.getName();//获取class名字 包含包名路径
Package aPackage = class1.getPackage();//获取class的包信息
String simpleName = class1.getSimpleName();//获取class类名
int modifiers = class1.getModifiers();//获取class访问权限
Class<?>[] declaredClasses = class1.getDeclaredClasses();//内部类
Class<?> declaringClass = class1.getDeclaringClass();//外部类
ClassLoader ClassLoader = class1.getClassLoader() 返回类加载器
getSuperclass():获取某类全部的父类
getInterfaces():获取某类全部实现的接口
复制代码
代理的操做是经过java.lang.reflect.Proxy 类中实现的,经过Proxy的newProxyInstance()方法能够建立一个代理对象,以下:
public static Object newProxyInstance(ClassLoader loader,类<?>[] interfaces,InvocationHandler h)
复制代码
不要看到这里面一大坨晦涩的屎代码就惧怕,这里面是有技巧的,其实都是模板,须要什么,咱们传什么过去就能够了。能够看到须要三个参数,类加载器,接口和调用处理者。咱们在上面已经能拿到Class类了,使用class.getClassLoader就能够获取类加载器,使用class.getgetInterfaces()能够获取全部的接口,那如今要写的不就是新建一个InvocationHandler对象了吗?事实上,咱们动态代理的核心代码也就是在这里面写的。我上面说的模板,其实就是下面这几步:
public interface PersonInterface {
void doSomething();
void saySomething();
}
复制代码
接口的实现类:
public class PersonImpl implements PersonInterface {
@Override
public void doSomething() {
System.out.println("人类在作事");
}
@Override
public void saySomething() {
System.out.println("人类在说话");
}
}
复制代码
代理类:
/**
* @author 刘俊重
*/
public class PersonProxy {
public static void main(String[] args) {
final PersonImpl person = new PersonImpl();
PersonInterface proxyPerson = (PersonInterface) Proxy.newProxyInstance(PersonImpl.class.getClassLoader(),
PersonImpl.class.getInterfaces(), new InvocationHandler() {
//在下面的invoke方法里面写咱们的业务
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if(method.getName()=="doSomething"){
person.doSomething();
System.out.println("经过常规方法调用了实现类");
}else{
method.invoke(person,args);
System.out.println("经过反射机制调用了实现类");
}
return null;
}
});
proxyPerson.doSomething();
proxyPerson.saySomething();
}
}
复制代码
执行结果以下:
人类在作事
经过常规方法调用了实现类
人类在说话
经过反射机制调用了实现类
复制代码
在咱们经过proxyPerson.doSomething()调用的时候,其实不是立马进入实现类的doSomething方法,而是带着方法名,参数进入到了咱们的代理方法invoke里面,在这里面我进行了一次判断,若是等于"doSomething"就使用常规方法调用,不然使用反射的方法调用。这样看似仍是平时的调用,可是每次执行都要走咱们的代理方法里面,咱们能够在这里面作些“手脚”,加入咱们的业务处理。 能够看下面另外一个示例,好比天猫一件衣服正常卖50,如今你是个人vip用户,能够给你打折扣10块,其它业务都是相同的,只有这里便宜了10,从新服务提供者就很麻烦,用代理能够解决这个问题。 接口SaleService:
public interface SaleService {
//根据尺码返回衣服的大小
int clothes(String size);
}
复制代码
接口实现类SaleServiceImpl:
public class SaleServiceImpl implements SaleService {
@Override
public int clothes(String size) {
System.out.println("衣服大小"+size);
//模拟从数据库取衣服价格
return 50;
}
}
复制代码
普通无折扣的调用测试:
/**
* @author 刘俊重
* @Description 普通用户
*/
public class OrdinaryCustom {
public static void main(String[] args) {
SaleService saleService = new SaleServiceImpl();
int money = saleService.clothes("XXl");
System.out.println("价格是:"+money);
}
}
复制代码
输出结果:
衣服大小XXl
价格是:50
复制代码
代理类ProxySale:
/**
* @author 刘俊重
* @Description 代理类
*/
public class ProxySale {
//对接口方法进行代理
public static <T> T getProxy(final int discount, final Class<SaleServiceImpl> implementClasses, Class<SaleService> interfaceClasses){
return (T)Proxy.newProxyInstance(implementClasses.getClassLoader(),
implementClasses.getInterfaces(), new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//调用原始对象的方法,获取未打折以前的价格
int price = (int) method.invoke(implementClasses.newInstance(), args);
return price-discount;
}
});
}
}
复制代码
vip用户测试类VipCustom:
/**
* @author 刘俊重
* @Description Vip用户,有打折优惠
*/
public class VipCustom {
public static void main(String[] args) {
//vip用户,打10元折扣
int discount = 10;
SaleService saleService = ProxySale.getProxy(discount, SaleServiceImpl.class, SaleService.class);
int money = saleService.clothes("xxl");
System.out.println("价格是:"+money);
}
}
复制代码
输出结果是:
衣服大小xxl
价格是:40
复制代码
能够看到,在未修改服务提供者的状况下,咱们在代理类里面作了手脚,结果符合预期。
@Override
void myMethod() {
......
}
复制代码
这其中@Override就是注解。这个注解的做用也就是告诉编译器,myMethod()方法覆盖了父类中的myMethod()方法。
@Override:表示当前的方法定义将覆盖超类中的方法,若是出现错误,编译器就会报错。
@Deprecated:若是使用此注解,编译器会出现警告信息。
@SuppressWarnings:忽略编译器的警告信息。
复制代码
@Target
@Retention
@Documented
@Inherited
复制代码
java8加了两个新注解,后续我会讲到。
@Target说明了Annotation所修饰的对象范围:Annotation可被用于 packages、types(类、接口、枚举、Annotation类型)、类型成员(方法、构造方法、成员变量、枚举值)、方法参数和本地变量(如循环变量、catch参数)。在Annotation类型的声明中使用了target可更加明晰其修饰的目标。
做用:用于描述注解的使用范围(即:被描述的注解能够用在什么地方) 取值(ElementType)有:
类型 | 用途 |
---|---|
CONSTRUCTOR | 用于描述构造器 |
FIELD | 用于描述域 |
LOCAL_VARIABLE | 用于描述局部变量 |
METHOD | 用于描述方法 |
PACKAGE | 用于描述包 |
PARAMETER | 用于描述参数 |
TYPE | 用于描述类、接口(包括注解类型) 或enum声明 |
好比定义下面一个注解,它就只能用在方法上,由于已经限定了它是方法级别的注解,若是用在类或者其它上面,编译阶段就会报错:
@Target({ElementType.METHOD})
public @interface MyMethodAnnotation {
}
复制代码
测试类MyClass:
//@MyMethodAnnotation 报错,方法级别注解不能注在类头上
public class MyClass {
@MyMethodAnnotation
public void myTestMethod(){
//
}
}
复制代码
@Retention定义了该Annotation被保留的时间长短:某些Annotation仅出如今源代码中,而被编译器丢弃;而另外一些却被编译在class文件中;编译在class文件中的Annotation可能会被虚拟机忽略,而另外一些在class被装载时将被读取(请注意并不影响class的执行,由于Annotation与class在使用上是被分离的)。使用这个meta-Annotation能够对 Annotation的“生命周期”限制。
做用:表示须要在什么级别保存该注释信息,用于描述注解的生命周期(即:被描述的注解在什么范围内有效) 取值(RetentionPoicy)有:
类型 | 用途 | 说明 |
---|---|---|
SOURCE | 在源文件中有效(即源文件保留) | 仅出如今源代码中,而被编译器丢弃 |
CLASS | 在class文件中有效(即class保留) | 被编译在class文件中 |
RUNTIME | 在运行时有效(即运行时保留) | 编译在class文件中 |
示例:
@Target({ElementType.TYPE}) //用在描述类、接口或enum
@Retention(RetentionPolicy.RUNTIME) //运行时有效
public @interface MyClassAnnotation {
String value(); //这个MyClassAnnotation注解有个value属性,未来能够设置/获取值
}
复制代码
@Documented用于描述其它类型的annotation应该被做为被标注的程序成员的公共API,所以能够被例如javadoc此类的工具文档化。Documented是一个标记注解,没有成员。
做用:将注解包含在javadoc中
java.lang.annotation.Documented
@Documented
public @interface MyCustomAnnotation { //Annotation body}
复制代码
是一个标记注解,阐述了某个被标注的类型是被继承的,使用了@Inherited修饰的annotation类型被用于一个class,则这个annotation将被用于该class的子类,@Inherited annotation类型是被标注过的class的子类所继承。类并不从实现的接口继承annotation,方法不从它所重载的方法继承annotation,当@Inherited annotation类型标注的annotation的Retention是RetentionPolicy.RUNTIME,则反射API加强了这种继承性。若是咱们使用java.lang.reflect去查询一个@Inherited annotation类型的annotation时,反射代码检查将展开工做:检查class和其父类,直到发现指定的annotation类型被发现,或者到达类继承结构的顶层。
做用:容许子类继承父类中的注解 示例,这里的MyParentClass 使用的注解标注了@Inherited,因此子类能够继承这个注解信息:
java.lang.annotation.Inherited
@Inherited
public @interface MyCustomAnnotation {
}
@MyCustomAnnotation
public class MyParentClass {
...
}
public class MyChildClass extends MyParentClass {
...
}
复制代码
格式
public @interface 注解名{
定义体
}
复制代码
示例:
@Target(ElementType.FIELD)
@Retention(value=RetentionPolicy.RUNTIME)
@Documented
public @interface MyFieldAnnotation {
int id() default 0;
String name() default "";
}
复制代码
定义了一个用在字段上的,运行时有效的名为MyFieldAnnotation的注解,它有两个属性,int类型的id(id后面记得带括号)默认值是0,还有一个String类型的name,默认值是""。
在上面咱们已经知道了怎么自定义一个注解了,可是光定义没用啊,重要的是我要使用它,使用的方法也很简单,最上面讲反射的时候也提到过几个这样的方法了,好比:class1.isAnnotation(),其实他们通通是java.lang.reflect包下的AnnotatedElement接口里面的方法,这个接口主要有如下几个实现类:
测试代码CustomClassAnnotation以下:
/**
* @author 刘俊重
* @Description 自定义类注解
*/
@Target(ElementType.TYPE) //做用在类,枚举或接口上
@Retention(RetentionPolicy.RUNTIME) //运行时有效
@Documented //文档可见
public @interface CustomClassAnnotation {
String value(); //获取注解名称
}
复制代码
FruitName类以下:
/**
* @author 刘俊重
* @Description 字段注解(字符串类型的)
*/
@Target(ElementType.FIELD) //用在字段上
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface FruitName {
String name() default "";
}
复制代码
FruitColor类以下:
/**
* @author 刘俊重
* @Description 字段注解(枚举类型的)
*/
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface FruitColor {
//颜色枚举
enum Color{BLUE,RED,GREEN};
//颜色属性
Color color() default Color.RED;
}
复制代码
Fruit实体类以下:
/**
* @author 刘俊重
* @Description Fruit实体类
*/
@CustomClassAnnotation(value="fruit")
public class Fruit{
@FruitName(name="apple")
private String name;
@FruitColor(color= FruitColor.Color.RED)
private String color;
}
复制代码
测试类TestAnnotation以下:
/**
* @author 刘俊重
* @Description 测试类
*/
public class TestAnnotation {
public static void main(String[] args) {
Class<Fruit> clazz = Fruit.class; //反射获取Class对象
CustomClassAnnotation annotation = clazz.getAnnotation(CustomClassAnnotation.class); //拿到Fruit类的注解
if(null!=annotation && "fruit".equals(annotation.value())){
System.out.println("Fruit类的注解名是======"+annotation.value());
//获取全部的属性遍历,拿到每个属性的值
Field[] allFields = clazz.getDeclaredFields();
for(Field field : allFields){
if(field.isAnnotationPresent(FruitName.class)){
//判断是否存在FruitName注解
FruitName fruitName = field.getAnnotation(FruitName.class);
System.out.println("水果名称====="+fruitName.name());
}
if(field.isAnnotationPresent(FruitColor.class)){
FruitColor fruitColor = field.getAnnotation(FruitColor.class);
System.out.println("水果颜色====="+fruitColor.color());
}
}
}else{
System.out.println("注解值不对,请检查");
}
}
}
复制代码
总结:经过注解能够获取到类名,接口名,方法名,属性名,再搭配反射能够动态的生成对象,再搭配动态代理,去动态的去调用某些方法,这基本上也就是spring框架底层实现原理的一部分了。 注:本文一些内容引用自http://t.cn/RK8ci8w ,原做者反射和注解写的确实不错,我对部份内容进行了完善并重写了部分示例;动态代理部分我加的,考虑着把反射,代理,注解放在一块来讲。
附一下我的微信公众号,欢迎跟我交流。