1、反射的概念:
JAVA反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;
对于任意一个对象,都能够调用它的任意一个方法和属性;
这种动态获取的信息以及动态调用对象的方法的功能称为java语言的反射机制。
要想解剖一个类,必须先要获取到该类的字节码文件对象。
而解剖使用的就是Class类中的方法,所以先要获取到每一个字节码文件对应的Class类型的对象。
2、类加载:
当程序要使用某个类时,如果该类还未被加载到内存中,则系统会通过加载,连接,初始化三步来实现对这个类进行初始化。
加载时机
创建类的实例
访问类的静态变量,或者为静态变量赋值
调用类的静态方法
使用反射方式来强制创建某个类或接口对应的java.lang.Class对象
初始化某个类的子类
直接使用java.exe命令来运行某个主类
3、有3种方法获得class对象:
a.Object类的getClass()方法,判断两个对象是否是同一个字节码文件
b.静态属性class,锁对象
c.Class类中静态方法forName(),读取配置文件
因为class是java的关键字,所以我们用clazz来代替。
Class clazz1 = Class.forName("com.example.bean.Person");//第三种方法
Class clazz2 = Person.class;//第二种方法
Person p = new Person();//第一种方法,通过对象来获取Class对象
Class clazz3 = p.getClass();
4、通过Class.forName读取配置文件:
为什么要使用配置文件呢?因为我们可以只改配置文件就可以改变你的需求。
例如:
public class Demo {
/**
* * 榨汁机(Juicer)榨汁的案例
* 分别有水果(Fruit)苹果(Apple)香蕉(Banana)桔子(Orange)榨汁(squeeze)
* @throws IOException
*/
public static void main(String[] args) throws Exception {
Juicer j = new Juicer();//创建榨汁机
j.run(new Apple());
j.run(new Orange());
}
}
interface Fruit {//定义一个接口,下面所有的水果都实现这个接口,一种多态的思想。
public void squeeze();
}
class Apple implements Fruit {
public void squeeze() {
System.out.println("榨出一杯苹果汁儿");
}
}
class Orange implements Fruit {
public void squeeze() {
System.out.println("榨出一杯橘子汁儿");
}
}
class Juicer {
//参数定义为接口,我们传实现了这个接口的对象就可以实现调用不同对象的方法。
public void run(Fruit f) {
f.squeeze();
}
}
上面的代码有个问题就是,我们如果不想苹果和橘子,就需要改源码
j.run(new Melon());
我们的解决方法是,我们可以通过配置文件将你需要传的类写在配置文件中。
例如:
a.config.properties(工程目录下)文件内容:
com.example.reflect.Apple
b.我们将main函数改造下
public static void main(String[] args) throws Exception {
Juicer j = new Juicer();//创建榨汁机
BufferedReader br = new BufferedReader(new FileReader("config.properties"));
Class clazz = Class.forName(br.readLine()); //获取该类的字节码文件
Fruit f = (Fruit) clazz.newInstance(); //创建实例对象
j.run(f);//通过读取到配置文件,new的一个对象
}
这样我们就可以通过配置文件到达动态改变的目的的,不要修改代码。
5、反射-获取有参构造方法:
Class类的newInstance()方法是使用该类无参的构造函数创建对象, 如果一个类没有无参的构造函数,
就不能这样创建了。
可以调用Class类的getConstructor()方法,里面可以给参数。
例如:
Person.java:
public class Person {
private String name;
private int age;
public Person() {
super();
}
public Person(String name, int age) {
super();
this.name = name;
this.age = age;
}
//...生成get set方法
@Override
public String toString() {
return "Person [name=" + name + ", age=" + age + "]";
}
}
使用:
public static void main(String[] args) throws Exception {
Class clazz = Class.forName("com.example.bean.Person");
Constructor c = clazz.getConstructor(String.class,int.class); //获取有参构造
Person p = (Person) c.newInstance("张三",23);//通过有参构造创建对象
System.out.println(p);
}
注意参数也是使用class字节码,clazz.getConstructor(String.class,int.class);
6、反射-获取成员变量:
Class.getField(String)方法可以获取类中的指定字段(可见的,如果是private就拿不到)。
但是可以通过getDeclaedField("name")方法获取,通过set(obj, "李四")方法可以设置指定对象上该字段的值。
修改私有属性的值之前,需要先调用setAccessible(true)设置访问权限。
用获取的指定的字段调用get(obj)可以获取指定对象中该字段的值。
例如:
public static void main(String[] args) throws Exception {
Class clazz = Class.forName("com.example.bean.Person");
Constructor c = clazz.getConstructor(String.class,int.class); //获取有参构造
Person p = (Person) c.newInstance("张三",23);
Field f = clazz.getDeclaredField("name");//暴力反射获取字段
f.setAccessible(true);//去除私有权限
f.set(p, "李四");
System.out.println(p);
}
这个可以修改一个对象的私有属性,可以看到反射机制强大。
7、反射-获取方法:
Class.getMethod(String, Class...) 和 Class.getDeclaredMethod(String,Class...)方法可以获取类中的指定方法,
加了Declasred的都是可以获取到私有的。
调用invoke(Object,Object...)可以调用该方法。
第一个Object是你传的对象,第二个Object是你方法的参数。
例如:
我们在Person类里面加一个方法:
public void eat() {
System.out.println("吃饭");
}
public void eat(int num) {
System.out.println("吃了" + num + "顿饭");
}
public static void main(String[] args) throws Exception {
Class clazz = Class.forName("com.example.bean.Person");
Constructor c = clazz.getConstructor(String.class,int.class); //获取有参构造
Person p = (Person) c.newInstance("张三",23);
Method m = clazz.getMethod("eat");//获取eat方法
m.invoke(p);//调用方法
Method m2 = clazz.getMethod("eat", int.class); //获取有参的eat方法
m2.invoke(p, 10);//调用方法,有参的
}
注意:关键是invoke方法使用,在很多的地方都可看到。
8、反射-通过反射越过泛型检查:
泛型只在编译期有效,在运行期会被擦除掉
例如:
public class Test {
public static void main(String[] args) throws Exception {
ArrayList<Integer> list = new ArrayList<>();
list.add(123);
list.add(45);
System.out.println(list);
Class clazz = list.getClass();
Method method = clazz.getMethod("add", Object.class);
method.invoke(list, "adb");
System.out.println(list);
}
}
神奇。
9、反射-动态代理:
先演示下代码:
有个User.java接口
public interface User {
public void add();
public void delete();
}
有个User接口的实现类
public class UserImp implements User {
@Override
public void add() {
System.out.println("权限校验");
System.out.println("添加");
System.out.println("日志");
}
@Override
public void delete() {
//System.out.println("权限校验");
System.out.println("删除");
//System.out.println("日志");
}
}
现在有个问题,就是我们添加和删除都需要权限校验和日志记录,这样每个方法都去写太麻烦
所以我们可以使用反射功能生成代理类,使用代理去做。
在Java中java.lang.reflect包下提供了一个Proxy类和一个InvocationHandler接口,通过使用这个类和接口就可以生成动态代理对象。
JDK提供的代理只能针对接口做代理。我们有更强大的代理cglib,Proxy类中的方法创建动态代理类对象.
public static Object newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h)
最终会调用InvocationHandler的方法
InvocationHandler Object invoke(Object proxy,Method method,Object[] args)
例如我们演示下:
a.创建一个类,实现InvocationHandler。
MyInvocationHandler.java
public class MyInvocationHandler implements InvocationHandler {
private Object target;
public MyInvocationHandler(Object target) {
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
System.out.println("权限校验");
method.invoke(target, args);//执行被代理target对象的方法
System.out.println("日志记录");
return null;
}
}
b.使用:
public static void main(String[] args) {
UserImp ui = new UserImp();
ui.add();
ui.delete();
MyInvocationHandler m = new MyInvocationHandler(ui);//这个ui就是你要代理的对象
User u = (User)Proxy.newProxyInstance(ui.getClass().getClassLoader(), ui.getClass().getInterfaces(), m);
u.add();
u.delete();
}
使用代理后,在调用方法的时候默认就添加了校验和记录功能。
总结:
为什么叫动态代理?因为我们可以脱离具体的对象,我们传任意对象都可以进行这样的操作。
我们为什么要传一个接口过去呢?因为我们代理类需要实现和UserImp同样的接口,这样我们才能强转为User。
其实我们自己也可以手动写个实现User接口的代理类,实现代理功能,叫做静态代理。只是只能代理User的实现类,
比较局限,所以实际的开发中我们使用动态代理来扩展类的功能。
10、设计模式-模版(Template)设计模式:
例如我们想统计某段代码的运行时间,下面这段代码:
public static void main(String[] args) {
long start = System.currentTimeMillis();
for(int i = 0; i < 1000000; i++) {
System.out.println("test");
}
long end = System.currentTimeMillis();
System.out.println(end - start);
}
有个问题,这个代码写死了,我们需要在一个类实现,我们调用类里面的方法就可以,改造:
class GetTime {
public long getTime() {
long start = System.currentTimeMillis();
code();
long end = System.currentTimeMillis();
return end - start;
}
public void code(){//这个方法还是写死了
for(int i = 0; i < 1000000; i++) {
System.out.println("test");
}
}
}
所以我们再改造:
abstract class GetTime {//抽象方法必须放在抽象类中
public final long getTime() {//定义为final是不让重写
long start = System.currentTimeMillis();
code();
long end = System.currentTimeMillis();
return end - start;
}
public abstract void code();//定义为抽象的,让子类实现
}
编写一个类,继承GetTime
class Demo extends GetTime {
@Override
public void code() {
int i = 0;
while(i < 100000) {
System.out.println("test");
i++;
}
}
}
使用:
public static void main(String[] args) {
Demo d = new Demo();
System.out.println(d.getTime());
}
总结:
模版方法模式就是定义一个算法的骨架,而将具体的算法延迟到子类中来实现
优点:使用模版方法模式,在定义算法骨架的同时,可以很灵活的实现具体的算法,满足用户灵活多变的需求
缺点:如果算法骨架有修改的话,则需要修改抽象类