当前位置:   article > 正文

设计模式--代理模式

设计模式--代理模式

代理模式(Proxy Pattern)是指为其他对象提供一种代理,以控制对这个对象的访问,属于结构型模式。在某些情况下,一个对象不适合或者不能直接引用另一个对象,而代理对象可以在客户端和目标对象之间起到中介的作用。代理模式分为静态代理和动态代理。

使用代理模式的主要目的是:保护目标对象;增强目标对象。

代理模式的通用写法

  1. interface ISubject{
  2. void resquest();
  3. }
  4. class RealSubject implements ISubject{
  5. @Override
  6. public void resquest() {
  7. System.out.println("real service is called.");
  8. }
  9. }
  10. class Proxy implements ISubject{
  11. private ISubject subject;
  12. public Proxy(ISubject subject){
  13. this.subject = subject;
  14. }
  15. @Override
  16. public void resquest() {
  17. before();
  18. subject.resquest();
  19. after();
  20. }
  21. public void before(){
  22. System.out.println("called before request().");
  23. }
  24. public void after(){
  25. System.out.println("called after request().");
  26. }
  27. }
  28. public class Test {
  29. public static void main(String[] args) {
  30. Proxy proxy = new Proxy(new RealSubject());
  31. proxy.resquest();
  32. }
  33. }

由静态代理到动态代理

在Java生态中,目前最普遍使用的是JDK自带的代理和Cglib提供的类库。

首先,采用JDK的动态代理优化上述代码:

  1. import java.lang.reflect.InvocationHandler;
  2. import java.lang.reflect.Method;
  3. import java.lang.reflect.Proxy;
  4. interface ISubject{
  5. void resquest();
  6. }
  7. class RealSubject implements ISubject{
  8. @Override
  9. public void resquest() {
  10. System.out.println("real service is called.");
  11. }
  12. }
  13. /**
  14. * JDK动态代理
  15. */
  16. class ProxyJDK implements InvocationHandler {
  17. private ISubject subject;
  18. public ISubject getInstance(ISubject subject){
  19. this.subject = subject;
  20. Class<?> clazz = subject.getClass();
  21. return (ISubject) Proxy.newProxyInstance(clazz.getClassLoader(),clazz.getInterfaces(),this);
  22. }
  23. public void before(){
  24. System.out.println("called before request().");
  25. }
  26. public void after(){
  27. System.out.println("called after request().");
  28. }
  29. @Override
  30. public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
  31. before();
  32. Object result = method.invoke(this.subject, args);
  33. after();
  34. return result;
  35. }
  36. }
  37. public class Test {
  38. public static void main(String[] args) {
  39. ISubject instance = new ProxyJDK().getInstance(new RealSubject());
  40. instance.resquest();
  41. }
  42. }

手动实现JDK动态代理:

JDK 动态代理采用字节码重组,重新生成对象来替代原始对象,以达到动态代理的目的。JDK 动态代理生成对象的步骤如下:(JDK代理通过反射调用)

  • 获取被代理对象的引用,并且获取它的所有接口,反射获取;
  • JDK 动态代理类重新生成一个新的类,同时新的类要实现被代理类实现的所有接口;
  • 动态生成 Java 代码,新加的业务逻辑方法由一定的逻辑代码调用(在代码中体现);
  • 编译新生成的 Java 代码.class 文件;
  • 重新加载到 JVM 中运行。
  1. /**
  2. * 自己实现JDK动态代理
  3. */
  4. interface GPInvocationHandler{
  5. public Object invoke(Object proxy, Method method, Object[] args) throws Throwable;
  6. }
  7. /**
  8. * 用来生成源码的工具类
  9. */
  10. class GPProxy{
  11. public static final String ln = "\r\n";
  12. public static Object newProxyInstance(GPClassLoader classLoader, Class<?>[] interfaces, GPInvocationHandler h){
  13. try{
  14. //动态生成源代码.java文件
  15. String src = generateSrc(interfaces);
  16. //2、Java文件输出磁盘
  17. String filePath = URLDecoder.decode(GPProxy.class.getResource("").getPath(), "UTF-8");
  18. System.out.println("GpProxy filePath: "+filePath);
  19. File f = new File(filePath + "$Proxy0.java");
  20. System.out.println("GpProxy File: "+f);
  21. FileWriter fw = new FileWriter(f);
  22. fw.write(src);
  23. fw.flush();
  24. fw.close();
  25. //3、把生成的.java文件编译成.class文件
  26. JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
  27. StandardJavaFileManager manage = compiler.getStandardFileManager(null, null, null);
  28. Iterable iterable = manage.getJavaFileObjects(f);
  29. JavaCompiler.CompilationTask task = compiler.getTask(null, manage, null, null, null, iterable);
  30. task.call();
  31. manage.close();
  32. //4、编译生成的.class文件加载到JVM中来
  33. Class proxyClass = classLoader.findClass("$Proxy0", interfaces);
  34. Constructor c = proxyClass.getConstructor(GPInvocationHandler.class);
  35. System.out.println("GpProxy Constructor: "+c );
  36. f.delete();
  37. //5、返回字节码重组以后的新的代理对象
  38. return c.newInstance(h);
  39. }catch (Exception e){
  40. e.printStackTrace();
  41. }
  42. return null;
  43. }
  44. private static String generateSrc(Class<?>[] interfaces){
  45. StringBuffer sb = new StringBuffer();
  46. sb.append("import java.lang.reflect.*;" + ln);
  47. sb.append("class $Proxy0 implements " + interfaces[0].getName() + "{" + ln);
  48. sb.append("GPInvocationHandler h;" + ln);
  49. sb.append("public $Proxy0(GPInvocationHandler h) { " + ln);
  50. sb.append("this.h = h;");
  51. sb.append("}" + ln);
  52. for (Method m : interfaces[0].getMethods()) {
  53. Class<?>[] params = m.getParameterTypes();
  54. StringBuffer paramNames = new StringBuffer();
  55. StringBuffer paramValues = new StringBuffer();
  56. StringBuffer paramClasses = new StringBuffer();
  57. for (int i = 0; i < params.length; i++) {
  58. Class clazz = params[i];
  59. String type = clazz.getName();
  60. String paramName = toLowerFirstCase(clazz.getSimpleName());
  61. paramNames.append(type + " " + paramName);
  62. paramValues.append(paramName);
  63. paramClasses.append(clazz.getName() + ".class");
  64. if (i > 0 && i < params.length - 1) {
  65. paramNames.append(",");
  66. paramValues.append(",");
  67. paramClasses.append(",");
  68. }
  69. }
  70. System.out.println("paramNames: "+paramNames);
  71. System.out.println("paramValues: "+paramValues);
  72. System.out.println("paramClasses: "+paramClasses);
  73. sb.append("public " + m.getReturnType().getName() + " " + m.getName()
  74. + "(" + paramNames.toString() + ") {" + ln);
  75. sb.append("try{" + ln);
  76. sb.append("Method m = " + interfaces[0].getName() + ".class.getMethod(\""
  77. + m.getName() + "\",new Class[]{" + paramClasses.toString() + "});" + ln);
  78. sb.append((hasReturnValue(m.getReturnType()) ? "return " : "")
  79. + getCaseCode("this.h.invoke(this,m,new Object[]{" + paramValues + "})", m.getReturnType())
  80. + ";" + ln);
  81. sb.append("}catch(Error _ex) { }");
  82. sb.append("catch(Throwable e){" + ln);
  83. sb.append("throw new UndeclaredThrowableException(e);" + ln);
  84. sb.append("}");
  85. sb.append(getReturnEmptyCode(m.getReturnType()));
  86. sb.append("}");
  87. }
  88. sb.append("}" + ln);
  89. return sb.toString();
  90. }
  91. private static Map<Class,Class> mappings = new HashMap<>();
  92. static{
  93. mappings.put(int.class,Integer.class);
  94. }
  95. private static String getReturnEmptyCode(Class<?> returnClass) {
  96. if (mappings.containsKey(returnClass)) {
  97. return "return 0;";
  98. } else if (returnClass == void.class) {
  99. return "";
  100. } else {
  101. return "return null;";
  102. }
  103. }
  104. private static String getCaseCode(String code, Class<?> returnClass) {
  105. if (mappings.containsKey(returnClass)) {
  106. return "((" + mappings.get(returnClass).getName() + ")" + code + ")."
  107. + returnClass.getSimpleName() + "Values()";
  108. }
  109. System.out.println("code: "+code);
  110. return code;
  111. }
  112. private static boolean hasReturnValue(Class<?> clazz) {
  113. return clazz != void.class;
  114. }
  115. private static String toLowerFirstCase(String src) {
  116. char[] chars = src.toCharArray();
  117. chars[0] += 32;
  118. return String.valueOf(chars);
  119. }
  120. }
  121. class GPClassLoader extends ClassLoader{
  122. private File classPathFile;
  123. public GPClassLoader(){
  124. String classPath = GPClassLoader.class.getResource("").getPath();
  125. this.classPathFile = new File(classPath);
  126. }
  127. protected Class<?> findClass(String name, Class<?>[] interfaces)throws ClassNotFoundException {
  128. String className;
  129. if(StringUtils.isNotBlank(GPClassLoader.class.getPackage().getName())){
  130. className = GPClassLoader.class.getPackage().getName() + "." + name;
  131. }else{
  132. className = name;
  133. }
  134. if(classPathFile != null){
  135. File classFile = new File(classPathFile,name.replaceAll("\\.","/") + ".class");
  136. if(classFile.exists()){
  137. FileInputStream in = null;
  138. ByteArrayOutputStream out = null;
  139. try{
  140. in = new FileInputStream(classFile);
  141. out = new ByteArrayOutputStream();
  142. byte[] buff = new byte[1024];
  143. int len;
  144. while((len=in.read(buff)) != -1){
  145. out.write(buff,0,len);
  146. }
  147. return defineClass(className,out.toByteArray(),0,out.size());
  148. }catch (Exception e){
  149. e.printStackTrace();
  150. }finally {
  151. if(null != in){
  152. try {
  153. in.close();
  154. } catch (IOException e) {
  155. e.printStackTrace();
  156. }
  157. }
  158. if(null != out){
  159. try {
  160. out.close();
  161. } catch (IOException e) {
  162. e.printStackTrace();
  163. }
  164. }
  165. }
  166. }
  167. }
  168. return null;
  169. }
  170. }
  171. /**
  172. * JDK动态代理
  173. */
  174. class ProxyJDK2 implements GPInvocationHandler {
  175. private ISubject subject;
  176. public ISubject getInstance(ISubject subject){
  177. this.subject = subject;
  178. Class<?> clazz = subject.getClass();
  179. return (ISubject) GPProxy.newProxyInstance(new GPClassLoader(),clazz.getInterfaces(),this);
  180. }
  181. public void before(){
  182. System.out.println("called before request().");
  183. }
  184. public void after(){
  185. System.out.println("called after request().");
  186. }
  187. @Override
  188. public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
  189. before();
  190. Object result = method.invoke(this.subject, args);
  191. after();
  192. return result;
  193. }
  194. }
  195. public class Test {
  196. public static void main(String[] args) {
  197. // ISubject instance = new ProxyJDK().getInstance(new RealSubject());
  198. // System.out.println(instance.getClass());
  199. // instance.resquest();
  200. ISubject instance2 = new ProxyJDK2().getInstance(new RealSubject());
  201. System.out.println(instance2.getClass());
  202. instance2.resquest();
  203. }
  204. }

注意:ISubject接口必须单独放一个文件里,不然会报错:class $Proxy0 cannot access its superinterface ISubject ($Proxy0 is in unnamed module of loader GPClassLoader @7d4793a8; ISubject is in unnamed module of loader 'app'),应该是保护域问题ProtectionDomain。反射机制的应用必须要求该类是public访问权限的。有朋友可能会说,可以调用setAccessible(true)方式来改变可见性,但这是一个概念混淆导致的错误。setAccessible(true)函数是反射机制用于改变类内属性访问权限的,而不是改变类本身的可见性。

CGLib动态代理

CGlib代理的目标对象不需要实现任何接口,它通过动态继承目标对象实现动态代理。

  1. /**
  2. * CGLib创建动态代理
  3. * 这是由于 JDK 8 中有关反射相关的功能自从 JDK 9 开始就已经被限制了,为了兼容原先的版本,需要在运行项目时添加 --add-opens java.base/java.lang=ALL-UNNAMED 选项来开启这种默认不被允许的行为。
  4. */
  5. class ProxyCGLib implements MethodInterceptor {
  6. public Object getInstance(Class<?> clazz){
  7. Enhancer enhancer = new Enhancer();
  8. //要把设置为即将生成的新类的父类
  9. enhancer.setSuperclass(clazz);
  10. enhancer.setCallback(this);
  11. return enhancer.create();
  12. }
  13. @Override
  14. public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
  15. before();
  16. Object obj = methodProxy.invokeSuper(o,objects);
  17. after();
  18. return obj;
  19. }
  20. public void before(){
  21. System.out.println("called before request().");
  22. }
  23. public void after(){
  24. System.out.println("called after request().");
  25. }
  26. }
  27. public class Test {
  28. public static void main(String[] args) {
  29. ISubject instance = (ISubject) new ProxyCGLib().getInstance(RealSubject.class);
  30. instance.resquest();
  31. }
  32. }

CGLib和JDK动态代理对比

  • JDK动态代理实现了被代理对象的接口,CGLib代理继承了被代理对象
  • JDK动态代理和CGLib代理都在运行期生成字节码,JDK动态代理直接写Class字节码,CGLib代理使用ASM框架写Class字节码,CGLib代理实现更复杂,生成代理类比JDK动态代理效率低
  • JDK动态代理调用代理方法是通过反射机制调用的,CGLib代理是通过FastClass机制调用方法,CGLib效率更高。

代理模式的优缺点:

优点:

  • 代理模式能将代理对象与真实被调用目标对象分离
  • 在一定程度上降低了系统的耦合性,扩展性好
  • 可以起到保护目标对象的作用
  • 可以增强目标对象的功能

缺点:

  • 代理模式会造成系统设计中类的数量
  • 在客户端和目标对象中增加一个代理对象,会导致请求速度变慢
  • 增加了系统的复杂性。
声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/我家小花儿/article/detail/138064
推荐阅读
相关标签
  

闽ICP备14008679号