Demo

在分析动态代理之前,我们先做一个 Demo,来模拟一下 MyBatis 是如果执行 SQL 的

思路是:

  • 写一个 @Select 注解,注解里面写上 SQL 语句
  • 写一个 SessionFactory,获取 mapper
  • 写一个动态代理,执行 Dao 接口中的 query 方法

    1. @Target(ElementType.METHOD)
    2. @Retention(RetentionPolicy.RUNTIME)
    3. public @interface Select {
    4. String value() default "";
    5. }

    ```java public interface UserDao {

    @Select(“select * from user”) List query();

    }

    1. ```java
    2. public class MyInvocationHandler implements InvocationHandler {
    3. @Override
    4. public Object invoke(Object proxy, Method method, Object[] objects) throws Throwable {
    5. System.out.println("db connection...");
    6. Select annotation = method.getAnnotation(Select.class);
    7. String sql = annotation.value();
    8. System.out.println("execute sql: " + sql);
    9. Class<?> returnType = method.getReturnType();
    10. return null;
    11. }
    12. }
    1. public class SessionFactory {
    2. public static <T> T getMapper(Class<T> clazz) {
    3. return (T) Proxy.newProxyInstance(SessionFactory.class.getClassLoader(),
    4. new Class[]{clazz},
    5. new MyInvocationHandler());
    6. }
    7. }
    1. public static void main(String[] args) {
    2. AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext();
    3. ac.register(AppConfig.class);
    4. ac.refresh();
    5. UserDao mapper = SessionFactory.getMapper(UserDao.class);
    6. mapper.query();
    7. }
    8. // db connection...
    9. // execute sql: select * from user

    这就是一个典型的动态代理案例,相当于我们自己实现了一个接口实现类,并重写了业务逻辑

    什么是代理

    代理就是对原有对象进行了一个“改变”,代理分为静态代理和动态代理,

    静态代理

    静态代理的实现有“继承”和“聚合”两种方法

    继承

    优点:使用简单,缺点:不灵活,当有一个新的需求,就需要新写一个类

    1. public class UserService {
    2. public void find() {
    3. System.out.println("find users");
    4. }
    5. }
    6. public class UserServiceProxyLog extends UserService {
    7. @Override
    8. public void find() {
    9. System.out.println("record log");
    10. super.find();
    11. }
    12. }
    13. public class Test {
    14. public static void main(String[] args) {
    15. UserService userService = new UserServiceProxyLog();
    16. userService.find();
    17. }
    18. }

    聚合

    1. 代理对象和目标对象,都必须实现同一个接口
    2. 代理对象必须包含目标对象
    3. 执行代理对象重写的方法

    优点:层次清晰,缺点:如果业务流程很复杂,代码量就会比较大

    1. public interface Service {
    2. void query();
    3. }
    4. public class UserService implements Service {
    5. @Override
    6. public void query() {
    7. System.out.println("query for user");
    8. }
    9. }
    10. public class ServiceLog implements Service {
    11. Service target;
    12. public ServiceLog(Service target) {
    13. this.target = target;
    14. }
    15. @Override
    16. public void query() {
    17. System.out.println("log");
    18. target.query();
    19. }
    20. }
    21. public class ServicePower implements Service {
    22. Service target;
    23. public ServicePower(Service target) {
    24. this.target = target;
    25. }
    26. @Override
    27. public void query() {
    28. System.out.println("power");
    29. target.query();
    30. }
    31. }
    32. public class Test {
    33. public static void main(String[] args) {
    34. Service proxy = new ServiceLog(new UserService());
    35. proxy.query();
    36. Service proxy1 = new ServicePower(proxy);
    37. proxy1.query();
    38. }
    39. }

    动态代理

    动态代理有 JDK 动态代理和 CGLIB 动态代理,这里我们模拟一下 JDK 的 Proxy

    接口

    1. public interface Service {
    2. void query(String name);
    3. }

    实现类

    我们现在的需求是给实现类加上一个日志方法

    1. public class UserService implements Service {
    2. @Override
    3. public void query(String name) {
    4. System.out.println("query " + name);
    5. }
    6. }

    方法增强

    1. public interface MyInvocationHandler {
    2. void invoke();
    3. }

    动态代理类

    1. public class MyProxy {
    2. public static final String LINE = "\n";
    3. public static final String TAB = "\t";
    4. @SuppressWarnings("unchecked")
    5. public static <T> T getInstance(Object target, MyInvocationHandler handler) {
    6. Class<?> clazz = target.getClass().getInterfaces()[0];
    7. String interfaceName = clazz.getSimpleName();
    8. String packageContent = "package org.eric.proxy;" + LINE;
    9. String importContent = "import " + clazz.getName() + ";" + LINE;
    10. importContent += "import " + MyInvocationHandler.class.getName() + ";" + LINE;
    11. String clazzFirstLineContent = "public class $Proxy implements " + interfaceName + " {" + LINE;
    12. String fieldContent = TAB + "private " + interfaceName + " target;" + LINE;
    13. fieldContent += TAB + "private MyInvocationHandler handler;" + LINE;
    14. String constructorContent = TAB + "public $Proxy(" + interfaceName + " target, MyInvocationHandler handler) {" + LINE;
    15. constructorContent += TAB + TAB + "this.target = target;" + LINE;
    16. constructorContent += TAB + TAB + "this.handler = handler;" + LINE;
    17. constructorContent += TAB + "}" + LINE;
    18. String methodContent = "";
    19. Method[] methods = clazz.getDeclaredMethods();
    20. for (Method method : methods) {
    21. Class<?>[] args = method.getParameterTypes();
    22. String argsContent = "";
    23. String paramsContent = "";
    24. int flag = 0;
    25. for (Class<?> arg : args) {
    26. String temp = arg.getSimpleName();
    27. // String p0, String p1,
    28. argsContent += temp + " p" + flag + ", ";
    29. // p0, p1,
    30. paramsContent += "p" + flag + ", ";
    31. flag++;
    32. }
    33. if (args.length > 0) {
    34. argsContent = argsContent.substring(0, argsContent.lastIndexOf(","));
    35. paramsContent = paramsContent.substring(0, paramsContent.lastIndexOf(","));
    36. }
    37. String returnTypeName = method.getReturnType().getSimpleName();
    38. String methodName = method.getName();
    39. methodContent += TAB + "public " + returnTypeName + " " + methodName + "(" + argsContent + ") {" + LINE;
    40. methodContent += TAB + TAB + "handler.invoke();" + LINE;
    41. methodContent += TAB + TAB + "target." + methodName + "(" + paramsContent + ");" + LINE;
    42. methodContent += TAB + "}" + LINE;
    43. }
    44. String content = packageContent + importContent + clazzFirstLineContent + fieldContent + constructorContent + methodContent + "}";
    45. try {
    46. File file = new File("d:\\org\\eric\\proxy\\$Proxy.java");
    47. if (!file.getParentFile().exists()) {
    48. file.getParentFile().mkdirs();
    49. }
    50. if (!file.exists()) {
    51. file.createNewFile();
    52. }
    53. FileWriter fw = new FileWriter(file);
    54. fw.write(content);
    55. fw.flush();
    56. fw.close();
    57. JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
    58. StandardJavaFileManager fileManager = compiler.getStandardFileManager(null, null, null);
    59. Iterable<? extends JavaFileObject> javaFileObjects = fileManager.getJavaFileObjects(file);
    60. JavaCompiler.CompilationTask t = compiler.getTask(null, fileManager, null, null, null, javaFileObjects);
    61. t.call();
    62. fileManager.close();
    63. URL[] urls = new URL[]{new URL("file:D:\\\\")};
    64. URLClassLoader urlClassLoader = new URLClassLoader(urls);
    65. Class<?> aClass = urlClassLoader.loadClass("org.eric.proxy.$Proxy");
    66. Constructor<?> constructor = aClass.getConstructor(clazz, MyInvocationHandler.class);
    67. return (T) constructor.newInstance(target, handler);
    68. } catch (Exception e) {
    69. e.printStackTrace();
    70. }
    71. return null;
    72. }
    73. }

    测试

    有了动态代理类后,我们就可以用,动态代理类来增强现有实现类的方法

    1. public class Test {
    2. public static void main(String[] args) {
    3. Service target = new UserService();
    4. Service proxy = MyProxy.getInstance(target, new MyInvocationHandler() {
    5. @Override
    6. public void invoke() {
    7. System.out.println("record log...");
    8. }
    9. });
    10. proxy.query("hello");
    11. }
    12. }
    13. // record log...
    14. // query hello

    总结

    动态代理其实就是改变原有实现类的方法,核心是,直接在内存中生成字节码文件,并返回实现类的接口