反射
反射是在jvm运行的过程中,动态的获取类的属性和方法
常见的反射场景:
- 编辑器在编辑的时候,动态代码提醒、
- 创建数据库连接时用到的 Class.forName("com.mysql.jdbc.Driver);
jvm加载类的过程
- java文件会经过编译器编译成 .class 文件
- JVM 加载 .class 文件,会在内存中生成代表某个对象的Class 类,就如上面写的Class.forName("com.mysql.jdbc.Driver); 他的返回值就是一个Class 对象。一个类只会有一个Class对象。
- 加载结束,会进行类的一些初始化操作。
Class 类
- 所以要生成对象,首先要有Class类
Class 类的构造函数是私有的,所以我们没有办法直接创建一个Class,不能直接new 一个Class 那就只能通过其他方式获取到一个Class。
- 前面说过,Class.forName 会返回一个Class。
- Object 类中有getClass() 方法
public final native Class<?> getClass();
- 直接通过静态变量获取
Class a = int.class;
Class<Test> testClass = Test.class;
有了Class类,我们就可以对对象进行一些列的操作。
实现反射
//1.获取student的Class类
Class<?> stuClazz = Class.forName("reflect.Student");
// 2.对student初始化,new 一个student出来。
Student student = (Student)stuClazz.newInstance();
//3.获取方法名
Method setName = stuClazz.getMethod("setName", String.class);
//4.给student设置name 为 lant
setName.invoke(student,"lant");
//5.输出 Student{name='lant', age=0, addr='null'}
System.out.println(student.toString());
// 对于私有方法,不能使用getMethod直接调用会抛出NoSuchMethodException 异常
Method setAddr = stuClazz.getMethod("setAddr", String.class);
// 需要使用getDeclaredMethod方法进行调用。 这时又会抛出 IllegalAccessException,Student with modifiers "private" 异常
Method setAddr = stuClazz.getDeclaredMethod("setAddr", String.class);
//需要再代码里面设置权限为true,就可以对私有方法进行访问,
Student{name='lant', age=0, addr='Nanjing'}
setAddr.setAccessible(true);
setAddr.invoke(student,"Nanjing");
System.out.println(student.toString());
以上分别实现了公共方法和私有方法的调用,对于静态方法,可以省略 2 instance的步骤,
反射过程无法执行某些虚拟机优化,所有性能开销会大, 并且通过反射会使类的内部曝光,访问到类的私有属性, 也能给一个 int 类型的数组塞入一个字符串, 会使代码存在一定的安全性。
代理
代理,就是将自己要做的事情,交给别人去做。
代理分为静态代理和动态代理。
静态代理
public interface IStudentService {
public String getUserName();
}
public class StudentImpl implements IStudentService{
@Override
public String getUserName() {
return "StudentImpl";
}
}
public class StudentProxy implements IStudentService{
private IStudentService service;
public StudentProxy(IStudentService service) {
this.service = service;
}
@Override
public String getUserName() {
return "StudentProxy";
}
}
public static void main(String[] args) throws Exception {
IStudentService service = new StudentImpl();
StudentProxy studentProxy = new StudentProxy(service);
System.out.println(studentProxy.getUserName()); //StudentProxy
}
如上,在
StudentProxy实现了代理,但是因为他是要实现service接口的,所以他必须要重新里面所有的方法,这种方式不易维护代码,但是可以保护目标对象,可以在代理类中进行功能拓展。
jdk动态代理。
动态代理需要实现 InvocationHandler 接口,并重写里面的invoke方法。通过 handler 实现对所有的方法进行统一管理。在invoke方法里面,可以对原有的方法进行拓展。
public class StudentJdkHandler implements InvocationHandler {
private Object obj;
public StudentJdkHandler(Object object) {
this.obj = object;
}
/**
*
* @param object 代理的真是对象,
* @param method 需要调用的方法
* @param args 调用方法的入参
* @return
* @throws Throwable
*/
@Override
public Object invoke(Object object, Method method, Object[] args) throws Throwable {
return method.invoke(obj, args);
}
}
public class Test {
public static void main(String[] args) {
IStudentService service = new StudentImpl();
StudentJdkHandler studentJdkHandler = new StudentJdkHandler(service);
/**
* newProxyInstance 三个参数,
*ClassLoader loader 指定由哪个Classloader对象来机型代理对象
*Class<?>[] interfaces, 一个interface对象的数组,将这组接口提供之后,就可以代理实现这些接口
*InvocationHandler h 表示调用方法的时候,需要关联到哪个handler上面去
*
*/
IStudentService proxyInstance =
(IStudentService)Proxy.newProxyInstance(studentJdkHandler.getClass().getClassLoader(),
service.getClass().getInterfaces(),
studentJdkHandler);
System.out.println(proxyInstance.getUserName());
}
}
jdk动态代理是通过反射完成的。jdk动态代理是通过实现接口来实现的,也就是说动态代理的对象必须实现一个或者多个接口。只是我们不需要重写所有的接口了。
cglib动态代理
jdk动态代理是通过实现接口实现的,cglib是通过继承来实现代理的。
public class People {
private String name;
public String say(){
return "people";
}
}
public class CgProxy implements MethodInterceptor {
private Object target;
public CgProxy(Object target) {
this.target = target;
}
// 通过cglib 里面的工具类 创建代理对象
public Object getInstance(Object target) {
Enhancer enhancer= new Enhancer();
// 设置父类为需要代理的类
enhancer.setSuperclass(target.getClass());
// 设置回调, 在CgProxy执行这行代码的时候,将this设置为回调对象, 在这测试代码里就是Student, 在后续 执行的过程中会回调 CgProxy的intercept 方法
enhancer.setCallback(this);
return enhancer.create();
}
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
Object invoke = method.invoke(target, objects);
return invoke;
}
}
public class Test {
public static void main(String[] args) {
People people = new People();
CgProxy proxy = new CgProxy(people);
// 可以理解为 父类 A = new 子类()
People peopleInstance = (People) proxy.getInstance(people);
System.out.println(peopleInstance.say());
}
}
因为cglib是通过继承实现的,所有如果类是无法被继承的,那就没有办法使用cglib,cglib的性能更高一点。
Mybatis中的Mapper实现
- 使用mapper层的时候,我们并不需要实现mapper接口,而是通过绑定 xml 文件实现数据库操作。所以,mapper的实现不是基于cglib实现的。
- 静态代理需要实现接口中的所有方法,显然也不会采用静态代理的方式进行实现。
- 看下是不是jdk动态代理方式
调试代码,找到getMapper方法,一层层执行进来
进来之后,可以看到 getMapper的执行方式
可以看到这里调用了newInstance方法,
进来之后发现,MapperProxy 又执行了instance方法,
查看MapperProxy 后,发现他实现了InvocationHandler接口,并重新了invoke方法。
在invoke方法里面,调用了execute方法
进来之后发现,这里面就是对应了 xml 里面的标签,在这里对sql进行了操作。
可以看到这里是调用了SqlSession的方法,继续向下,发现是DefaultSqlSession 实现了SqlSession,通过这里实现了对数据的操作。
但是还有一个问题,有这么多mapper,到底执行哪个mapper?
再回来看刚才getMapper的方法,这个knownMappers 是一个HashMap, 那就会有初始化的地方,
往下看,这里有一个addMapper的方法,就是从这里初始化的。
从这个addMapper方法一步步向上找,
在 package org.apache.ibatis.builder.xml; 里面,找到了对addMapper的操作,这里调用了Class.forName,参数就是namespace, 也就是我们在 xml 里面写的那个namespace。
在这里进行了初始化之后,将所有的mapper放到knownMappers,key就是 mapper的class,
所以在最初执行getmapper的时候,传入的实际上是一个Class,然后再从knownMappers get出来。获取到对应的mapper,然后再执行invoke
参考内容:
https://zhuanlan.zhihu.com/p/133619880