代理模式及spring使用的代理模式分析

代理模式及spring使用的代理模式分析,第1张

代理模式及spring使用的代理模式分析

什么是代理

代理(Proxy)是一种设计模式, 提供了对目标对象另外的访问方式,即通过代理访问目标对象。 这样好处:可以做到在不修改目标对象的功能前提下,对目标对象的功能进行扩展。*。(扩展目标对象的功能)。

静态代理: 介绍

静态代理:通过事先写好代理类,其实现了公共接口和内部维护真实角色,在真实角色的基础上做一些功能扩展,可通过代理类调用真实角色的方法

静态代理中涉及到的角色:

抽象角色:一般使用接口或者抽象类来实现真实角色:被代理的对象代理角色:代理真实角色,做一些附属的 *** 作客户:使用代理角色来进行一些 *** 作 实例

//抽象接口:租房
public interface Rent {
    public void rent();
}
//真实角色:房东-->租房
public class Host implements Rent{
    public void rent() {
        System.out.println("房屋出租");
    }
}

//代理类:中介
public class Proxy implements Rent{
    // 接收保存目标对象【真正做事的还是真实角色Host】,因此需要维护Host的引用
    Host host;
    public Proxy(Host host){this.host = host;}
    public void rent() {
        seeHouse();//看房
        host.rent();    执行目标对象的方法
        fare();//收费
    }

    public void seeHouse(){
        System.out.println("带房客看房");
    }
    public void fare(){
        System.out.println("收中介费");
    }

}

//客户类:通过代理类执行一系列业务
public class Client {
    public static void main(String[] args) {
        // 目标对象
        Host host = new Host(); 
        // 代理
        Proxy proxy = new Proxy(host);
        proxy.rent(); // 执行的是,代理的方法
    }
}

静态代理优缺点

优点:

可以使真实角色更加纯粹,不在去关注一些公共的事情

公共的业务由代理来完成,完成业务的分工

公共业务发生扩展时更加集中方便

缺点:

如果接口改了,代理的也要跟着改

因为代理对象,需要与目标对象实现一样的接口,所以会有很多代理类,增加了代码量

在编译期就将代理类和目标对象确立下来,不利于程序的扩展。

动态代理 介绍

动态代理:代理类编写时不指定被代理的类是谁,代理类是动态生成的,通过java反射机制,在运行时获取某个目标类的接口,并创建代理类

动态代理分为两类:基于接口的动态代理;基于类的动态代理

基于接口的动态代理—JDK API动态代理基于类的动态代理–cglib动态代理 JDK API 动态代理:

被代理类和代理类都实现同一套接口,代理类先看代理的是谁,运行时把代理类加载进来,被代理类实现的接口代理类也实现了,从而可对目标对象的方法进行调用和扩展

两个重要的类:

**Proxy类:**他是所有动态代理类的超类,调用它的静态方法newProxyInstance()可以生成目标对象的代理对象

    + public static Object newProxyInstance(ClassLoader loader,Class[] interfaces,InvocationHandler h)
    
    - 参数一:生成代理对象使用哪个类装载器【一般我们使用的是代理类的装载器】
    - 参数二:要代理的对象,通过接口指定【指定要代理类的接口】
    - 参数三:要将方法调用分派到的调用处理程序【可自行实现Invocationhandler接口传入】

InvocationHandler:【调用处理程序】每个代理实例都有一个关联的调用处理程序。 当在代理实例上调用方法时,方法调用被编码并分发给其调用处理程序的invoke方法来执行并返回结果。

InvocationHandler是JDK动态代理的核心,生成的代理对象的方法调用都会委托到InvocationHandler.invoke()方法

+ invoke()是InvocationHandler接口唯一的一个方法
+ public Object invoke(Object proxy, Method method, Object[] args)

- 参数一:the proxy instance that the method was invoked on (正在被调用的方法的代理实例)
- 参数二:instance corresponding to the interface method invoked on the proxy instance.(method对应于调用代理实例上的接口方法的实例)
- 参数三args:当前被调用的代理实例方法中的参数

注意:

代理对象拥有目标对象相同的方法【因为参数二指定了对象的接口】用户调用代理对象的什么方法,调用的都是处理器的invoke方法。使用JDK动态代理必须要有接口【参数二需要接口】 实例

//抽象接口和真实角色用的还是上面那个
//代理类
public class JdkProxy implements InvocationHandler {
     // 接收保存目标对象
    private Object target;
    public void setRent(Rent rent){this.target = rent;}
    
    //生成代理类
    public Object  getJdkProxt(){return Proxy.newProxyInstance(this.getClass().getClassLoader(),target.getClass().getInterfaces(),this);
//
    }
    
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        seeHouse();
        //核心:本质利用反射实现
        Object result = method.invoke(target, args);
        fare();
        return result;
    }

    public void seeHouse(){
        System.out.println("带房客看房");
    }
    
    public void fare(){
        System.out.println("收中介费");
    }
}

//上述代码还可以换一种写法,不用实现InvocationHandler接口,直接在newInstace()中传入InvocationHandler的匿名类匿名对象来绑定调用处理程序
public class JdkProxy2 {
    Object target;
    public void setRent(Rent rent){this.target = rent;}
    public Object  getJdkProxt(){return Proxy.newProxyInstance(this.getClass().getClassLoader(),target.getClass().getInterfaces(),this);
    }
    public Object getProxy(){
        return Proxy.newProxyInstance(this.getClass().getClassLoader(), target.getClass().getInterfaces(), new InvocationHandler() {
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                seeHouse();
                Object result = method.invoke(target, args);
                fare();
                return result;
            }
        })
                
    }
    public void seeHouse(){
        System.out.println("带房客看房");
    }
    public void fare(){
        System.out.println("收中介费");
    }
}


//客户端
public class Client {
    public static void main(String[] args) {
        Host host = new Host();
        JdkProxy jdkProxy = new JdkProxy();
        jdkProxy.setRent(host);
        Rent proxy = (Rent)jdkProxy.getProxt();
        proxy.rent();
    }
}
cglib动态代理 介绍

JDK动态代理是必须实现接口才能进行代理,如果一个类没实现接口,就不能使用Proxy动态代理。此时可以使用CGLIB(Code Generation Library)动态代理,来实现增强。

cglib【code generator bibrary】,代码生成器,可以动态的生成字节码对象,即生成目标类的子类,通过调用父类(目标类)的方法实现,在调用父类方法时再代理中进行增强

cglib实现动态代理的步骤:

生成一个字节码对象(空的)

设置字节码对象的父类是目标对象

查找目标类上的所有非final的public类型的方法定义;

将符合条件的方法定义转换成字节码;

通过生成字节码对象进行回调方法,在回调的过程中,追加功能代码(方法功能的扩展)

将组成的字节码转换成相应的代理的class对象;

实现MethodInterceptor接口,用来处理对代理类上所有方法的请求

**Enhance类:**生成动态子类以启用方法拦截。动态生成的子类将重写父类的非final方法,并回调到用户定义的拦截器实现的钩子

**MethodInterceptor:**方法拦截器

Object intercept(Object obj, Method method, Object[] args, MethodProxy methodProxy)
参数一:由CGLib生成的代理类实例
参数二:在代理类上调用的被代理方法的引用
参数三:被调用的方法的参数
参数四:要被触发的父类的方法的引用

使用CGLIB的前提:
1)如果类是抽象类,只能调用已实现的方法,如果调用抽象方法的时候会报错
2)类不能是final类,否则会报错

实例
//目标类
public class Host{
    public void rent() {
        System.out.println("房屋出租");
    }
}


public class CglibProxy implements MethodInterceptor {
    private Object target;//需要代理的目标对象
    public Object getCglibProxy(Object objectTarget){
        //为目标对象target赋值
        this.target = objectTarget;
        Enhancer enhancer = new Enhancer();
        //设置父类,因为Cglib是针对指定的类生成一个子类,所以需要指定父类
        enhancer.setSuperclass(objectTarget.getClass());
        // 设置回调
        enhancer.setCallback(this);
		//创建并返回代理对象
        return enhancer.create();
    }
    
    public Object intercept(Object o, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
        seeHouse();
        Object result = methodProxy.invoke(target, args);
        fare();
        return result;
    }

    public void seeHouse(){
        System.out.println("带房客看房");
    }
    public void fare(){
        System.out.println("收中介费");
    }

}
JDK动态代理与CGLIB对比

1)

JDK动态代理:基于Java反射机制实现,必须要实现了接口的业务类才生成代理对象,在调用具体方法前调用InvokeHandler的invoke()处理。

CGLIB动态代理:利用asm开源包,将代理对象类的class文件加载进来,通过修改其字节码生成子类来处理。

2)

如果目标对象实现了接口,默认情况下会采用JDK的动态代理实现AOP

如果目标对象实现了接口,可以强制使用CGLIB实现AOP

如何强制使用CGLIB实现AOP?
(1)添加CGLIB库,SPRING_HOME/cglib/*.jar
(2)在spring配置文件中加入

如果目标对象没有实现了接口,必须采用CGLIB库,spring会自动在JDK动态代理和CGLIB之间转换

3)

JDK动态代理只能对实现了接口的类生成代理,而不能针对类CGLIB是针对类实现代理,主要是对指定的类生成一个子类,覆盖其中的方法。因为是继承,所以该类或方法最好不要声明成final

4)

JDK Proxy的优势:

最小化依赖关系、代码实现简单、简化开发和维护、JDK原生支持,比CGLIB更加可靠,随JDK版本平滑升级。而字节码类库通常需要进行更新以保证在新版Java上能够使用。

CGLIB的优势:

无需实现接口,达到代理类无侵入,只 *** 作关心的类,而不必为其他相关类增加工作量。高性能。

动态代理优缺点

优点:

可以使真实角色更加纯粹,不在去关注一些公共的事情公共的业务由代理来完成,完成业务的分工公共业务发生扩展时更加集中方便可以代理多个类,一个动态代理类代理的是一个接口,只要实现了该接口的类,他都可以代理 Spring AOP使用的代理方式

默认使用 JDK 动态代理,这样便可以代理所有的接口类型

如果目标对象没有实现任何接口,则默认是CGLIB的代理方式

可强制使用CGLIB,指定proxy-target-class = “true” 或者 基于注解@EnableAspectJAutoProxy(proxyTargetClass = true)

org.springframework.aop.framework.ProxyProcessorSupport

org.springframework.aop.framework.DefaultAopProxyFactory

spring会根据对当前类的所有接口进行判断,如果没有满足的接口,则设置proxyTargetClass为true,如果有则将接口记录下来方便之后使用JDK代理方式时创建代理类使用。

在createAopProxy()中,config.isProxyTargetClass() 为true,则会进入第一个if分支里面。在这个分支里面,如果bean它本身是一个interface或者是本身已经是一个代理对象,那么就会创建JDK代理,否则会使用CGLIB的方式创建代理对象。

参考文档:https://blog.csdn.net/w449226544/article/details/103365446

欢迎分享,转载请注明来源:内存溢出

原文地址: https://outofmemory.cn/zaji/5716858.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-12-17
下一篇 2022-12-18

发表评论

登录后才能评论

评论列表(0条)

保存