什么是反射,反射原理
Java反射的原理:java类的执行需要经历以下过程,
编译:.java文件编译后生成.class字节码文件
加载:类加载器负责根据一个类的全限定名来读取此类的二进制字节流到JVM内部,并存储在运行时内存区的方法区,然后将其转换为一个与目标类型对应的java.lang.Class对象实例
链接
验证:格式(class文件规范) 语义(final类是否有子类) 操作
准备:静态变量赋初值和内存空间,final修饰的内存空间直接赋原值,此处不是用户指定的初值。
解析:符号引用转化为直接引用,分配地址
初始化
有父类先初始化父类,然后初始化自己;将static修饰代码执行一遍,如果是静态变量,则用用户指定值覆盖原有初值;如果是代码块,则执行一遍操作。
Java的反射就是利用上面第二步加载到jvm中的.class文件来进行操作的。.class文件中包含java类的所有信息,当你不知道某个类具体信息时,可以使用反射获取class,然后进行各种操作。
Java反射就是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意方法和属性;并且能改变它的属性。总结说:反射就是把java类中的各种成分映射成一个个的Java对象,并且可以进行操作。
获取class的三种方式
先定义一个实体类Person:
Package reflex;
public class Person {
//私有属性
private String name = "Tom";
//公有属性
public int age = 18;
//构造方法
public Person() {
}
//私有方法
private void say(){
System.out.println("private say()...");
}
//公有方法
public void work(){
System.out.println("public work()...");
}
}
获取class方法
//1、对象调用 getClass() 方法来获取,通常应用在:比如你传过来一个 Object
// 类型的对象,而我不知道你具体是什么类,用这种方法
Person p1 = new Person();
Class c1 = p1.getClass();
//2、类名.class 的方式得到,该方法最为安全可靠,程序性能更高
// 这说明任何一个类都有一个隐含的静态成员变量 class
Class c2 = Person.class;
//3、通过 Class 对象的 forName() 静态方法来获取,用的最多,
// 但可能抛出 ClassNotFoundException 异常
Class c3 = Class.forName("reflex.Person");
需要注意的是:一个类在 JVM 中只会有一个 Class 实例,即我们对上面获取的 c1,c2,c3进行 equals 比较,发现都是true。代码如下:
Class class1 = Person.class;
Person person = new Person();
Class class2= person.getClass();
if(class1.equals(class2)){
System.out.println("class1.equals(class2)");
}
Class具有的部分方法如下:
getName():获得类的完整名字。
getFields():获得类的public类型的属性。
getDeclaredFields():获得类的所有属性。包括private 声明的和继承类
getMethods():获得类的public类型的方法。
getDeclaredMethods():获得类的所有方法。包括private 声明的和继承类
getMethod(String name, Class[] parameterTypes):获得类的特定方法,name参数指定方法的名字,parameterTypes 参数指定方法的参数类型。
getConstructors():获得类的public类型的构造方法。
getConstructor(Class[] parameterTypes):获得类的特定构造方法,parameterTypes 参数指定构造方法的参数类型。
newInstance():通过类的不带参数的构造方法创建这个类的一个对象。
Class能实现的功能
1判断对象属于哪个类
Person person = new Person();
Class class2= person.getClass();
System.out.println("class2:"+class2);
输出:class2:class reflect.Person
2获取类信息
Class class1 = Person.class;
Method[] methods = class1.getMethods();
Method[] declaredMethods = class1.getDeclaredMethods();
Field[] declaredFields = class1.getDeclaredFields();
3构建对象
Person person = new Person();
Class class2= person.getClass();
Object o = class2.newInstance();
//强转前先用instanceof判断
if(o instanceof Person){
((Person) o).work();
}
4动态执行方法
Class class1 = Person.class;
Method work = class1.getDeclaredMethod("work");
Person person = new Person();
work.invoke(person);
5动态操作属性
Class class1 = Person.class;
Person person = new Person();
Field field = class1.getDeclaredField("age");
//age默认值是18
field.set(person,22);
System.out.println(person.age);
首先简单说明下为什么需要代理模式:为其他对象提供一种代理以控制对这个对象的访问,可以隔离客户端和委托类的中介。我们还可以借助代理来在增加一些功能,而不需要修改原有代码。
重点是代理模式的三种实现方式:
先给出简单的接口和实现类:
public interface IHello {
void sayHello();
}
public final class Hello implements IHello{
@Override
public void sayHello() {
System.out.println("hello");
}
}
1静态代理模式
public class StaticProxy {
IHello hello;
public StaticProxy(IHello hello){
this.hello=hello;
}
public void syaHello(){
System.out.println("before");
hello.sayHello();
System.out.println("after");
}
public static void main(String args[]){
new StaticProxy(new Hello()).syaHello();
}
}
输出为:
before
hello
after
2java动态代理实现的动态代理
//java动态代理实现的动态代理
public class DynamicProxy implements InvocationHandler {
Object target;
public DynamicProxy(Object target){
this.target=target;
}
// 此处生成接口的实现类,所以需要有接口,无法代理未实现接口的类
public Object bind(){
return Proxy.newProxyInstance(this.target.getClass().getClassLoader(),
this.target.getClass().getInterfaces(),this);
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("before");
method.invoke(this.target,args);
System.out.println("after");
return null;
}
public static void main(String args[]){
//此处返回的必定是接口,不可以转为具体实体类,所以java反射要求被代理类必须实现了接口;但不要求final
IHello hello = (IHello) new DynamicProxy(new Hello()).bind();
hello.sayHello();
}
}
java动态代理实现代理步骤:
a定义被代理类:接口及接口实现类
b定义代理类,代理类需要实现InvocationHandler接口的类并重写invoke方法
c生成被代理的类的实例:调用 Proxy.newProxyInstance(被代理的类.getClass().getClassLoader(),
被代理的类.getClass().getInterfaces(),
InvocationHandler的实现类);
注意:newProxyInstance返回的是接口类型,所以java动态代理要求被代理类实现接口。
d被代理的类的实例调用需要执行的方法
3cglib实现的动态代理
maven先引入包:
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>2.2.2</version>
</dependency>
public class CGLibProxy {
public static void main(String args[]){
Enhancer enhancer = new Enhancer();
// 此处将目标类设置为父类,生成该类的子类来实现动态代理,所以如果此时将Hello类声明为final,则会报IllegalArgumentException;但不要求实现接口
enhancer.setSuperclass(Hello.class);
enhancer.setCallback(new MethodInterceptor(){
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
System.out.println("before");
Object result = methodProxy.invokeSuper(o,objects);
System.out.println("after");
return result;
}
});
Hello hello = (Hello)enhancer.create();
hello.sayHello();
}
}
Ehancer介绍:
Enhancer
Enhancer可能是CGLIB中最常用的一个类,和Java1.3动态代理中引入的Proxy类差不多(如果对Proxy不懂,可以参考这里)。和Proxy不同的是,Enhancer既能够代理普通的class,也能够代理接口。Enhancer创建一个被代理对象的子类并且拦截所有的方法调用(包括从Object中继承的toString和hashCode方法)。Enhancer不能够拦截final方法,例如Object.getClass()方法,这是由于Java final方法语义决定的。基于同样的道理,Enhancer也不能对fianl类进行代理操作。这也是Hibernate为什么不能持久化final class的原因。
public class SampleClass {
public String test(String input){
return "hello world";
}
}
下面我们将以这个类作为主要的测试类,来测试调用各种方法
@Test
public void testFixedValue(){
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(SampleClass.class);
enhancer.setCallback(new FixedValue() {
@Override
public Object loadObject() throws Exception {
return "Hello cglib";
}
});
SampleClass proxy = (SampleClass) enhancer.create();
System.out.println(proxy.test(null)); //拦截test,输出Hello cglib
System.out.println(proxy.toString());
System.out.println(proxy.getClass());
System.out.println(proxy.hashCode());
}
程序的输出为:
Hello cglib
Hello cglib
class com.zeus.cglib.SampleClass
EnhancerByCGLIB
EnhancerByCGLIB
e3ea9b7
java.lang.ClassCastException: java.lang.String cannot be cast to java.lang.Number
at com.zeus.cglib.SampleClass
EnhancerByCGLIB
EnhancerByCGLIB
e3ea9b7.hashCode(<generated>)
...
上述代码中,FixedValue用来对所有拦截的方法返回相同的值,从输出我们可以看出来,Enhancer对非final方法test()、toString()、hashCode()进行了拦截,没有对getClass进行拦截。由于hashCode()方法需要返回一个Number,但是我们返回的是一个String,这解释了上面的程序中为什么会抛出异常。
Enhancer.setSuperclass用来设置父类型,从toString方法可以看出,使用CGLIB生成的类为被代理类的一个子类,形如:SampleClass
EnhancerByCGLIB
EnhancerByCGLIB
e3ea9b7
Enhancer.create(Object…)方法是用来创建增强对象的,其提供了很多不同参数的方法用来匹配被增强类的不同构造方法。(虽然类的构造放法只是Java字节码层面的函数,但是Enhancer却不能对其进行操作。Enhancer同样不能操作static或者final类)。我们也可以先使用Enhancer.createClass()来创建字节码(.class),然后用字节码动态的生成增强后的对象。
原理上:
Java动态代理通过创建接口的实现类来完成对目标对象的代理,使用Java原生的反射API进行操作,在生成类上比较高效;但不能代理未实现接口的类;
CGLIB 在运行期间生成的是目标类扩展的子类,直接使用ASM框架直接对字节码进行操作,在类的执行过程中比较高效;不能扩展final修饰的类或方法;
使用上:
Java动态代理只能够对接口进行代理,不能对普通的类进行代理(因为所有生成的代理类的父类为Proxy,Java类继承机制不允许多重继承);CGLIB能够代理普通类,但是不能代理final修改时的类;
java动态代理是利用反射机制生成一个实现代理接口的匿名类,在调用具体方法前调用InvokeHandler来处理。而cglib动态代理是利用asm开源包,对代理对象类的class文件加载进来,通过修改其字节码生成子类来处理。
1、如果目标对象实现了接口,默认情况下会采用JDK的动态代理实现AOP
2、如果目标对象实现了接口,可以强制使用CGLIB实现AOP ,spring中配置proxy-target-class='true'
3、如果目标对象没有实现了接口,必须采用CGLIB库,spring会自动在JDK动态代理和CGLIB之间转换