导学
文章目录
一、代理模式(Proxy pattern)
AOP的原理是动态代理
动态代理解决了方法之间的紧耦合,在方法调用方法中间可动态进行附加操作
IOC解决了类与类之间的紧耦合!

1. 核心角色与应用场景
核心角色:
- 抽象对象 
  - 定义代理角色和真实角色的公共对外方法
 
- 真实角色 
  - 实现抽象角色,定义真实角色所要实现的业务逻辑,供代理角色调用
- 关注真正的业务逻辑!
 
- 代理角色 
  - 实现抽象角色,是真实角色的代理,通过真实角色的业务逻辑方法来实现抽象方法,并可以添加自己的操作。
- 将统一的流程控制放到代理角色中处理!
 
应用场景:
- 安全代理:屏蔽对真实角色的直接访问。
- 远程代理:通过代理类远程方法调用(RMI)
- 延迟加载:先加载轻量级的代理对象,真正需要再加载真实对象。 
  - 比如要开发一个大文档查看软件,大文档中有大的图片,有可能一个图片100MB,在打开文件时不可能将所有的图片都显示出来,这样就可以使用代理模式,当需要查看图片时,用proxy来进行大图片的打开。
 
2. 代理模式介绍
-  代理设计模式的原理: 使用一个代理将对象包装起来,然后用该代理对象取代原始对象。任何对原始对象的调用都要通过代理。代理对象决定是否以及何时将方法调用转到原始对象上。 
-  代理模式的作用就是在不修改原对象的基础上增强该对象的方法。比如官方购买苹果手机不赠送充电头,此时京东平台作为苹果的代理商,可以在代理销售苹果手机时赠送充电头。  
-  代理模式分为静态代理和动态代理。静态代理会生成一个代理类,动态代理不会生成代理类,直接生成代理对象。 - 动态代理:客户通过代理类来调用其他对象的方法,并且是在程序运行时根据需要动态创建目标类的代理对象。
- 静态代理:特征是代理类和目标对象的类都是在编译期间确定下来的,不利于程序的扩展。同时,每一个代理类只能为一个接口服务,这样一来程序开发中必然产生过多的代理。最好可以通过一个代理类完成全部的代理功能。
 
-  动态代理使用场合: - 调试
- 远程方法调用
 
-  动态代理相比静态代理的优点: 抽象角色中(接口)声明的所有方法都被转移到调用处理器一个集中的方法中处理,这样,我们可以更加灵活和统一的处理众多的方法。 
二、静态代理
代理类和被代理类实现同一个接口,在代理类中对方法进行扩展
代理类和被代理类在编译期间就确定下来了
interface ClothFactory{
    void produceCloth();
}
//代理类
class ProxyClothFactory implements ClothFactory{
    private ClothFactory clothFactory;//用被代理类对象进行实例化
    public ProxyClothFactory(ClothFactory clothFactory) {
        this.clothFactory = clothFactory;
    }
    @Override
    public void produceCloth() {
        System.out.println("代理工厂做一些准备工作");
        clothFactory.produceCloth();
        System.out.println("代理工厂做一些后续的收尾工作");
    }
}
//被代理类
class NikeClothFactory implements ClothFactory{
    @Override
    public void produceCloth() {
        System.out.println("Nike工厂生产一批运动服");
    }
}
public class StaticProxyTest {
    public static void main(String[] args) {
        //创建被代理类的对象
        ClothFactory nike = new NikeClothFactory();
        //创建代理类的对象
        ClothFactory proxy = new ProxyClothFactory(nike);
        //通过代理类的对象调用方法
        proxy.produceCloth();
    }
}
三、jdk动态代理一
在代理模式中,代理类和被代理类都要实现同一个接口
使用到了一个类Proxy和一个接口InvocationHandler
- 创建被代理类,实现接口,编写方法
- 要实现动态代理,创建一个类ProxyFactory,用于创建代理类对象 
  - 在ProxyFactory中提供一个静态方法,参数为被代理类对象,用于获取代理类对象
- 通过Java反射包下的Proxy的newProxyInstance方法获取代理类对象
- newProxyInstance方法的参数为被代理类对象的类加载器,被代理类对象实现的接口,以及一个handler(主要用于调用被代理类的同名方法)
 
- 创建一个类实现InvocationHandler,重写invoke方法,参数有proxy,method,args 
  - 在invoke方法中通过method.invoke()来调用被代理类的方法,invoke方法中需要传入被代理类对象,和被代理类对象方法所需的参数args,返回值即为被代理类方法返回值
- 被代理类obj通过bind()从ProxyFactory中传入
 
package com.sxt.proxyTest;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
interface Human{
    String getBelief();
    void eat(String food);
}
//被代理类
class SuperMan implements Human{
    @Override
    public String getBelief() {
        return "I believe I can fly";
    }
    @Override
    public void eat(String food) {
        System.out.println("我喜欢吃"+food);
    }
}
/*
实现动态代理,解决的问题:
问题一:根据加载到内存中的被代理类,动态的创建一个代理类及其对象
问题二:当通过代理类的对象调用方法时,如何去调用被代理类的同名方法
 */
class ProxyFactory{
    //调用此方法,返回一个代理类的对象——解决问题一
    public static Object getProxyInstance(Object obj){//obj:被代理类的对象
        MyInvocationHandler handler = new MyInvocationHandler();
        handler.bind(obj);
        return Proxy.newProxyInstance(obj.getClass().getClassLoader(),obj.getClass().getInterfaces(),handler);
    }
}
class MyInvocationHandler implements InvocationHandler{
    private Object obj;//需要使用被代理类的对象进行赋值
    public void bind(Object obj){
        this.obj=obj;
    }
    //当我们通过代理类的对象,调用方法a时,就会自动调用如下的方法:invoke()
    //将被代理类要执行的方法a的功能就声明在invoke()中
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        //method:即为代理类对象调用的方法,此方法也就作为了被代理类对象要调用的方法
        //obj:被代理类的对象
        Object returnValue = method.invoke(obj, args);
        //上述方法的返回值作为当前类中的invoke方法的返回值
        return returnValue;
    }
}
public class DynamicProxyTest {
    public static void main(String[] args) {
        SuperMan superMan = new SuperMan();
        //proxyInstance:代理类的对象
        Human proxyInstance = (Human) ProxyFactory.getProxyInstance(superMan);
        //当通过代理类对象调用方法时,会自动的调用被代理类中同名的方法
        String belief = proxyInstance.getBelief();
        System.out.println(belief);
        proxyInstance.eat("麻辣烫");
        System.out.println("--------------------------");
        NikeClothFactory nikeClothFactory = new NikeClothFactory();
        ClothFactory proxyInstance1 = (ClothFactory) ProxyFactory.getProxyInstance(nikeClothFactory);
        proxyInstance1.produceCloth();
    }
}
测试:
public class DynamicProxyTest {
    public static void main(String[] args) {
        SuperMan superMan = new SuperMan();
        //proxyInstance:代理类的对象
        Human proxyInstance = (Human) ProxyFactory.getProxyInstance(superMan);
        //当通过代理类对象调用方法时,会自动的调用被代理类中同名的方法
        String belief = proxyInstance.getBelief();
        System.out.println(belief);
        proxyInstance.eat("麻辣烫");
        System.out.println("--------------------------");
        NikeClothFactory nikeClothFactory = new NikeClothFactory();
        ClothFactory proxyInstance1 = (ClothFactory) ProxyFactory.getProxyInstance(nikeClothFactory);
        proxyInstance1.produceCloth();
    }
}
四、jdk动态代理二

JDK动态代理是针对接口进行代理,所以我们要写被代理的接口和该接口的实现类。
//接口
public interface Apple {
    String sell(double price);//卖产品
    void repair();//维修
}
//被代理接口的实现类
public class AppleImpl implements Apple {
    @Override
    public String sell(double price) {
        System.out.println("产品卖了"+price+"元");
        return "iphone13";
    }
    @Override
    public void repair() {
        System.out.println("苹果售后维修");
    }
}
//代理方式类,定义被代理方法的增强方式
//该类实现InvocationHandler接口,重写invoke方法,定义方法的增强方式
public class ShoppingProxy implements InvocationHandler {
    private Apple apple;//传入被代理对象
    public ShoppingProxy(Apple apple) {
        this.apple = apple;
    }
    /**
     * 定义原方法的增强方式
     * @param proxy 被代理对象
     * @param method 被代理对象调用的方法
     * @param args 被代理对象调用方法时传入的参数
     * @return  方法的返回值
     * @throws Throwable
     */
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        String name = method.getName();//被代理对象执行的方法名
        if("sell".equals(name)){
            double price = (double) args[0] * 0.9;//增强参数
            Object result = method.invoke(apple, price);//执行方法
            return result + "和充电头";//增强返回值
        }else if("repair".equals(name)){
            System.out.println("专属客服为您服务!");//增强方法流程
            return method.invoke(apple,args);
        }else{
            return method.invoke(apple,args);   //什么都不增强
        }
    }
}
测试:
public class Test {
    public static void main(String[] args) {
        //被代理对象
        Apple apple = new AppleImpl();
        //代理方式对象
        ShoppingProxy shoppingProxy = new ShoppingProxy(apple);
        //生成代理对象
        Apple appleJD = (Apple) Proxy.newProxyInstance(
                apple.getClass().getClassLoader(),//代理类的加载器
                apple.getClass().getInterfaces(),//被代理接口
                shoppingProxy //代理方式对象
        );
        //执行增强后的方法
        String sell = appleJD.sell(6000);
        System.out.println(sell);
        appleJD.repair();
    }
}
五、cglib动态代理

CGLib动态代理简化了JDK动态代理的写法,JDK是针对接口代理,而CGLib是针对类代理。
- 需要先引入cglib依赖
- 代理方式类,需要实现MehtodInterceptor接口,重写intercept方法,并且通过该类的构造器将被代理对象传入 
  - 重写的intercept方法中,参数为被代理对象,被代理对象调用的方法,参数列表,底层生成的代理类的应用
- 通过参数method调用invoke方法来调用被代理对象的方法
 
- 在测试类中创建被代理对象,代理方式类对象,通过Enhancer.create方法创建代理对象,将被代理对象和代理方式类对象传入
<dependencies>
    <!--cglib动态代理-->
    <dependency>
        <groupId>cglib</groupId>
        <artifactId>cglib</artifactId>
        <version>3.3.0</version>
    </dependency>
</dependencies>
//被代理类
public class Apple {
    public String sell(double price) {
        System.out.println("产品卖了"+price+"元");
        return "iphone13";
    }
    public void repair() {
        System.out.println("苹果售后维修");
    }
}
//代理方式类,实现MethodInterceptor接口,重写intercept方法
public class ShoppingProxy implements MethodInterceptor {
    private Apple apple;//被代理对象
    public ShoppingProxy(Apple apple){
        this.apple = apple;
    }
    /**
     * 定义原方法的增强方式
     * @param o 被代理对象
     * @param method 被代理对象调用的方法
     * @param objects 被代理对象调用方法时传入的参数
     * @param methodProxy 底层生成的代理类的应用
     * @return 方法的返回值
     * @throws Throwable
     */
    @Override
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        String name = method.getName();
        if("sell".equals(name)){
            double price = (double)objects[0]*0.8;
            Object result = method.invoke(apple, price);
            return result+"和数据线";
        }else if("repair".equals(name)){
            System.out.println("专属客服为您服务!");
            return method.invoke(apple,objects);
        }else{
            return method.invoke(apple,objects);
        }
    }
}
测试:
//测试类
public class Test {
    public static void main(String[] args) {
        //被代理对象
        Apple apple = new Apple();
        //代理方式
        ShoppingProxy shoppingProxy = new ShoppingProxy(apple);
        //生成代理对象
        Apple appleTB = (Apple) Enhancer.create(Apple.class, shoppingProxy);
        //执行增强后的方法
        String sell = appleTB.sell(9000);
        System.out.println(sell);
        appleTB.repair();
    }
}
六、cglib和jdk动态代理的区别
1.Cglib 和 jdk 动态代理的区别?
- Jdk动态代理:利用拦截器(必须实现 InvocationHandler )加上反射机制生成一个代理接口的匿名类,在调用具体方法前调用InvokeHandler来处理
- Cglib动态代理:利用 ASM框架,对代理对象类生成的 class 文件加载进来,通过修改其字节码生成子类来处理
2. 什么时候用 cglib 什么时候用 JDK 动态代理?
- 目标对象生成了接口 默认使用jdk动态代理
- 如果目标对象使用了接口,可以强制使用cglib
- 如果目标对象没有实现接口,必须采用cglib库,Spring会自动在jdk动态代理和cglib之间转换
3. JDK 动态代理和 cglib 字节码生成的区别?
- JDK 动态代理只能对实现了接口的类生成代理,而不能针对类
- Cglib 是针对类实现代理,主要是对指定的类生成一个子类,覆盖其中的方法,并覆盖其中方法的增强,但是因为采用的是继承,所以该类或方法最好不要设置为 final ,对于 final 类或方法,是无法继承的
4. Cglib 比 JDK 快?
- cglib 底层是 ASM 字节码生成框架,但是字节码技术生成代理类,在 JDK1.6 之前比使用 java 反射的效率要高
- 在 JDK1.6 之后逐步对 JDK 动态代理进行了优化,在调用次数比较少时效率高于 cglib 代理效率
- 只有在大量调用的时候 cglib 的效率高,但是在 JDK8 的时候JDK的效率已高于 cglib
- Cglib 不能对声明 final 的方法进行代理,因为 cglib 是动态生成代理对象,final 关键字修饰的类不可变只能被引用不能被修改
5. Spring 如何选择是用 JDK 还是 cglib?
- 当 bean 实现接口时,会用 JDK 代理模式
- 当 bean 没有实现接口,用 cglib实现
- 可以强制使用cglib(在spring配置中加入<aop:aspectj-autoproxy proxyt-target-class="true"/>)
6. Cglib 原理
动态生成一个要代理的子类,子类重写要代理的类的所有不是 final 的方法。在子类中采用方法拦截技术拦截所有的父类方法的调用,顺势织入横切逻辑,它比 Java 反射的 jdk 动态代理要快
Cglib 是一个强大的、高性能的代码生成包,它被广泛应用在许多 AOP 框架中,为他们提供方法的拦截。










