反射与动态代理的关系

WuYiLong原创大约 3 分钟java反射

先展示下mybatis的动态代理是怎样的

结合上一篇文章mybatis的启动过程

String resource = "mybatis.xml";
        InputStream resourceAsStream = Resources.getResourceAsStream(resource);

        SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(resourceAsStream);
SqlSession sqlSession = factory.openSession();
// 不使用动态代理的情况下
try{
    List<User> userList = sqlSession.selectList("com.wyl.mybatis.dao.UserDao.getUserList");
            System.out.println("**********"+JSON.toJSON(userList));
        }finally {
            sqlSession.close();
        }
        
// 使用动态代理的情况下
 try{
            UserDao userDao = sqlSession.getMapper(UserDao.class);
            List<User> userList = userDao.getUserList();
            System.out.println("**********"+JSON.toJSON(userList));
        }finally {
            sqlSession.close();
        }

这里就是mybatis最原始的代理实现方式,也是用反射机制完成的,

反射

// Test.java
public class Test {

    public void sayHello(String name) {
        System.out.println("*******"+name);
    }
}

//MybatisApplication.java
public class MybatisApplication {
    public static void main(String[] args) throws  Exception{
        Object instance = Class.forName(Test.class.getName()).newInstance();
        Method sayHell = instance.getClass().getMethod("sayHello", String.class);
        sayHell.invoke(instance,"hello");
}

// 再举一个例子
// class.txt
class=com.wyl.mybatis.controller.Test
method=printLog

// Texst.java
public class Test {
    public void printLog(){
        System.out.println("****打印日志***8");
    }
}

//MybatisApplication.java
public class MybatisApplication {

    public static void main(String[] args) throws  Exception{
        Properties properties = new Properties();
        properties.load(new FileReader("src/main/resources/class.txt"));
        String className = properties.getProperty("class");
        String methodName = properties.getProperty("method");
        Class<?> forName = Class.forName(className);
        Method method = forName.getMethod(methodName);
        method.invoke(forName.newInstance());
}

从上面可以知道,反射就是在程序执行之前根本就不知道要加载什么类和什么方法,只有通过程序运行中,动态的调用。

JDK动态代理

// UserDao.java
public interface UserDao {
    void getMoney();
}

// UserDaoImpl.java
@Slf4j
public class UserDaoImpl implements UserDao {

    @Override
    public void getMoney() {
        log.info("******获得大量的美元****");
    }
}

> 需求需要记录人在什么时候获得金钱

// JDKProxy.java
@Slf4j
public class JDKProxy implements InvocationHandler {

    private Object object;
    public JDKProxy(Object object) {
        this.object = object;
    }
    // 创建代理对象
    public  Object createProxy() {
        Object o = Proxy.newProxyInstance(object.getClass().getClassLoader(), object.getClass().getInterfaces(), this);
        return o;
    }
    
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        log.info("*********当前时间->{}", DateUtil.format(LocalDateTime.now(),"YYYY-MM-dd:HH:mm:ss") );
        method.invoke(object, args);
        return null;
    }
}

// MybatisApplication.java
public class MybatisApplication {

    public static void main(String[] args) throws  Exception{
        UserDaoImpl userDao = new UserDaoImpl();
        UserDao proxy= (UserDao) new JDKProxy(userDao).createProxy();
        proxy.getMoney();


运行结果:
11:45:44.330 [main] INFO com.wyl.mybatis.proxy.JDKProxy - *********当前时间->2020-03-10:11:45:44
11:45:44.363 [main] INFO com.wyl.mybatis.dao.impl.UserDaoImpl - ******获得大量的美元****

CGLIB动态代理

// CGLIBProxy.java
@Slf4j
public class CGLIBProxy implements MethodInterceptor {

    private Object object;

    public CGLIBProxy(Object object) {
        this.object = object;
    }

    // 创建代理对象
    public Object createProxy() {
        Object o = Enhancer.create(object.getClass(), this);
        return o;

    }

    @Override
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {

        log.info("*********当前时间->{}", DateUtil.format(LocalDateTime.now(),"YYYY-MM-dd:HH:mm:ss") );
        methodProxy.invoke(object,objects);
        return null;
    }
}


// 先使用上面有接口的测试用例
public class MybatisApplication {
    public static void main(String[] args) throws  Exception{
        UserDaoImpl userDao = new UserDaoImpl();
        UserDao proxy = (UserDao) new CGLIBProxy(userDao).createProxy();
        proxy.getMoney();
    }
    
运行结果:
14:49:32.938 [main] INFO com.wyl.mybatis.proxy.CGLIBProxy - *********当前时间->2020-03-10:14:49:32
14:49:32.973 [main] INFO com.wyl.mybatis.dao.impl.UserDaoImpl - ******获得大量的美元****

// 新建UserController.class
@Slf4j
public class UserController {

    public void getMoney() {
        log.info("**********获得大量的金钱*********");
    }
}

// MybatisApplication.java
public class MybatisApplication {

    public static void main(String[] args) throws  Exception{
        UserController userController = new UserController();
        UserController proxy = (UserController) new CGLIBProxy(userController).createProxy();
        proxy.getMoney();
}

运行结果:
14:53:04.621 [main] INFO com.wyl.mybatis.proxy.CGLIBProxy - *********当前时间->2020-03-10:14:53:04
14:53:04.659 [main] INFO com.wyl.mybatis.controller.UserController - **********获得大量的金钱*********

从这里可以看出无论是否有接口CGLIB代理都可以,那么JDK代理呢?接着测试

public class MybatisApplication {

    public static void main(String[] args) throws  Exception{
        UserController userController = new UserController();
        UserController proxy= (UserController) new JDKProxy(userController).createProxy();
        proxy.getMoney();
}
运行结果:
Exception in thread "main" java.lang.ClassCastException: com.sun.proxy.$Proxy0 cannot be cast to com.wyl.mybatis.controller.UserController
	at com.wyl.mybatis.MybatisApplication.main(MybatisApplication.java:29)

可以看到,出现了类转换时,发生了不兼容的异常,所以说对于JDK动态代理来说,实现类一定要有接口。

总结

JDK动态代理其实是反射机制实现的aop动态代理,实现类一定要有接口;CGLIB动态代理是JDK动态代理的补充。在不用接口的情况下,也可以使用动态代理。

上次编辑于:
贡献者: wuyilong