反射和代理以及Mapper中的应用

8 篇文章 0 订阅
5 篇文章 0 订阅
本文详细介绍了Java中的反射机制,包括如何获取Class对象、创建对象、调用方法等。同时,文章还探讨了代理的概念,分别讲解了静态代理和动态代理(JDK与CGlib)的实现方式,并在Mybatis中展示了Mapper的动态代理实现过程。
摘要由CSDN通过智能技术生成

反射

反射是在jvm运行的过程中,动态的获取类的属性和方法

常见的反射场景:

  1. 编辑器在编辑的时候,动态代码提醒、
  2. 创建数据库连接时用到的 Class.forName("com.mysql.jdbc.Driver);

jvm加载类的过程

  1. java文件会经过编译器编译成 .class 文件
  2. JVM 加载 .class 文件,会在内存中生成代表某个对象的Class 类,就如上面写的Class.forName("com.mysql.jdbc.Driver);  他的返回值就是一个Class 对象。一个类只会有一个Class对象。
  3. 加载结束,会进行类的一些初始化操作。

Class 类

  • 所以要生成对象,首先要有Class类

Class 类的构造函数是私有的,所以我们没有办法直接创建一个Class,不能直接new 一个Class 那就只能通过其他方式获取到一个Class。

  1. 前面说过,Class.forName 会返回一个Class。
  2. Object 类中有getClass() 方法
    public final native Class<?> getClass();
  3. 直接通过静态变量获取
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

https://segmentfault.com/a/1190000011291179

https://www.cnblogs.com/hanganglin/p/4485999.html

  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值