基础概念:
1.什么是动态?
在代码运行时动态的创建代理类对象
2.什么是代理?
代理模式/装饰模式,再不改变原有类的前提下,增强这个类的功能
接口:obj和proxy要实现相同的接口
被代理类:obj实际干活的对象
代理类proxy:包装obj,增强obj,实际底层操作obj干活
增强的点
静态代理的静态:代理类在代码运行之前写好的
动态代理的动态:代理类在代码运行时写的(创建的) 底层:反射
对比:
1>能用动态代理写的,都能用静态代理写
2>在很多场景下,动态代理比静态代理简洁
(因为程序猿无需提前写代理类)牛逼之处
静态代码书写步骤:
1.创建被代理类的对象
2.创建代理对象
3.调用代理
动态代理书写步骤
1.创建被代理类的对象
2.创建代理对象
I.被代理类对象获取自己的类加载器
II.获取被代理类实现的接口
III. new invocationHandler 代理对象被调用之后执行的方法
重写invoke方法
3.调用代理
英文单词翻译
proxy代理
loader类加载器
invoke 调用
newProxyInstance个人理解创建一个代理实例
new创建
Instance实例
Invocation调用
Handler处理器
静态代理代码步骤
1.先写一个接口,里面有两个抽象方法
public interface Singer {
void sing(double money);
void dance(double money);
}
2.写被代理类实现这个接口,重写方法+toString方便打印
class YangChaoYue implements Singer{
@Override
public void sing(double money) {
System.out.println("杨超越唱歌,收钱:" + money);
}
@Override
public void dance(double money) {
System.out.println("杨超越跳舞,收钱:" + money);
}
@Override
public String toString() {
return "YangChaoYue{}";
}
}
3.写代理类,也要实现这个接口
/*
需求: 可以增强Singer接口的所有实现类对象
增强的点: 讨价还价功能
代理类:
1. 定义一个代理类,实现接口Singer
2. 该代理类设置一个Singer接口的成员变量,并用构造赋值
3. 业务: 增强点+被代理对象调用
*/
public class MyProxy implements Singer{
Singer singer;//被代理的对象
public MyProxy( Singer singer){
this.singer = singer;
}
@Override
public void sing(double money) {
if(money > 100){
singer.sing(money);
}else{
System.out.println("钱不够,不唱");
}
}
@Override
public void dance(double money) {
if(money > 200){
singer.dance(money);
}else{
System.out.println("钱不够,不跳");
}
}
}
动态代理代码步骤
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
/*
动态代理
Object proxy = Proxy.newProxyInstance(loader,interfaces,invocationHandler);
1. loader 类加载器
2. interfaces 代理类实现的接口(要跟被代理类相同)
3. invocationHandler 代理对象被调用之后执行的方法
return 代理类对象
*/
public class Demo02 {
public static void main(String[] args) {
//1. 创建被代理类对象
YangChaoYue ycy = new YangChaoYue();
//2. 创建代理
//被代理类对象获取自己的Class对象,类获取自己的类加载器
ClassLoader loader = ycy.getClass().getClassLoader();
//获取被代理类实现的接口
Class<?>[] interfaces = ycy.getClass().getInterfaces();
//接口类型参数,必定接收其实现类对象,因为接口不能创建实例
InvocationHandler h = new InvocationHandler() {
/*
invoke 方法: 代理对象每调用一次方法(无论什么方法),invoke就会执行一次!!!
参数列表
proxy : 就是代理对象(就是这里的singer)
method(重要) : 当前代理对象调用的方法(当前案例,可以是sing/dance/toString)
(比如 singer.sing(105), method就是sing方法对象)
args(重要) : 当前代理对象调用的方法的参数
(比如 singer.sing(105,"xx"), Object[] args = {105,"xx"} )
返回值
就是代理对象调用方法的返回值,如果某方法的返回值是void无返回值,就是**加粗样式**return null
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
String name = method.getName();
//name可能是sing,也可能是dance,也可能是toString
if("sing".equals(name)){
double money = (double) args[0];
if(money > 100){
ycy.sing(money);
}else{
System.out.println("动态: 钱不够,不唱");
}
}else if("dance".equals(name)){
double money = (double) args[0];
if(money > 200){
ycy.dance(money);
}else{
System.out.println("动态: 钱不够,不跳");
}
}else if("toString".equals(name)){
//toString有返回值,就返回一个字符串
return "我是代理类";
}
//singer和dance没有返回值,就是return null
return null;
}
};
Singer singer = (Singer)Proxy.newProxyInstance(loader, interfaces, h);
//3. 调用代理
singer.sing(105);
singer.dance(33);
String str = singer.toString();
System.out.println(str);
}
}
**执行结果**
杨超越唱歌,收钱:105.0
动态: 钱不够,不跳
我是代理类
动态代理代码第二遍书写
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
public class Demo03 {
public static void main(String[] args) {
//1. 创建被代理的对象
YangChaoYue ycy = new YangChaoYue();
//2. 创建代理对象
ClassLoader loader = ycy.getClass().getClassLoader();
Class<?>[] interfaces = ycy.getClass().getInterfaces();
InvocationHandler h = new InvocationHandler() {
/*
invoke方法的特点: 代理每调用一次方法,就会执行一次invoke
参数列表:
proxy : 就是代理(案例: singer)
method: 代理当前调用的方法(案例: 可以是sing/dance/toString... )
args: 代理当前调用方法的参数(案例: sing方法, Object[] args = {130} )
返回值:
如果代理调用的方法没有返回值,返回null
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
String name = method.getName();
if("sing".equals(name)){
double money = (double) args[0];
if(money > 100){
ycy.sing(money);
}else{
System.out.println("哼哼,钱不够不唱");
}
}else{
// 除了sing之外的方法,我们调用代理其他方法,代理都会给被代理对象执行
// 下面调用dance的时候这句代码就 等价于ycy.dance(130);
// 调用toString的时候 等价于String s = ycy.toString();
// method是什么?调用dance它就是dance方法,调用toString就是toString方法
// Object接收是因为底层没办法知道你的返回值类型
Object o = method.invoke(ycy, args);
return o;
}
return null;
}
};
Singer singer = (Singer)Proxy.newProxyInstance(loader, interfaces, h);
//3. 调用代理
singer.dance(130);
String str = singer.toString();
System.out.println(str);
}
}
**执行结果**
杨超越跳舞,收钱:130.0
YangChaoYue{}
原理分析
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
/*
# 动态代理
1. 动态: 代理类在程序运行时创建的
底层: 肯定和反射有关系
1). 类加载器: 将字节码加载进内存,jvm依此创建Class对象
*/
public class Demo04 {
public static void main(String[] args) {
//1. 创建被代理的对象
YangChaoYue ycy = new YangChaoYue();
//2. 创建代理对象
/*
原理的第一步: loader + interfaces (继承+ 反射)
实现接口是用了继承
类加载器是用了反射
运行时: jvm底层会定义一个类,去实现interfaces中的接口(动态字节码技术)
这个字节码是在运行时动态创建的
loader此时加载这个字节码, jvm依此创建代理类的Class对象
总结: loader + interfaces -> 代理类Class对象
TODO:说一个点:我们的字节码最终在运行时的目的就是产生Class对象,
TODO:newProxyInstance方法底层已经实现了,生成了指定的代理类
TODO:我这个目的达到了,我的字节码需要提前写好吗?
TODO:只要我们能在运行时产生,我们就不用提前写好
原理的第二步 : InvocationHandler h (多态+ 反射)
问题: jvm能够创建类实现接口,无法预知业务重写方法
重写方法还是需要交给我们程序猿来实现
*/
ClassLoader loader = ycy.getClass().getClassLoader();
Class<?>[] interfaces = ycy.getClass().getInterfaces();
InvocationHandler h = new InvocationHandler() {
/*
invoke方法的特点: 代理无论执行什么方法,invoke都会被调用
参数列表:
proxy : 就是代理本身(无意义)
method: 当前代理调用的方法(可以是sing/dance/toString...)
args: 当前代理调用的方法参数
返回值:
代理调用方法的返回值
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
String name = method.getName();
if("sing".equals(name)){
double money = (double) args[0];
if(money > 100){
ycy.sing(money);
}else{
System.out.println("哼哼,钱不够不唱");
}
}else{
Object o = method.invoke(ycy, args);
return o;
}
return null;
}
};
Singer singer = (Singer)Proxy.newProxyInstance(loader, interfaces, h);
//3. 调用代理
singer.dance(130);
singer.sing(80);
String string = singer.toString();
System.out.println(string);
}
}
执行结果
杨超越跳舞,收钱:130.0
哼哼,钱不够不唱
YangChaoYue{}
newProxyInstance底层代码图片,分析的上面代码中的一句话
伪代码分析图片
这里是一段伪代码帮助我们理解
InnerProxy这个类实际上是不存在的,只在运行时产生
为了方便想象写的一段代码
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
// 伪代码
public class InnerProxy implements Singer {
//这个h是我们写原理的代码给它传进去的
InvocationHandler h;
public InnerProxy(InvocationHandler h){
this.h = h;
}
@Override
public void sing(double money) {
try {
Class<? > clazz = this.getClass();
Method method = clazz.getDeclaredMethod("sing",double.class);
Object[] args = {money};
h.invoke(this,method, args);
} catch (Throwable throwable) {
throwable.printStackTrace();
}
}
@Override
public void dance(double money) {
try {
Class<? > clazz = this.getClass();
Method method = clazz.getDeclaredMethod("dance",double.class);
Object[] args = {money};
h.invoke(this,method,args);
} catch (Throwable throwable) {
throwable.printStackTrace();
}
}
@Override
public String toString() {
try {
Class<? > clazz = this.getClass();
Method method = clazz.getDeclaredMethod("toString");
Object[] args = {};
String obj = (String) h.invoke(this, method, args);
return obj;
} catch (Throwable throwable) {
throwable.printStackTrace();
}
return null;
}
}
动态代理随笔
方法增强: 在不惊动原始设计的基础上(不修改源代码),为其添加功能
要实现对一个类的方法增强,我们有以下三种方式: 继承,静态代理和动态代理
1. 继承
1). 实现方式
创建一个子类继承需要增强的类,重写该方法进行增强
2). 优缺点
a. 优点: 实现简单
b. 缺点: 对比其他两种方式, 继承中的一个子类只能增强其父类(只能增强一个)
2. 装饰者模式(静态代理):
1). 实现方式
a. 装饰者类和被装饰者类必须实现同一个接口或继承同一个类
b. 在装饰者类中必须要有被装饰者类的引用
c. 在装饰者类中对需要增强的方法进行增强
d. 在装饰者类中对不需要增强的方法调用原来的逻辑
2). 优缺点
a. 优点: 对比继承方式中的子类,装饰模式的代理类可以增强父接口/父类的所有子类
b. 缺点: 对比动态代理,装饰模式的代理类需要提前定义
3. 动态代理:
1). 实现方式
a. JDK的动态代理:
Proxy: 基于接口的代理(Proxy来源于JDK)
b. cglib的动态代理
Enhancer: 基于类的代理(第三方工具包)
2). 优缺点:
a. 优点: 无论是JDK还是cglib的动态代理, 采用的都是动态字节码技术,也就是代理类都是运行过程中动态产生的,无需在编写代码时定义
b. 缺点:与继承或静态代理对比, 相对不好理解
4. JDK的动态代理与cglib详解
1). 使用条件
a. JDK动态代理只能对实现了接口的类生成代理,而不能针对类
b. CGLib是针对类实现代理,主要是对指定的类生成一个子类,重写其中的方法
(就是继承,父类的方法不能用final修饰)
2). 性能对比
a. 在JDK1.6、JDK1.7、JDK1.8逐步对JDK动态代理优化之后,在调用次数较少的情况下,JDK代理效率高于CGLib代理效率,只有当进行大量调用的时候,JDK1.6和JDK1.7比CGLib代理效率低一点
b. 但是到JDK1.8的时候,JDK代理效率高于CGLib代理
3). spring的AOP采用的代理方案
a. 当Bean实现接口时,Spring就会用JDK的动态代理
b. 当Bean没有实现接口时,Spring使用CGLib来实现
c. 备注: 开发者可以在spring中强制使用CGLib
(在Spring配置中加入<aop:aspectj-autoproxy proxy-target-class=“true”/>)