Java动态代理(AOP)
动态代理(理解): 基于反射机制。
现在需要知道以下的就行:
什么是动态代理 ?
使用jdk的反射机制,创建对象的能力, 创建的是代理类的对象。 而不用你创建类文件。不用写java文件。
动态:在程序执行时,调用jdk提供的方法才能创建代理类的对象。
jdk动态代理,必须有接口,目标类必须实现接口, 没有接口时,需要使用cglib动态代理
- 知道动态代理能做什么 ?
可以在不改变原来目标方法功能的前提下, 可以在代理中增强自己的功能代码。
程序开发中的意思。
比如:你所在的项目中,有一个功能是其他人(公司的其它部门,其它小组的人)写好的,你可以使用。
GoNong.class , GoNong gn = new GoNong(), gn.print();
你发现这个功能,现在还缺点, 不能完全满足我项目的需要。 我需要在gn.print()执行后,需要自己在增加代码。
用代理实现 gn.print()调用时, 增加自己代码, 而不用去改原来的 GoNong文件
后面会有,mybatis ,spring
一、代理
1. 什么是代理?
代购, 中介,换ip,商家等等
比如有一家美国的大学, 可以对全世界招生。 留学中介(代理)
留学中介(代理): 帮助这家美国的学校招生, 中介是学校的代理, 中介是代替学校完成招生功能。
代理特点:
1. 中介和代理他们要做的事情是一致的: 招生。
2. 中介是学校代理, 学校是目标。
3. 家长---中介(学校介绍,办入学手续)----美国学校。
4. 中介是代理,不能白干活,需要收取费用。
5. 代理不让你访问到目标。
或者是买东西都是商家卖, 商家是某个商品的代理, 你个人买东西, 肯定不会让你接触到厂家的。
在开发中也会有这样的情况, 你有a类, 本来是调用c类的方法, 完成某个功能。 但是c不让a调用。
a -----不能调用 c的方法。
在a 和 c 直接 创建一个 b 代理, c让b访问。
a --访问b---访问c
2. 使用代理模式的作用
- 功能增强: 在你原有的功能上,增加了额外的功能。 新增加的功能,叫做功能增强。
- 控制访问: 代理类不让你访问目标,例如商家不让用户访问厂家。
3. 实现代理的方式
静态代理和动态代理
二、静态代理
静态代理是指,代理类在程序运行前就已经定义好.java 源文件,其与目标类的关系在 程序运行前就已经确立。在程序运行前代理类已经编译为.class 文件。
代理类是自己手工实现的,自己创建一个java类,表示代理类。
同时你所要代理的目标类是确定的。
特点: 实现简单 、容易理解
缺点:
当你的项目中,目标类和代理类很多时候,有以下的缺点:
- 当目标类增加了, 代理类可能也需要成倍的增加。 代理类数量过多。
- 当你的接口中功能增加了, 或者修改了,会影响众多的实现类,厂家类,代理都需要修改。
1. 模拟用户购买u盘
用户是客户端类
- 商家:代理,代理某个品牌的u盘。
- 厂家:目标类。
- 三者的关系: 用户(客户端)---商家(代理)---厂家(目标)
- 商家和厂家都是卖u盘的,他们完成的功能是一致的,都是卖u盘。
实现步骤:
- 创建一个接口,定义卖u盘的方法, 表示你的厂家和商家做的事情
package com.md.service;
/**
* @author MD
* @create 2020-08-03 9:06
*/
// 表示功能,厂家和商家都要完成的功能
public interface UsbSell {
// 定义方法,返回值为u盘的价格
float sell(int amount);
}
- 创建厂家类,实现1步骤的接口
package com.md.factory;
import com.md.service.UsbSell;
/**
* @author MD
* @create 2020-08-03 9:11
*/
// 目标类。金士顿厂家
public class UsbKingFactory implements UsbSell {
@Override
public float sell(int amount) {
return 50.0f;
}
}
- 创建商家,就是代理,也需要实现1步骤中的接口。
package com.md.shangjia;
import com.md.factory.UsbKingFactory;
import com.md.service.UsbSell;
import java.lang.reflect.AccessibleObject;
/**
* @author MD
* @create 2020-08-03 9:13
*/
// 代理类,这是商家,代理金士顿u盘销售
public class Taobao implements UsbSell {
// 访问目标类
// 声明商家代理的厂家是谁
private UsbSell factory = new UsbKingFactory();
// 实现销售u盘的功能
@Override
public float sell(int amount) {
// 向厂家发送订单,告诉厂家,进行发货,这是进货价格
float price = factory.sell(amount);
// 商家加价获得利润,增强功能
price+=50;
// 在目标类的方法调用之后,剩下写的其他功能,都是增加功能
System.out.println("淘宝商家给你一个大的优惠卷");
return price;
}
}
- 创建商家,不同的商家销售同一款产品,也是代理,也需要实现1步骤中的接口。
package com.md.shangjia;
import com.md.factory.UsbKingFactory;
import com.md.service.UsbSell;
/**
* @author MD
* @create 2020-08-03 9:32
*/
// 代理类
public class Weishang implements UsbSell {
// 代理的也是金士顿
private UsbSell factory = new UsbKingFactory();
@Override
public float sell(int amount) {
float price = factory.sell(amount);
// 增强功能,每个代理根据自己的模式有不同的增强功能
price+=30;
System.out.println("微商:亲,要五星好评哟!");
return price;
}
}
- 创建客户端类,调用商家的方法买一个u盘。
package com.md;
import com.md.shangjia.Taobao;
import com.md.shangjia.Weishang;
/**
* @author MD
* @create 2020-08-03 9:23
*/
public class Shopping {
public static void main(String[] args) {
// 创建代理的商家对象
Taobao taobao = new Taobao();
float price = taobao.sell(1);
System.out.println("通过淘宝购买的u盘:"+price);
Weishang weishang = new Weishang();
float sell = weishang.sell(1);
System.out.println("通过微商购买的u盘:"+sell);
}
}
2. 静态代理的缺点
代码复杂,难于管理
代理类和目标类实现了相同的接口,每个代理都需要实现目标类的方法,这样就出现了大量的代 码重复。如果接口增加一个方法,除了所有目标类需要实现这个方法外,所有代理类也需要实现 此方法。增加了代码维护的复杂度。
代理类依赖目标类,代理类过多
代理类只服务于一种类型的目标类,如果要服务多个类型。势必要为每一种目标类都进行代理, 静态代理在程序规模稍大时就无法胜任了,代理类数量过多。
三、动态代理
动态代理是指代理类对象在程序运行时由 JVM 根据反射机制动态生成的。动态代理不需要定义代理类的.java 源文件。
动态代理其实就是 jdk 运行期间,动态创建 class 字节码并加载到 JVM。
动态代理的实现方式常用的有两种:
使用 JDK 动态代理,和通过 CGLIB 动态代理。
四、 JDK 动态代理
jdk 动态代理是基于 Java 的反射机制实现的。使用 jdk 中接口和类实现代理对象的动态创建。
Jdk 的动态要求目标对象必须实现接口,这是 java 设计上的要求。
从 jdk1.3 以来,java 语言通过 java.lang.reflect 包提供三个类支持代理模式 Proxy, Method 和 InvocationHandler
1. InvocationHandler接口
InvocationHandler 接口叫做调用处理器,负责完调用目标方法,并增强功能。
通过代理对象执行目标接口中的方法,会把方法的调用分派给调用处理器 (InvocationHandler)的实现类,
执行实现类中的 invoke()方法,我们需要把功能代理写在 invoke ()方法中
在 invoke 方法中可以截取对目标方法的调用。在这里进行功能增强
invoke()方法:
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
}
// proxy:代表生成的代理对象
// method:代表目标方法
// args:代表目标方法的参数
//第一个参数 proxy 是 jdk 在运行时赋值的,在方法中直接使用,第二个参数后面介绍, 第三个参数是方法执行的参数, 这三个参数都是 jdk 运行时赋值的,无需程序员给出。
Java 的动态代理是 建立在反射机制之上的。 实现了 InvocationHandler 接口的类用于加强目标类的主业务逻辑。这个接口中有一个 方法 invoke(),具体加强的代码逻辑就是定义在该方法中的,通过代理对象执行接口中的方法时,会自动调用 invoke()方法,
总结:
InvocationHandler 接口:表示你的代理要干什么,怎么用:
- 创建类实现接口InvocationHandler
- 重写invoke()方法, 把原来静态代理中代理类要完成的功能,写在这
2. Method 类
invoke()方法的第二个参数为 Method 类对象,该类有一个方法也叫 invoke(),可以调用目标方法。这两个 invoke()方法,虽然同名,但无关。
这个类的invoke方法
public Object invoke(Object obj , Object...args){
}
// obj:表示目标对象
// args:表示目标方法参数,也就是上面invoke方法中的第三个参数
该方法的作用是:
- 调用执行 obj 对象所属类的方法,这个方法由其调用者 Method 对象确定。
在代码中,一般的写法为 method.invoke(target, args); 其中,method 为上一层 invoke 方法的第二个参数。这样,即可调用了目标类的目标方法。
3. Proxy类
通过 JDK 的 java.lang.reflect.Proxy 类 实 现 动 态 代 理 ,会 使 用 其 静 态 方 法 newProxyInstance(),依据目标对象、业务接口及调用处理器三者,自动生成一个动态代理对 象
Proxy类:核心的对象,创建代理对象。之前创建对象都是 new 类的构造方法()
现在我们是使用Proxy类的方法,代替new的使用。
方法: 静态方法 newProxyInstance()
作用是: 创建代理对象, 等同于静态代理中的TaoBao taoBao = new TaoBao();
返回值:就是代理对象
public static Object newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h)
// 1. ClassLoader loader 类加载器,负责向内存中加载对象的。 使用反射获取对象的ClassLoader类a , a.getCalss().getClassLoader(), 目标对象的类加载器
// 2. Class<?>[] interfaces: 接口, 目标对象实现的接口,也是反射获取的。
// 3. InvocationHandler h : 我们自己写的,代理类要完成的功能。
4. 实现动态代理的步骤
和静态代理实现相同的功能
- 创建接口,定义目标类要完成的功能
package com.md.service;
/**
* @author MD
* @create 2020-08-03 10:36
*/
// 目标接口
public interface UsbSell {
float sell(int count);
}
- 创建目标类实现接口
package com.md.factory;
import com.md.service.UsbSell;
/**
* @author MD
* @create 2020-08-03 10:37
*/
// 目标类
public class UsbKingFactory implements UsbSell {
// 目标方法
@Override
public float sell(int count) {
System.out.println("目标类:执行了目标方法");
return 50.0f;
}
}
- 创建InvocationHandler接口的实现类,在invoke方法中完成代理类的功能
package com.md.handler;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
/**
* @author MD
* @create 2020-08-03 10:43
*/
// 必须实现InvocationHandler接口,完成代理类需要的功能
// 1. 调用目标方法
// 2. 增强功能
public class MySellHandler implements InvocationHandler {
//
private Object target = null;
// 动态代理,目标对象不是固定的,需要什么就传入什么,之后通过 new MySellHandler(),就可以把目标对象传进来
public MySellHandler(Object target) {
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 1. 向厂家发送订单,告诉厂家,进行发货,这是进货价格
// float price = factory.sell(amount);
// 2. 商家加价获得利润,增强功能
//price+=50;
// 3. 在目标类的方法调用之后,剩下写的其他功能,都是增加功能
// System.out.println("淘宝商家给你一个大的优惠卷");
// 1. 执行目标方法,和上面等价,调用目标方法,传入目标对象和相应的参数
Object res = method.invoke(target,args);
// 2. 增强功能
if (res != null){
Float price = (Float)res;
price+=50;
res = price;
}
// 3. 在目标类的方法调用之后,剩下写的其他功能,都是增加功能
System.out.println("淘宝商家给你一个大的优惠卷");
return res;
}
}
- 使用Proxy类的静态方法,创建代理对象。 并把返回值转为接口类型
package com.md;
import com.md.factory.UsbKingFactory;
import com.md.handler.MySellHandler;
import com.md.service.UsbSell;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;
/**
* @author MD
* @create 2020-08-03 10:55
*/
public class Shopping {
public static void main(String[] args) {
// 创建代理对象,使用Proxy
// 1. 创建目标对象
UsbSell factory = new UsbKingFactory();
// 2. 创建InvocationHandler对象
InvocationHandler handler = new MySellHandler(factory);
// 3. 创建代理对象
UsbSell proxy = (UsbSell) Proxy.newProxyInstance(factory.getClass().getClassLoader(),
factory.getClass().getInterfaces(),
handler);
// 4. 通过代理执行方法
float price = proxy.sell(1);
System.out.println("通过动态代理调用方法:"+price);
}
}
五、cgLib 代理
CGLIB(Code Generation Library)是一个开源项目。是一个强大的,高性能,高质量的 Code 生成类 库,它可以在运行期扩展 Java 类与实现 Java 接口。它广泛的被许多 AOP 的框架使用,例如 Spring AOP。
使用 JDK 的 Proxy 实现代理,要求目标类与代理类实现相同的接口。若目标类不存在 接口,则无法使用该方式实现。
但对于无接口的类,要为其创建动态代理,就要使用 CGLIB 来实现。
CGLIB 代理的生成 原理是生成目标类的子类,而子类是增强过的,这个子类对象就是代理对象。所以,使用 CGLIB 生成动态代理,要求目标类必须能够被继承,即不能是 final 的类。
cglib 经常被应用在框架中,例如 Spring ,Hibernate 等。Cglib 的代理效率高于 Jdk。对 于 cglib 一般的开发中并不使用。做了一个了解就可以。
六、练习
- 定义接口
package com.md.service;
/**
* @author MD
* @create 2020-08-03 20:51
*/
public interface Dog {
void info();
void run();
}
- 接口的具体实现类
package com.md.factory;
import com.md.service.Dog;
/**
* @author MD
* @create 2020-08-03 20:51
*/
public class GunDog implements Dog {
@Override
public void info() {
System.out.println("我是狗");
}
@Override
public void run() {
System.out.println("狗在跑");
}
}
- 增加方法
package com.md.Util;
/**
* @author MD
* @create 2020-08-03 20:53
*/
public class DogUtil {
public void method1(){
System.out.println("我是第一个增强方法");
}
public void method2(){
System.out.println("我是第二个增强方法");
}
}
实现 InnovationHandler 接口
动态代理的核心环节就是要实现 InvocaitonHandler 接口。每个动态代理类都必须要实现 InvocationHandler 接口,并且每个代理类的实例都关联到了一个 InvocationHandler 接口实现类的实例对象,当我们通过代理对象调用一个方法的时候,这个方法的调用就会被转发为由 InvocationHandler 这个接口的 invoke() 方法来进行调用,具体的实现如下:
package com.md.handler;
import com.md.Util.DogUtil;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
/**
* @author MD
* @create 2020-08-03 20:55
*/
public class MyHandler implements InvocationHandler {
private Object target;
// 需要被代理的对象
public void setTarget(Object target) {
this.target = target;
}
// 当我们调用被代理对象的方法时,invvoke方法会取而代之
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
DogUtil dogUtil = new DogUtil();
// 增强方法
dogUtil.method1();
// 用反射调用Dog class中的方法
Object res = method.invoke(target, args);
dogUtil.method2();
return res;
}
}
- 使用Proxy类的静态方法,创建代理对象。 并把返回值转为接口类型
package com.md;
import com.md.factory.GunDog;
import com.md.handler.MyHandler;
import com.md.service.Dog;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;
/**
* @author MD
* @create 2020-08-03 21:14
*/
public class DogTest {
public static void main(String[] args) {
// 实例化被代理对象
Dog dog = new GunDog();
// 实例化MyHandler对象,
MyHandler myHandler = new MyHandler();
myHandler.setTarget(dog);
// 用被代理对象产生一个代理对象
Dog proxy = (Dog) Proxy.newProxyInstance(dog.getClass().getClassLoader(), dog.getClass().getInterfaces(), myHandler);
// 代理对象调用被代理对象的方法
proxy.info();
System.out.println("-------------------");
proxy.run();
}
}
// 运行结果
/*
我是第一个增强方法
我是狗
我是第二个增强方法
-------------------
我是第一个增强方法
狗在跑
我是第二个增强方法
*/
还是需要注意newProxyInstance() ,要把返回值转为接口类型,以及注意参数
- 参数一:类加载器对象即用哪个类加载器来加载这个代理类到 JVM 的方法区
- 参数二:接口表明该代理类需要实现的接口
- 参数三:是调用处理器类实例即 InvocationHandler 的实现的实例对象
这个函数是 JDK 为了程序员方便创建代理对象而封装的一个函数,因此你调用newProxyInstance()
时直接创建了代理对象
Java动态代理(AOP)的更多相关文章
- Java动态代理学习【Spring AOP基础之一】
Spring AOP使用的其中一个底层技术就是Java的动态代理技术.Java的动态代理技术主要围绕两个类进行的 java.lang.reflect.InvocationHandler java.la ...
- java动态代理实现与原理详细分析(代码层面解释了AOP的实现)
关于Java中的动态代理,我们首先需要了解的是一种常用的设计模式--代理模式,而对于代理,根据创建代理类的时间点,又可以分为静态代理和动态代理. 一.代理模式 代理模式是常用的java设计模式, ...
- AOP面向切面编程JAVA动态代理实现用户权限管理(实现篇)
java动态代理机制的功能十分强大,使用动态代理技术能够有效的降低应用中各个对象之间的耦合紧密程度,提高开发的效率以及程序的可维护性,事实上Spring AOP就是建立在Java动态代理的基础之上.其 ...
- 转:AOP与JAVA动态代理
原文链接:AOP与JAVA动态代理 1.AOP的各种实现 AOP就是面向切面编程,我们可以从以下几个层面来实现AOP 在编译期修改源代码 在运行期字节码加载前修改字节码 在运行期字节码加载后动态创建代 ...
- Java动态代理-->Spring AOP
引述要学习Spring框架的技术内幕,必须事先掌握一些基本的Java知识,正所谓“登高必自卑,涉远必自迩”.以下几项Java知识和Spring框架息息相关,不可不学(我将通过一个系列分别介绍这些Jav ...
- Java 动态代理机制详解
在学习Spring的时候,我们知道Spring主要有两大思想,一个是IoC,另一个就是AOP,对于IoC,依赖注入就不用多说了,而对于Spring的核心AOP来说,我们不但要知道怎么通过AOP来满足的 ...
- Java 动态代理作用是什么?
Java 动态代理作用是什么? 1 条评论 分享 默认排序按时间排序 19 个回答 133赞同反对,不会显示你的姓名 Intopass 程序员,近期沉迷于动漫ING 133 人赞同 ① 首先你 ...
- java动态代理浅析
最近在公司看到了mybatis与spring整合中MapperScannerConfigurer的使用,该类通过反向代理自动生成基于接口的动态代理类. 于是想起了java的动态代理,然后就有了这篇文章 ...
- java动态代理模式
java动态代理机制详解 Spring的核心AOP的原理就是java的动态代理机制. 在java的动态代理机制中,有两个重要的类或接口: 1.InvocationHandler(Interface): ...
- 【转载】Java 动态代理
Java 动态代理 本文为 Android 开源项目源码解析 公共技术点中的 动态代理 部分项目地址:Jave Proxy,分析的版本:openjdk 1.6,Demo 地址:Proxy Demo分析 ...
随机推荐
- 二分查找&二叉排序树
首先我们先来复习一下二分查找的算法 对于正向序列的二分查找 递归实现: bool binary_search(vector<int> &sort_arry,int begin,in ...
- day54 作业
编写代码,将当前日期按"2017-12-27 11:11 星期三"格式输出(提示:switch结构) var date = new Date() ymd = data.toLoca ...
- Pandas基础知识图谱
所有内容整理自<利用Python进行数据分析>,使用MindMaster Pro 7.3制作,emmx格式,源文件已经上传Github,需要的同学转左上角自行下载或者右击保存图片.该图谱只 ...
- 正则表达式以及sed,awk用法 附带案例
则表达式 基本正则 ^ $ [ ] [^] . * \{n,m\} \{n,\} \(ro\)\{2\} \(\) 扩展正则 egrep grep - ...
- 【网鼎杯2018】fakebook
解题过程: 首先进行目录扫描,发现以下目录: user.php.bak login.php flag.php user.php robots.txt user.php.bak猜测存在源码泄露. 查看源 ...
- input type=file过滤图片
<input type="file" accept=".png,.jpg,.jpeg,image/png,image/jpg,image/jpeg"> ...
- java 基本语法(十二) 数组(五)Arrays工具类的使用
1.理解:① 定义在java.util包下.② Arrays:提供了很多操作数组的方法. 2.使用: //1.boolean equals(int[] a,int[] b):判断两个数组是否相等. i ...
- 数据可视化之powerBI技巧(九)PowerBI按周进行业务分析的思路
按周进行数据分析,在零售业.电商等类型的公司中很常见,但是不少人觉得按周进行分析无从下手,一个主要的原因是找不到对应的函数,因为时间智能函数只对应年.季.月.天这几个粒度,没有关于周的时间智能函数. ...
- Python Hacking Tools - Web Scraper
Preparation: Python Libray in the following programming: 1. Requests Document: https://2.python-requ ...
- P1290 欧几里德的游戏(洛谷)
欧几里德的两个后代 Stan 和 Ollie 正在玩一种数字游戏,这个游戏是他们的祖先欧几里德发明的.给定两个正整数 M 和 N,从 Stan 开始,从其中较大的一个数,减去较小的数的正整数倍,当然, ...