设计模式这个词相信在座程序员都不会陌生,在编程的成长路线上迈过面向对象的思想后,遇见的应该就是设计模式。在实际的开发工作中,设计模式也是非常重要的,它不仅能让coder可以快速地完成coding工作,也可以让代码结构更为合理。那么今天就与大家分享一下最为常见的一种设计模式–代理模式。
一、什么是代理
‘代理’–我们在生活中也经常使用这种方式,来让我们的生活更为舒适,例如:租房子时,我们会找中介帮我们物色好的房源,而物色房源的过程(联系房主、约定时间等事宜)我们就不需要关心了,一切都有中介来完成,这种操作方式就是代理。
对应到程序中,就是为一个对象提供一个代理对象,这个代理对象持有被代理对象的引用,来达到简化调用或增强原对象功能的目的。
二、为什么用代理
中介隔离
在一些情况下,被调用对象不想对调用者暴露,或者调用者不想或者不能直接引用某个对象,此时可采用代理进行中介隔离。
增强扩展
面向对象编程中,有一个非常重要的原则–开闭原则,“对扩展开放,对修改封闭”;因此,在调用某个对象时应该对其进行扩展,而不是修改;通过代理,可给被调用对象扩展功能,这种实现符合了开闭原则。
三、UML结构示意图
四、代理模式种类
静态代理
静态代理的代理类在编译时生成。静态代理是编码时就已经确定好委托代理关系,是代码编译前就已经确定好的。
public interface Subject { void findHouse();}public class MySubject implements Subject{ @Override public void findHouse() { System.out.println("findHouse in MySubject"); }}public class Main { public static void main(String[] args) { Subject subject = new MySubject(); subject.findHouse(); }}
运行结果:
findHouse in MySubject
此时,租客冒出了一个想法,能不能在找到房子后,打扫一下房子呢?但是,’开闭原则‘不能修改MySubject类,因此增加代理类ProxySubject,代码如下:
public class ProxySubject implements Subject{ private Subject subject; public ProxySubject(Subject subject){ this.subject = subject; } @Override public void findHouse() { System.out.println("before MySubject.findHouse"); this.subject.findHouse(); System.out.println("after MySubject.findHouse"); }}
client代码如下:
public class Main { public static void main(String[] args) { Subject subject = new ProxySubject(new MySubject()); subject.findHouse(); }}
运行结果:
before MySubject.findHousefindHouse in MySubjectafter MySubject.findHouse
这样就在遵从’开闭原则‘的情况下实现了对MySubject类的扩展。目前MySubject中只有一个方法,如果MySubject中方法增加了,这种静态代理构建代码的方式就会造成代理类的代码膨胀,因此需要动态代理登场。
动态代理
动态代理的代理类在java运行时动态生成。主要通过JDK自身的java.lang.reflect包和cglib实现。
JDK动态代理
public interface Subject { public void doSomething();}
public class ProxySubject implements InvocationHandler { public <T> T newInstance(Class<T> clz) { return (T) Proxy.newProxyInstance(clz.getClassLoader(), new Class[] { clz }, this); } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println("befor invoke"); System.out.println("proxy = " + proxy.getClass() + ", method = " + method.getName() + ", args = " + Arrays.deepToString(args)); System.out.println("after inovke"); return null; }}
public class Client { public static void main(String[] args) { ProxySubject proxy = new ProxySubject(); Subject subject = proxy.newInstance(Subject.class); subject.doSomething(); }}
运行结果:
befor invokeproxy = class com.sun.proxy.$Proxy0, method = doSomething, args = nullafter inovke
从运行结果上可以看到,invoke方法的入参proxy便是动态创建出来的代理对象;method便是代理的方法,args为代理方法的参数集合。现在为Subject接口增加一个doSomething1方法,如下:
public interface Subject { public void doSomething(); public void doSomething1();}
client增加执行doSomething1的代码
public class Client { public static void main(String[] args) { ProxySubject proxy = new ProxySubject(); Subject subject = proxy.newInstance(Subject.class); subject.doSomething(); subject.doSomething1(); }}
ProxySubject代码不变
运行结果:
befor invokeproxy = class com.sun.proxy.$Proxy0, method = doSomething, args = nullafter inovkebefor invokeproxy = class com.sun.proxy.$Proxy0, method = doSomething1, args = nullafter inovke
从结果上,无论Subject中的方法如何改动,都不会影响到代理类ProxySubject,可以很好的控制住代理类代码膨胀的问题。
cglib动态代理
public class Subject { public void doSomething(){ System.out.println("doSomething"); }}
public class ProxyFactory implements MethodInterceptor { // 维护目标对象 private Object target; public ProxyFactory(Object target) { this.target = target; } // 给目标对象创建一个代理对象 public Object getProxyInstance() { //1.工具类 Enhancer en = new Enhancer(); //2.设置父类 en.setSuperclass(target.getClass()); //3.设置回调函数 en.setCallback(this); //4.创建子类(代理对象) return en.create(); } @Override public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable { System.out.println("before invoke"); Object returnValue = method.invoke(target, objects); System.out.println("after invoke"); return returnValue; }}
public class Client { public static void main(String[] args) { Subject subject = (Subject) new ProxyFactory(new Subject()).getProxyInstance(); subject.doSomething(); }}
运行结果:
before invokedoSomethingafter invoke
从运行结果看,达到了JDK动态代理一样的效果,不同的是Subject是一个实体类,并非接口类;这也是cglib与jdk代理不同之处,cglib可以代理实体类和接口类,而jdk只能代理接口类。
cglib、JDK动态代理异同
JDK动态代理 |
cglib动态代理 |
|
代理生成机制 |
反射机制生成代理接口的匿名类 |
使用字节码处理框架ASM,加载代理对象class文件,通过修改字节码生成 |
创建代理效率 |
高 |
低 |
执行效率 |
低 |
高 |
代理机制 |
委托机制,只能代理接口类 |
继承机制,被代理类和代理类是继承关系,所以不能代理final修饰的类 |
五、动态代理实际应用
在日常开发工作中用到的许多框架都使用了动态代理,例如:spring AOP、mybatis的mapper接口。下面跟大家分享一下小编最近在工作中对动态代理的实际应用。
需求:
对接10几个接口;
接口请求都是get方法;
请求路径均为:/xxx/xxx+params
在梳理完需求后,想到用动态代理实现一个类似mybatis mapper不用写实现类的方式,实现远程api调用。
演示代码涉及两个项目,一是作为Api服务,用来模拟远程调用的api接口,代码如下:
@RestController@RequestMapping("/api")public class ApiController { @GetMapping("/test1") @ResponseBody public Object test1(){ return "test1"; } @GetMapping("/test2") @ResponseBody public Object test2(String userName){ return "test2" + userName; }}
以上代码主要是模拟/api/test1和/api/test2?userName=xxx的两个接口,比较简单没什么好说的。
下面是访问Api接口的client端程序代码,就命名为ProxyClient(以下简称client)。
首先,定义了一个注解类,一是用于标记需要扫描加载的Apiservice接口(就是要代理的接口类),二是设置一些请求参数。代码如下:
@Target({ElementType.TYPE, ElementType.METHOD})@Retention(RetentionPolicy.RUNTIME)@Documentedpublic @interface ApiAnnotation { String url () default "";}
创建接口类IApiservice
@ApiAnnotationpublic interface IApiService { @ApiAnnotation(url = "/api") public String test1(); @ApiAnnotation(url = "/api") public String test2(String userName);}
定义的方法名与api服务程序的接口名一致,参数名也与请求的参数名一致。
代理类代码如下:
@Componentpublic class ApiProxy implements InvocationHandler { private String server="http://127.0.0.1:8080"; @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { StringBuilder url = new StringBuilder(server); url.append(method.getAnnotation(ApiAnnotation.class).url()); url.append("/"); url.append(method.getName()); Parameter[] parameters = method.getParameters(); if(parameters.length > 0){ url.append("?"); for (int i= 0;i<parameters.length;i++) { url.append(parameters[i].getName()); url.append("="); url.append(args[i]); if(i < parameters.length -1){ url.append("&"); } } } return HttpUtils.doGet(url.toString() ,null,10); }}
为了方便管理,决定将代理类和IApiService交给spring容器管理,编写ProxyFactory用于创建代理类,代码如下:
public class ProxyFactory<T> implements FactoryBean<T> { @Resource private ApiProxy apiProxy; private Class<T> interfaceClass; public Class<T> getInterfaceClass() { return interfaceClass; } public void setInterfaceClass(Class<T> interfaceClass) { this.interfaceClass = interfaceClass; } @Override public T getObject() throws Exception { final Class[] interfaces = {interfaceClass}; return (T) Proxy.newProxyInstance(this.getClass().getClassLoader(), interfaces, apiProxy); } @Override public Class<?> getObjectType() { return interfaceClass; } @Override public boolean isSingleton() { return true; }}
编写好以上代码,还需要将IApiService和工厂类扫描注入到容器,因此,编写配置类代码
@Componentpublic class ApiConfig implements BeanDefinitionRegistryPostProcessor { @Override public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException { //扫描包下所有ApiAnnotation注解的类,并注册到容器中 Set<Class<?>> scanPackage = ClassUtil.scanPackageByAnnotation("com.xz.proxyclient", ApiAnnotation.class); for (Class<?> cls : scanPackage) { BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(cls); GenericBeanDefinition definition = (GenericBeanDefinition) builder.getRawBeanDefinition(); definition.getPropertyValues().add("interfaceClass", definition.getBeanClassName()); definition.setBeanClass(ProxyFactory.class); definition.setAutowireMode(GenericBeanDefinition.AUTOWIRE_BY_TYPE); String beanName = StrUtil.removePreAndLowerFirst(cls.getSimpleName(), 0); registry.registerBeanDefinition(beanName, definition); } } @Override public void postProcessBeanFactory(ConfigurableListableBeanFactory configurableListableBeanFactory) throws BeansException { }}
最后,编写一个接口,用于测试
@RestController@RequestMappingpublic class TestController { @Resource private IApiService apiService; @GetMapping("/hello") @ResponseBody public Object hello(String userName){ String str1 = apiService.test1(); String str2 = apiService.test2("xxx"); return "hello:" + str1 + " | " + str2; }}
将client程序启动端口配置8181,ApiServer配置为8080,启动运行。。。。
浏览器访问:
http://localhost:8181/hello
结果显示:
hello:test1 | test2xxx
写在最后:
相信大多数的程序员日常的编码工作都是非常枯燥的curd,能实现业务逻辑就ok了。没有办法这是多数程序员的真是写照,也是我的真实写照,但是我们可以努力的去从中寻找一些乐趣,比如采用一些设计模式来优化业务代码。欢迎大家分享一些开发中的小技巧,大家互相交流,共同进步!!!代理模式就和大家分享到这里。
声明:本站部分文章及图片源自用户投稿,如本站任何资料有侵权请您尽早请联系jinwei@zod.com.cn进行处理,非常感谢!