JAVA编程小技巧--代理模式

设计模式这个词相信在座程序员都不会陌生,在编程的成长路线上迈过面向对象的思想后,遇见的应该就是设计模式。在实际的开发工作中,设计模式也是非常重要的,它不仅能让coder可以快速地完成coding工作,也可以让代码结构更为合理。那么今天就与大家分享一下最为常见的一种设计模式–代理模式。

一、什么是代理

‘代理’–我们在生活中也经常使用这种方式,来让我们的生活更为舒适,例如:租房子时,我们会找中介帮我们物色好的房源,而物色房源的过程(联系房主、约定时间等事宜)我们就不需要关心了,一切都有中介来完成,这种操作方式就是代理。

对应到程序中,就是为一个对象提供一个代理对象,这个代理对象持有被代理对象的引用,来达到简化调用或增强原对象功能的目的。

二、为什么用代理

中介隔离

在一些情况下,被调用对象不想对调用者暴露,或者调用者不想或者不能直接引用某个对象,此时可采用代理进行中介隔离。

增强扩展

面向对象编程中,有一个非常重要的原则–开闭原则,“对扩展开放,对修改封闭”;因此,在调用某个对象时应该对其进行扩展,而不是修改;通过代理,可给被调用对象扩展功能,这种实现符合了开闭原则。

三、UML结构示意图

image-20221027154038597

四、代理模式种类

静态代理

静态代理的代理类在编译时生成。静态代理是编码时就已经确定好委托代理关系,是代码编译前就已经确定好的。

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.findHouse
findHouse in MySubject
after 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 invoke
proxy = class com.sun.proxy.$Proxy0, method = doSomething, args = null
after 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 invoke
proxy = class com.sun.proxy.$Proxy0, method = doSomething, args = null
after inovke
befor invoke
proxy = class com.sun.proxy.$Proxy0, method = doSomething1, args = null
after 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 invoke
doSomething
after 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)
@Documented
public @interface ApiAnnotation {
    String url () default "";
}

创建接口类IApiservice

@ApiAnnotation
public interface IApiService {

    @ApiAnnotation(url = "/api")
    public String test1();

    @ApiAnnotation(url = "/api")
    public String test2(String userName);
}

定义的方法名与api服务程序的接口名一致,参数名也与请求的参数名一致。

代理类代码如下:

@Component
public 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和工厂类扫描注入到容器,因此,编写配置类代码

@Component
public 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
@RequestMapping
public 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了。没有办法这是多数程序员的真是写照,也是我的真实写照,但是我们可以努力的去从中寻找一些乐趣,比如采用一些设计模式来优化业务代码。欢迎大家分享一些开发中的小技巧,大家互相交流,共同进步!!!代理模式就和大家分享到这里。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值