导学
文章目录
一、代理模式(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 框架中,为他们提供方法的拦截。