JAVA编程小技巧–代理模式

设计模式这个词相信在座程序员都不会陌生,在编程的成长路线上迈过面向对象的思想后,遇见的应该就是设计模式。在实际的开发工作中,设计模式也是非常重要的,它不仅能让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进行处理,非常感谢!

上一篇 2022年10月1日
下一篇 2022年10月1日

相关推荐