引言
在反射出现之前,当我们有一个java类之后,我们在类的外部是不能使用类中的私有结构的,比如说不能调用private的构造器,private的方法等等。但是出现了反射之后,我们就可以调用这个类的任何结构,包括私有的。
import org.junit.Test;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
public class ReflectionTest {
@Test
public void test1(){
Person p = new Person("tom",23);
p.show();
}
@Test
public void test2() throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
//1.获取类对象
Class<Person> personClass = Person.class;
//2.获取类对象的构造器
Constructor constructor = personClass.getConstructor(String.class, int.class);
Object tom = constructor.newInstance("Jerry", 23);
//3.获取类方法
Method show = personClass.getDeclaredMethod("show");
show.invoke(tom);
//反射的特殊功能,可以调用私有的类结构
Constructor<Person> cons1 = personClass.getDeclaredConstructor(String.class);
cons1.setAccessible(true);
Person wanglei = cons1.newInstance("wanglei");
wanglei.show();
}
}
反射和单例模式
单例模式私有化了构造器,希望这个类的实例只需要创建一个就可以了。到那时反射是解决能不能用的问题,单例模式是建议你的这张写法,他们是不冲突的。
关于java.lang.Class的理解
Class作为反射的源头
(1)类的加载过程:程序经过javac.exe编译之后会生成一个或多个字节码文件.class结尾,每一个java类对应一个字节码文件,通过java.exe解释运行某个包含有mian方法的字节码文件。相当于把这个字节码文件加载到内存中,此过程称为类的加载。记载到内存中的类称之为运行时类,这个运行时类作为Class的一个实例。换句话说Class的实例就对应着一个运行时类。
获取Class对象的四种方式
//方式一:调用运行时类的属性:.class
Class clazz1 = Person.class;
System.out.println(clazz1);
//方式二:通过运行时类的对象,调用getClass()
Person p1 = new Person();
Class clazz2 = p1.getClass();
System.out.println(clazz2);
//方式三:调用Class的静态方法:forName(String classPath)
Class clazz3 = Class.forName("com.atguigu.java.Person");
// clazz3 = Class.forName("java.lang.String");
System.out.println(clazz3);
System.out.println(clazz1 == clazz2);
System.out.println(clazz1 == clazz3);
//方式四:使用类的加载器:ClassLoader (了解)
ClassLoader classLoader = ReflectionTest.class.getClassLoader();
Class clazz4 = classLoader.loadClass("com.atguigu.java.Person");
System.out.println(clazz4);
System.out.println(clazz1 == clazz4);
三步:
加载:将类的class文件加入到内存,并创建Class对象,此过程由类加载器完成
链接:确保Class文件的字节流中包含的信息符合当前虚拟机的要求,并且不会危害虚拟机自身的安全。
初始化:在编译生成class文件时,编译器会产生两个方法加于class文件中,一个是类的初始化方法clinit, 另一个是实例的初始化方法init。clinit指的是类构造器,主要作用是在类加载过程中的初始化阶段进行执行,执行内容包括静态变量初始化和静态块的执行。
引言
代理模式是23种设计模式中的一种,是比较重要的知识,在Spring框架中比较重要的AOP(Aspect Oriented Programing)也是基于动态代理实现的。
代理的理解
关于代理的理解,我举个例子说明:20年前,我们想要购买一台联想电脑的方式是:我们去联想的工厂,工厂卖给我们电脑,并且提供售后的服务。
在这几十年的发展中,出现了一些经销商,他们从联想工厂进货,然后卖给我们,电脑坏了我们也先去经销商的店里找他,然后他再去找工厂,于是我们和工厂的联系其实就中断了。
2020年,我们想要买一台笔记本电脑,会直接去天猫、苏宁、京东等大型的经销商那里去买一台电脑,出现问题就7天无理由退货。
这里我们就是客户端Client,那些经销商也叫做代理商,联想工厂就叫做被代理对象。这样的过程就是代理。
动态代理
动态代理的特点:字节码随用随创建,随用随加载
动态代理的作用:在不修改源码的基础上增强方法
动态代理的分类:2类,一个是基于接口的动态代理,一个是基于子类的动态代理
基于接口的动态代理
由JDK提供的Proxy类实现,要求被代理类至少实现一个接口。实现过程如下
(1)首先创建Lenovo类并且实现一个接口
package com.alibaba200408.动态代理;
public class Lenovo implements ILenovo {
@Override
public void sale(Double money) {
System.out.println("拿到"+money+"元,电脑发货");
}
@Override
public void afterService(Double money) {
System.out.println("拿到"+money+"元,售后服务开始");
}
}
代码很简单,只涉及了2个方法,一个是销售电脑,一个是维修电脑。
接口的代码如下
package com.alibaba200408.动态代理;
public interface ILenovo {
void sale(Double money);
void afterService(Double money);
}
(2)实例化一个动态代理的对象,通过代理对象调用方法
package com.alibaba200408.动态代理;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
public class Client {
public static void main(String[] args) {
//联想厂家
ILenovo lenovo = new Lenovo();
//创建代理对象
ILenovo proxyInstance = (ILenovo) Proxy.newProxyInstance(lenovo.getClass().getClassLoader(),
lenovo.getClass().getInterfaces(),
new InvocationHandler() {
/**
* invoke方法的作用是调用任何被代理对象的方法都会被这个invoke方法拦截
* proxy:表示当前代理对象的引用
* method:表示当前正在执行的方法
* args:表示当前执行的方法的参数
*
* 返回值就是当前方法的返回值
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//提供增强的代码
Double money = (Double) args[0];
if ("sale".equals(method.getName())){
method.invoke(lenovo, money * 0.8);
}else {
method.invoke(lenovo,args);
}
return null;
}
});
proxyInstance.afterService(10000.0);
}
}
基于子类的动态代理
这种实现方式要求被代理类不能是final的,因为final修饰的类不能有子类,其次这种方式需要第三方的cglib。这里不再赘述了。