- 使用 Spring Initializr 创建 Spring Boot 应用程序
- 在Spring Boot中配置Cassandra
- 在 Spring Boot 上配置 Tomcat 连接池
- 将Camel消息路由到嵌入WildFly的Artemis上
代理模式分为:静态代理、动态代理
我们以火车站售票的案例,涉及的对象有:购票人、火车站售票点、代售点(火车站售票点即为被代理对象,代售点即为代理对象)
购票人通过代售点买到火车站售票点的票,代售点为此收点费用!
代码实现:
public interface SellTickets {
void sell() ;
}
public class TrainStation implements SellTickets{
@Override
public void sell() {
System.out.println("火车站售票");
}
}
public class ProxyPoint implements SellTickets {
private TrainStation trainStation = new TrainStation() ;
@Override
public void sell() {
System.out.println("代理点先收点手续费!");
trainStation.sell();
}
}
public class Client {
public static void main(String[] args) {
// 创建代售点对象
ProxyPoint proxyPoint = new ProxyPoint();
proxyPoint.sell();
}
}
静态代理的缺点:
为了解决,这个问题,就有了动态代理 !
动态代理有两种实现方法:1、JDK实现动态代理 2、Cglib实现动态代理
前提:动态代理不存在代理类,而是在内存中动态的生成代理类!
Java 中提供了一个动态代理类 Proxy,Proxy 并不是我们上述所说的代理对象的类,而是提供了一个创建代理对象的静态方法(newProxyInstance
方法)来获取代理对象。
注:JDK 动态代要求必须定义接口,因为它只能对接口进行代理。
①、卖火车票的接口类:同静态代理。
public interface SellTickets {
void sell() ;
}
②、火车站类:火车票具有卖票功能,实现 SellTickets 接口,同静态代理。
public class TrainStation implements SellTickets {
@Override
public void sell() {
System.out.println("火车站售票");
}
}
③、获取代理对象的工厂类:ProxyFactory.java 通过工厂类获取代理对象,
/***
* 获取代理对象的工厂类
*
* 代理类也实现了对应的接口(此处接口指的是被代理对象实现的接口)
*/
public class ProxyFactory {
//声明目标对象(被代理对象)
private TrainStation station = new TrainStation() ;
public SellTickets getProxyObject(){
//返回代理对象
/*
* ClassLoader loader, // 类加载器,用于加载代理类,可以通过目标对象获取类加载器!
* Class<?>[] interfaces, //代理类实现的接口的字节码对象
* InvocationHandler h //代理对象的调用处理函数
*/
SellTickets proxyObject = (SellTickets) Proxy.newProxyInstance(
station.getClass().getClassLoader(),
station.getClass().getInterfaces(),
new InvocationHandler() {
/*
Object proxy, 代理对象,根ProxyObject是同一个对象,在invoke方法中基本不用
Method method, 对接口中的方法进行封装的method对象,本例子中指的就是sell
Object[] args 传递的实际参数
返回值:方法的返回值
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("代理点收取一定的费用(JDK动态代理)");
Object obj = method.invoke(station, args);// 通过反射执行目标对象中的方法(火车站中的sell方法)!
return obj;
}
}
);
return proxyObject ;
}
}
④、测试类client.java
public class Client {
public static void main(String[] args) {
ProxyFactory proxyFactory = new ProxyFactory();
SellTickets proxyObject = proxyFactory.getProxyObject();
proxyObject.sell(); //代理对象同样实现了SellTickets,此处用到多态,编译执行的是sell方法,本质执行的是invoke方法
}
}
测试结果 :
代理点收取一定的费用(JDK动态代理)
火车站售票
JDK动态代理底层的原理
思考问题:ProxyFactory 是代理类吗?
答:ProxyFactory 不是代理模式中所说的代理类,ProxyFactory 只是提供了一个生成代理对象的方法,而代理类是程序在运行过程中动
态的在内存中生成的。
1、准备工作:通过生成的代理对象可以获取到代理类的名字
System.out.println(proxyObject.getClass()); // class com.sun.proxy.$Proxy0
2、启动 Arthas【阿尔萨斯】,查看该代理类的结构 (注:已经删除非必要代码!)
// 程序运行过程中动态生成的代理类
public final class $Proxy0 extends Proxy implements SellTickets {
private static Method m3;
public $Proxy0(InvocationHandler invocationHandler) {
super(invocationHandler);
}
static {
m3 = Class.forName("com.itheima.proxy.dynamic.jdk.SellTickets").getMethod("sell", new Class[0]);
return;
}
public final void sell() {
this.h.invoke(this, m3, null);
}
}
我们看到:**1、**在静态代码块中==加载的是接口中的sell方法==,然后将其赋值给m3,**2、**代理类只存在一个有参构造,而且把参数通过
super(invocationHandler) ; 传递给父类Proxy中的成员变量 h ,这个invocationHandler就是我们的写的匿名内部类!
接下来就是:当我们代理对象调用sell 方法的时候,编译时执行的接口中的方法,运行时则是代理类中的sell方法,而代理类中的sell方法
public final void sell() {
this.h.invoke(this, m3, null);
}
会去调用我们匿名内部类invocationHandler中的invoke方法!
得出结论:代理对象的所有方法,都会被映射到invoke方法中执行 !
执行流程总结如下:
① 、在测试类中通过代理对象调用 sell() 方法
② 、根据多态的特性,执行的是代理类 $Proxy0 中的 sell() 方法
③ 、代理类 $Proxy0 中的 sell() 方法中又调用了 InvocationHandler 接口的子实现类对象的 invoke 方法
④ 、invoke 方法通过反射执行了真实对象所属类 TrainStation 中的 sell() 方法
上面的案例中,如果没有定义 SellTickets 接口,只定义了 TrainStation 火车站类。则 JDK 代理无法使用,因为 JDK 动态代理要求必须定
义接口,它只能对接口进行代理。
CGLIB 是一个功能强大,高性能的代码生成包,它可以为没有实现接口的类提供代理,为 JDK 的动态代理提供了很好的补充
CGLIB 是第三方提供的包,所以需要引入依赖:
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>2.2.2</version>
</dependency>
代码如下:CGLIB 动态代理可以不代理接口,直接代理类。
①、火车站类:火车票具有卖票功能
public class TranStation {
public void sell(){
System.out.println("火车站卖票!");
}
}
②、代理工厂类:用来获取代理对象
/**
* 代理对象工厂 : 用于获取我们的代理对象
*
* cglib的代理类,属于目标类的子类
*/
public class ProxyFactory implements MethodInterceptor {
private TranStation tranStation = new TranStation() ;
public TranStation getProxyObject(){
//创建Enhancer对象 , 类似于JDK代理中的Proxy类
Enhancer enhancer = new Enhancer() ;
//设置父类的字节码对象
enhancer.setSuperclass(TranStation.class);
//设置回调函数
enhancer.setCallback(this);
//创建代理对象
TranStation proxyObject = (TranStation) enhancer.create() ;
return proxyObject ;
}
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
System.out.println("代理点收取一定的费用(CGLIB实现动态代理)");
//调用目标类的方法
Object obj = method.invoke(tranStation, objects);
return obj;
}
}
两种动态代理的对比:JDK 代理 和 CGLib 代理
JDK 动态代理本质上是实现了被代理对象的接口,而 CGLib 本质上是继承了被代理对象,覆盖其中的方法
JDK 动态代理只能对实现了接口的类生成代理,CGLib 则没有这个限制。但是 CGLib 因为使用继承实现,所以 CGLib 所以无法对 final
类、private
方法和 static
方法进行代理。
JDK 动态代理是 JDK 里自带的,CGLib 动态代理需要引入第三方的 jar
包。
在性能上,JDK1.7 之前,由于使用了 FastClass 机制,CGLib 在执行效率上比 JDK 快,但是随着 JDK 动态代理的不断优化,从 JDK 1.7 开始,JDK 动态代理已经明显比 CGLib 更快了。所以,JDK1.8 以后,如果有接口就使用 JDK 动态代理,没有接口就使用 CGLib 代理。
动态代理和静态代理的对比:
动态代理与静态代理相比较,最大的好处是接口中声明的所有方法都被转移到调用处理器一个集中的方法中处理 (InvocationHandler.invoke)。这样,在接口方法数量比较多的时候,我们可以进行灵活处理,而不需要像静态代理那样每一个方法进行中转。
如果接口增加一个方法,静态代理模式除了所有实现类需要实现这个方法外,所有代理类也需要实现此方法,增加了代码维护的复杂度,而动态代理不会出现该问题。
优点:
缺点:
JDK 动态代理为什么只能对实现了接口的类生成代理?
答:根本原因是通过 JDK 动态代理生成的类已经继承了 Proxy 类,所以无法再使用继承的方式去对类实现代理(类只支持单继承)。
本文主要给大家介绍Mysql数据库分库和分表方式(常用),涉及到mysql数据库相关知识,对mysql数据库分库分表相关知识感兴趣的朋友一起学习吧 1 分库 1.1 按照功能分库 按照功能进行
在当前对象由其他包含对象操作的系统中,当传递对当前对象的引用时,链接似乎一直在继续......没有任何结束(对于下面的代码,Car ->myCurrentComponent->myCar_Brake-
我有一个密码 UIAlertView,我们要求用户提供。我需要根据情况在不同的 View 上询问它,从 downloadViewController (用户下载数据后),当他们切换到他们的数据时(如果
我正在尝试编写一个函数,使得对于任何整数 x 的 P(x) 都有一个包含三个元素的列表,即平方、立方和 n 的四次方,但我仍然不知道如何组合然后制作一个函数,例如我有平方、立方体和 4 次幂函数下面是
关闭。这个问题需要更多 focused .它目前不接受答案。 关闭4年前。 锁定。这个问题及其答案是locked因为这个问题是题外话,但具有历史意义。它目前不接受新的答案或交互。 我能否列出一份常见的
Python 常用 PEP8 编码规范 代码布局 缩进 每级缩进用4个空格。 括号中使用垂直隐式缩进或使用悬挂缩进。 EXAMPLE: ?
关闭。这个问题需要更多focused .它目前不接受答案。 想改善这个问题吗?更新问题,使其仅关注一个问题 editing this post . 去年关闭。 Improve this questio
在经典 ui 中,您可以使用 xtype:cqinclude 包含来自不同路径的 rtePlugins,基本上为标准 RTE 插件创建一个位置,我如何在 Touch UI 中执行相同操作? 我尝试使用
在经典 ui 中,您可以使用 xtype:cqinclude 包含来自不同路径的 rtePlugins,基本上为标准 RTE 插件创建一个位置,我如何在 Touch UI 中执行相同操作? 我尝试使用
*strong text*我有多个网络应用程序使用了一些常见的依赖项,比如蒙戈连接器谷歌 Guava 乔达时间 我想到将它们从 webapp/WEB-INF/lib 中取出并放入一些 common-l
我正在编写一个 Web 服务器,我想知道哪些 HTTP 请求 header (由客户端发送)是最常见的,因此我应该重点实现。 目前,我只支持Accept 和Host。 最佳答案 不确定您的范围,但由于
我是一名优秀的程序员,十分优秀!