反射与动态代理的关系

1,535 阅读7分钟

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

结合上一篇文章实践的

 1String resource = "mybatis.xml";
2        InputStream resourceAsStream = Resources.getResourceAsStream(resource);
3
4        SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(resourceAsStream);
5SqlSession sqlSession = factory.openSession();
6// 不使用动态代理的情况下
7try{
8    List<User> userList = sqlSession.selectList("com.wyl.mybatis.dao.UserDao.getUserList");
9            System.out.println("**********"+JSON.toJSON(userList));
10        }finally {
11            sqlSession.close();
12        }
13
14// 使用动态代理的情况下
15 try{
16            UserDao userDao = sqlSession.getMapper(UserDao.class);
17            List<User> userList = userDao.getUserList();
18            System.out.println("**********"+JSON.toJSON(userList));
19        }finally {
20            sqlSession.close();
21        }

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

反射

 1// Test.java
2public class Test {
3
4    public void sayHello(String name) {
5        System.out.println("*******"+name);
6    }
7}
8
9//MybatisApplication.java
10public class MybatisApplication {
11    public static void main(String[] args) throws  Exception{
12        Object instance = Class.forName(Test.class.getName()).newInstance();
13        Method sayHell = instance.getClass().getMethod("sayHello", String.class);
14        sayHell.invoke(instance,"hello");
15}
16
17// 再举一个例子
18// class.txt
19class=com.wyl.mybatis.controller.Test
20method=printLog
21
22// Texst.java
23public class Test {
24    public void printLog(){
25        System.out.println("****打印日志***8");
26    }
27}
28
29//MybatisApplication.java
30public class MybatisApplication {
31
32    public static void main(String[] args) throws  Exception{
33        Properties properties = new Properties();
34        properties.load(new FileReader("src/main/resources/class.txt"));
35        String className = properties.getProperty("class");
36        String methodName = properties.getProperty("method");
37        Class<?> forName = Class.forName(className);
38        Method method = forName.getMethod(methodName);
39        method.invoke(forName.newInstance());
40}

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

JDK动态代理

 1// UserDao.java
2public interface UserDao {
3    void getMoney();
4}
5
6// UserDaoImpl.java
7@Slf4j
8public class UserDaoImpl implements UserDao {
9
10    @Override
11    public void getMoney() {
12        log.info("******获得大量的美元****");
13    }
14}
15
16> 需求需要记录人在什么时候获得金钱
17
18// JDKProxy.java
19@Slf4j
20public class JDKProxy implements InvocationHandler {
21
22    private Object object;
23    public JDKProxy(Object object) {
24        this.object = object;
25    }
26    // 创建代理对象
27    public  Object createProxy() {
28        Object o = Proxy.newProxyInstance(object.getClass().getClassLoader(), object.getClass().getInterfaces(), this);
29        return o;
30    }
31
32    @Override
33    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
34        log.info("*********当前时间->{}", DateUtil.format(LocalDateTime.now(),"YYYY-MM-dd:HH:mm:ss") );
35        method.invoke(object, args);
36        return null;
37    }
38}
39
40// MybatisApplication.java
41public class MybatisApplication {
42
43    public static void main(String[] args) throws  Exception{
44        UserDaoImpl userDao = new UserDaoImpl();
45        UserDao proxy= (UserDao) new JDKProxy(userDao).createProxy();
46        proxy.getMoney();
47
48
49运行结果:
5011:45:44.330 [main] INFO com.wyl.mybatis.proxy.JDKProxy - *********当前时间->2020-03-10:11:45:44
5111:45:44.363 [main] INFO com.wyl.mybatis.dao.impl.UserDaoImpl - ******获得大量的美元****

CGLIB动态代理

 1// CGLIBProxy.java
2@Slf4j
3public class CGLIBProxy implements MethodInterceptor {
4
5    private Object object;
6
7    public CGLIBProxy(Object object) {
8        this.object = object;
9    }
10
11    // 创建代理对象
12    public Object createProxy() {
13        Object o = Enhancer.create(object.getClass(), this);
14        return o;
15
16    }
17
18    @Override
19    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
20
21        log.info("*********当前时间->{}", DateUtil.format(LocalDateTime.now(),"YYYY-MM-dd:HH:mm:ss") );
22        methodProxy.invoke(object,objects);
23        return null;
24    }
25}
26
27
28// 先使用上面有接口的测试用例
29public class MybatisApplication {
30    public static void main(String[] args) throws  Exception{
31        UserDaoImpl userDao = new UserDaoImpl();
32        UserDao proxy = (UserDao) new CGLIBProxy(userDao).createProxy();
33        proxy.getMoney();
34    }
35
36运行结果:
3714:49:32.938 [main] INFO com.wyl.mybatis.proxy.CGLIBProxy - *********当前时间->2020-03-10:14:49:32
3814:49:32.973 [main] INFO com.wyl.mybatis.dao.impl.UserDaoImpl - ******获得大量的美元****
39
40// 新建UserController.class
41@Slf4j
42public class UserController {
43
44    public void getMoney() {
45        log.info("**********获得大量的金钱*********");
46    }
47}
48
49// MybatisApplication.java
50public class MybatisApplication {
51
52    public static void main(String[] args) throws  Exception{
53        UserController userController = new UserController();
54        UserController proxy = (UserController) new CGLIBProxy(userController).createProxy();
55        proxy.getMoney();
56}
57
58运行结果:
5914:53:04.621 [main] INFO com.wyl.mybatis.proxy.CGLIBProxy - *********当前时间->2020-03-10:14:53:04
6014:53:04.659 [main] INFO com.wyl.mybatis.controller.UserController - **********获得大量的金钱*********
61
62从这里可以看出无论是否有接口CGLIB代理都可以,那么JDK代理呢?接着测试
63
64public class MybatisApplication {
65
66    public static void main(String[] args) throws  Exception{
67        UserController userController = new UserController();
68        UserController proxy= (UserController) new JDKProxy(userController).createProxy();
69        proxy.getMoney();
70}
71运行结果:
72Exception in thread "main" java.lang.ClassCastException: com.sun.proxy.$Proxy0 cannot be cast to com.wyl.mybatis.controller.UserController
73    at com.wyl.mybatis.MybatisApplication.main(MybatisApplication.java:29)
74
75可以看到,出现了类转换时,发生了不兼容的异常,所以说对于JDK动态代理来说,实现类一定要有接口。

总结

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