反射与动态代理的关系
原创大约 3 分钟
先展示下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动态代理的补充。在不用接口的情况下,也可以使用动态代理。