一、面向对象之多态
多态是同一个行为具有多个不同表现形式或形态的能力
1.1 实现多态的关键是接口
- 接口是一个抽象的类型,是提供相应方法的定义
- 实现类是一个接口得具体实现,要实现每一个接口方法的功能
1.2 具体实现
sample1.system 功能封装的包
//Lanuague.interface
/**
* 接口类,实现不同的动作
*/
public interface Language {
//模拟各国语言的方法定义
public void voice();
}
//Chinese.class
//实现类
public class Chinese implements Language {
@Override
public void voice() {
System.out.println("中国");
}
}
//French.class
//实现类
public class French implements Language{
@Override
public void voice() {
System.out.println("法国");
}
}
//English.class
//实现类
public class English implements Language{
@Override
public void voice() {
System.out.println("英语");
}
}
//CustomerService.class
public class CustomerService {
/**
* 封装实现类
* 多态的实现,不同的参数,返回不同的动作
* @param areaCode
* @return
*/
public Language contact(int areaCode){
if(areaCode == 86){
return new Chinese();
}
else if(areaCode == 33){
return new French();
}
else{
return new English();
}
}
}
sample1 包外调用封装包,实现多态
//Customer.class
public class Customer {
/**
* 在系统外去调用系统中的方法,不需要了解实现细节
* 只需要传入不同的参数,就能实现不同的功能
* 多态:通过调用同一个动作,传入不同的参数,产生不同的行为
* @param args
*/
public static void main(String[] args) {
CustomerService customerService = new CustomerService();
//接口本身不能被实例化,需要指向相应的实现类
//而不同的参数就能返回不同的实现类,从而实现多态
Language language = customerService.contact(86);
language.voice();
}
}
以上就是多态的基本实现
1.3 多态的优势
扩展性强,父类的变量可以赋值不同的子类对象,而调用不同的子类重写的方法
二、代理模式概念及分类
2.1 代理模式基本概念
为其他对象提供一种代理,以控制对这个对象的访问。代理对象起到中介的作用,可去掉功能服务或增加额外的服务。
常见的几种代理模式:
- 远程代理(remote proxy)
为不同地理的对象,提供局域网代表对象
简单应用:通过远程代理可以监控各个商铺,使之能直观的了解店内信息
- 虚拟代理(virtual proxy)
根据需要将资源消耗很大的对象进行延迟,真正需要的时候进行创建
简单应用:网页上浏览新闻时,若其中的图片过大,会导致整篇文章无法阅读;使用虚拟代理,就可以在图片加载时使用虚拟的图片代替,加载完后再使用原来的图片,期间可以正常阅读
- 保护代理
控制对一个对象的访问权限
简单应用:一个网站需要登录后才能使用一些功能,未登录前只能使用有限的功能
- 智能引用代理
提供对目标对象额外的服务
例如:火车站售票站,提供了火车站额外的一些服务
三、常见代理模式原理
3.1 以智能引用代理为例(静态代理)
两种实现方式:
- 静态代理
代理和被代理对象在代理之前是确定的。它们都实现相同的接口或继承相同的抽象类
- 动态代理
(1)未提供代理模式的实现:
//Moveable.class
/**
* 汽车行驶接口,提供方法
*/
public interface Moveable {
void move();
}
//Car1.class
/**
* 汽车行驶实现类
*/
public class Car1 implements Moveable{
@Override
public void move() {
long start = System.currentTimeMillis();
System.out.println("汽车开始行驶...");
try {
Thread.sleep(new Random().nextInt(1000)); //1000毫秒==1秒
System.out.println("汽车行驶中...");
} catch (InterruptedException e) {
e.printStackTrace();
}
long end = System.currentTimeMillis();
System.out.println("汽车结束行驶... 行驶时间为:"+(end-start) + "毫秒");
}
}
//Test.class
/**
* 汽车行驶测试类
*/
public class Test {
public static void main(String[] args) {
Car1 car = new Car1();
car.move();
}
}
(2)使用继承方式来实现:
//Moveable.class
//同样的接口类
//Car2.class
/**
* 为了体现出静态代理的区别,只保留汽车行驶核心的部分
*/
public class Car2 implements Moveable{
@Override
public void move() {
try {
Thread.sleep(new Random().nextInt(1000)); //1000毫秒==1秒
System.out.println("汽车行驶中...");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
//Test2.class
/**
* 继承了Car2类
*/
public class Test2 extends Car2{
/**
* 调用父类的方法,并添加一些功能
*/
@Override
public void move() {
long start = System.currentTimeMillis();
System.out.println("汽车开始行驶...");
super.move(); //super关键字,引用父类中的成员
long end = System.currentTimeMillis();
System.out.println("汽车结束行驶... 行驶时间为:"+(end-start) + "毫秒");
}
}
//Test.class
/**
* 汽车行驶测试类,最终的效果一致
*/
public class Test {
public static void main(String[] args) {
//继承方式
Moveable m = new Test2();
m.move();
}
}
(3)使用聚合方式实现
简而言之就是在一个类中调用另外一个对象
//Moveable.class
//还是相同的接口
//Car2.class
//一样的实现类
//Car3.class
/**
* 聚合方式,实现静态代理
* 在类中调用另一个对象的move方法,结果一致
*/
public class Car3 implements Moveable{
private Moveable car;
public Car3(Moveable m) {
super(); //调用父类的构造方法
this.car = m;
}
//增加新的功能
@Override
public void move() {
long start = System.currentTimeMillis();
System.out.println("汽车开始行驶...");
this.car.move();
long end = System.currentTimeMillis();
System.out.println("汽车结束行驶... 行驶时间为:"+(end-start) + "毫秒");
}
}
3.2 聚合比继承更适合代理模式
使用继承的方式:
假设我们需要添加新的功能或者修改功能的实现顺序:
每次都需要重新新建一个类,去重新构造这些方法,所以很麻烦
例如:
我先构造一个时间代理类,实现了记录时间的功能
接下来我就只能将时间代理类放在最里层
而其他的代理需要继承时间代理后,再去实现其他的功能
//Car2.class
//和前面一样,实现的是汽车行驶的功能
//Test2.class
public class Test2 extends Car2{
/**
* 调用父类的方法,并添加了记录时间的功能
* 继承方式
*/
@Override
public void move() {
long start = System.currentTimeMillis();
System.out.println("汽车开始行驶...");
super.move(); //汽车核心功能
long end = System.currentTimeMillis();
System.out.println("汽车结束行驶... 行驶时间为:"+(end-start) + "毫秒");
}
}
//Car3.class
//接下来我想要实现日志和时间的代理
//就需要再次继承,并且两者顺序已经不能改变
//如果改变就需要重写Test.class 类
public class Car3 extends Test2{
@Override
public void move() {
System.out.println("日志记录开始...");
super.move();
System.out.println("日志记录结束...");
}
}
使用聚合的方式:
这种方法相比继承更加灵活、方便
//CarTimeProxy.class
//汽车行驶时间的代理
public class CarTimeProxy implements Moveable{
private Moveable car;
public CarTimeProxy(Moveable m) {
this.car = m;
}
@Override
public void move() {
long start = System.currentTimeMillis();
System.out.println("汽车开始行驶...");
this.car.move();
long end = System.currentTimeMillis();
System.out.println("汽车结束行驶... 行驶时间为:"+(end-start) + "毫秒");
}
}
//CarLogProxy.class
//汽车行驶日志的代理
public class CarLogProxy implements Moveable{
private Moveable car;
public CarLogProxy(Moveable car) {
this.car=car;
}
@Override
public void move() {
System.out.println("日志记录开始...");
car.move();
System.out.println("日志记录结束...");
}
}
//Test.class
//实现类
//不同的代理类分开实现,**最后实现的顺序根据传入的顺序调整**
public class Test {
public static void main(String[] args) {
//先实现日志 -> 时间 -> 行驶
//调整传入参数的顺序,就能调整代理的顺序
Car2 car = new Car2(); //汽车行驶类
CarTimeProxy carTimeProxy = new CarTimeProxy(car); //汽车时间代理
CarLogProxy carLogProxy = new CarLogProxy(carTimeProxy); //汽车日志代理
carLogProxy.move();
}
}
3.3 JDK动态代理
根据前面的汽车行驶代理,看上去更方便了,但是它只能实现汽车的代理
那么不同的对象就需要不同的代理类来实现,这样代理类就越来越多了
那么引出一个问题:
能不能动态产生代理,实现对不同对象、方法的代理?(火车、飞机等)
Java动态代理类,位于java.lang.reflect包下,一般涉及到一下两个类:
- Interface InvocationHandler
该接口仅定义了一个方法 public Object invoke(Object obj,Method method,Object[] args)
在实际使用时,第一个参数obj指的是代理类,method是被代理的方法,args为该方法的参数数组
这个抽象方法在代理类中动态实现。
- Proxy
该类即为动态代理类
static Object newProxyInstance(ClassLoader loader,Class[] interfaces,InvocationHandler h)
返回代理类的一个实例,返回后的代理类可以当做被代理类来使用(可使用被代理类的在接口中声明的方法)
//TimeHandler.class
//事件处理器
public class TimeHandler implements InvocationHandler {
private Object target; //我们传入的代理类,一切类的父类都是Object
public TimeHandler(Object target) {
super();
this.target = target;
}
/**
*
* @param proxy 被代理的对象
* @param method 被代理对象的方法
* @param args 方法的参数
* @return Object 方法的返回值
* @throws Throwable
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
long start = System.currentTimeMillis();
System.out.println("汽车开始行驶...");
method.invoke(target); //动态调用方法
long end = System.currentTimeMillis();
System.out.println("汽车结束行驶... 行驶时间为:"+(end-start) + "毫秒");
return null; //car2.move()方法没有返回值,return null
}
}
//Test.class
/**
* JDK动态代理测试类
*/
public class Test {
public static void main(String[] args) {
Car2 car2 = new Car2();
InvocationHandler h = new TimeHandler(car2); //事件处理器
Class<?> cls = car2.getClass(); //Class<?>
/**
* loader 被代理类的类加载器
* interfaces 实现的接口
* h 事件处理器 (invocationHandler)
* 返回的是一个动态代理对象
*/
Moveable m = (Moveable) Proxy.newProxyInstance(cls.getClassLoader(),cls.getInterfaces(),h);
m.move();
}
}
总结:
所谓动态代理(Dynamic Proxy)是这样一种 Class:
它是在运行时生成的Class(Proxy类)
该Class需要实现一组interface
使用动态代理类时,必须实现InvocationHandler接口(由它来实现我们需要的功能)
3.4 CGLIB动态代理
JDK动态代理与CGLIB动态代理的区别
JDK动态代理:
1、只能代理实现了接口的类
2、没有实现接口的类不能使用JDK动态代理
CGLIB动态代理:
1、针对类来实现代理
2、对指定目标类产生一个子类,通过方法拦截技术拦截所有父类方法调用
可能由于高版本JDK的原因,基于子类的动态代理会产生报错:(还未解决)
module java.base does not “opens java.lang” to unnamed module @722c41f4
//Train.class
//被代理类
public class Train {
public void move(){
System.out.println("火车行驶中");
}
}
//CglibProxy.class
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;
/**
* 代理类
*/
public class CglibProxy implements MethodInterceptor {
//传入的目标类
private Object o ;
//返回被代理对象的子类
public Object getProxy(Object o){
this.o = o;
Enhancer enhancer = new Enhancer(); //用来生成代理的工厂类
//生成目标类的子类,来增强(所以需要知道目标类)
//设置被代理类的类型type
enhancer.setSuperclass(o.getClass());
//回调,应该 是回调调用函数,调用的函数被intercept函数拦截重写
enhancer.setCallback(this);
//动态生成代理类
return enhancer.create();
}
/**
*描述如何增强父类
* @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 {
//增强代码
//1. 利用代理类来实现增强代码
System.out.println("日志记录开始...");
//代理类的方法对象调用父类的方法
Object i = methodProxy.invokeSuper(o,objects);
System.out.println("日志记录结束...");
// Train t = new Train();
// method.invoke(t);
/**
* 2. 利用被代理类实现增强代码
* System.out.println("日志记录开始...");
* Train t = new Train();
* method.invoke(t);
* System.out.println("日志记录结束...");
*/
return i;
}
}
//Client.class
//测试类
public class Client {
public static void main(String[] args) {
CglibProxy cglibProxy = new CglibProxy();
//返回的是代理类的对象
Train t = new Train();
Train train = (Train) cglibProxy.getProxy(t);
train.move();
}
}
四、自定义模拟JDK动态代理的实现
4.1 JDK动态代理的基本实现
首先理一下JDK动态代理的逻辑:
- 实现功能:通过Proxy类的newProxyInstance方法返回代理对象
- 首先,声明一段源码(动态产生代理)
- 然后,编译源码(JDK Compiler API),产生新的类(代理类)
- 其次,将这个类load到内存当中,产生一个新的对象(代理对象)
- 最后,返回这个代理对象
//Proxy.java
/**
* 自定义Proxy类的newProxyInstance方法,返回一个代理对象
*/
public class Proxy {
//传入infce 接口,动态产生代理类
public static Object newProxyInstance(Class infce)throws Exception{
//由接口传入方法,动态产生代理类
String methodStr ="";
for (Method m:infce.getMethods()
) {
methodStr +=" @Override\r\n" +
" public void "+m.getName()+"() {\r\n" +
" long start = System.currentTimeMillis();\r\n" +
" System.out.println(\"汽车开始行驶...\");\r\n" +
" this.car."+m.getName()+"();\r\n" +
" long end = System.currentTimeMillis();\r\n" +
" System.out.println(\"汽车结束行驶... 行驶时间为:\"+(end-start) + \"毫秒\");\r\n" +
" }\r\n";
}
String str = "package com.my.jdkproxy;\r\n" +
"public class $Proxy0 implements "+infce.getName()+"{\r\n" +
" private "+infce.getName()+" car;\r\n" +
" public $Proxy0("+infce.getName()+" m) {\r\n" +
" super();\r\n" +
" this.car = m;\r\n" +
" }\r\n" +
methodStr +
"}";
//将动态产生的代理类写入文件
String filename=System.getProperty("user.dir")+"/src/com/my/jdkproxy/$Proxy0.java";
File file = new File(filename);
FileUtils.writeStringToFile(file,str);
//为了编译上述文件
// 拿到编译器
JavaCompiler javaCompiler = ToolProvider.getSystemJavaCompiler();
//文件管理者
StandardJavaFileManager fileMgr =
javaCompiler.getStandardFileManager(null,null,null);
//获取文件
Iterable units = fileMgr.getJavaFileObjects(filename);
//编译任务
/**
* out 文件输出的位置
* fileManeger 文件管理者
* diagnosticListener 监听器
* options
* classes
* compilationUnits 需要编译的文件
* return 返回一个编译任务
*/
JavaCompiler.CompilationTask t = javaCompiler.getTask(null,fileMgr,null,null,null,units);
//进行编译
t.call();
fileMgr.close();
//将编译好的文件 load 到内存当中
ClassLoader cl = ClassLoader.getSystemClassLoader();
Class c = cl.loadClass("com.my.jdkproxy.$Proxy0");
//产生代理类
Constructor ctr = c.getConstructor(infce);
//返回代理对象
return ctr.newInstance(new CarService());
}
}
//Client.java
//测试类
public class Client {
public static void main(String[] args) throws Exception {
Moveable m = (Moveable) Proxy.newProxyInstance(Moveable.class);
m.move();
}
}
到这基本实现了动态代理(任意对象的任意方法),但是还无法修改业务逻辑,那么如何做呢?
4.2 动态代理实现添加 InvocationHandler
//interface InvocationHandler.java
public interface InvocationHandler {
//对某个对象的方法进行处理
/**
*
* @param o 传递的是代理对象
* @param m 传递方法(还可以传递方法参数)
*/
public void invoke(Object o, Method m) throws InvocationTargetException, IllegalAccessException;
}
//Proxy.java
**
* 自定义Proxy类的newProxyInstance方法,返回一个代理对象
*/
public class Proxy {
//传入infce 接口,动态产生代理类
public static Object newProxyInstance(Class infce,InvocationHandler h)throws Exception{
//由接口传入方法,动态产生代理类
String methodStr ="";
for (Method m:infce.getMethods()
) {
methodStr +=" @Override\r\n" +
" public void "+m.getName()+"() {\r\n" +
"try{\r\n"+
"Method md = "+infce.getName()+".class.getMethod(\""+
m.getName()+"\");\r\n"+
" h.invoke(this,md);\r\n"+
"}catch(Exception e){e.printStackTrace();}\r\n"+
" }\r\n";
}
String str = "package com.my.jdkproxy;\r\n" +
"import java.lang.reflect.Method;\r\n"+
"import com.my.jdkproxy.InvocationHandler;\r\n"+
"public class $Proxy0 implements "+infce.getName()+"{\r\n" +
"private InvocationHandler h;\r\n"+
" public $Proxy0(InvocationHandler h) {\r\n" +
" super();\r\n" +
" this.h = h;\r\n" +
" }\r\n" +
methodStr +
"}";
//将动态产生的代理类写入文件
String filename=System.getProperty("user.dir")+"/src/com/my/jdkproxy/$Proxy0.java";
File file = new File(filename);
FileUtils.writeStringToFile(file,str);
//为了编译上述文件
// 拿到编译器
JavaCompiler javaCompiler = ToolProvider.getSystemJavaCompiler();
//文件管理者
StandardJavaFileManager fileMgr =
javaCompiler.getStandardFileManager(null,null,null);
//获取文件
Iterable units = fileMgr.getJavaFileObjects(filename);
//编译任务
/**
* out 文件输出的位置
* fileManeger 文件管理者
* diagnosticListener 监听器
* options
* classes
* compilationUnits 需要编译的文件
* return 返回一个编译任务
*/
JavaCompiler.CompilationTask t = javaCompiler.getTask(null,fileMgr,null,null,null,units);
//进行编译
t.call();
fileMgr.close();
//将编译好的文件 load 到内存当中
ClassLoader cl = ClassLoader.getSystemClassLoader();
Class c = cl.loadClass("com.my.jdkproxy.$Proxy0");
//产生代理类
Constructor ctr = c.getConstructor(InvocationHandler.class);
//返回代理对象
return ctr.newInstance(h);
}
}
//Client.java
//测试类
public class Client {
public static void main(String[] args) throws Exception {
CarService c = new CarService();
InvocationHandler h =new TimeHandler(c);
Moveable m = (Moveable) Proxy.newProxyInstance(Moveable.class,h);
m.move();
}
}
这里实现了InvocationHandler接口(也就是事务处理器),相比较前一个未实现事务处理器的Proxy类,这一个也就能实现业务代码的增加
那么到这里,JDK动态代理的大致流程我们都知道了
1、我们需要动态代理产生代理类,就需要实现Proxy类,其返回了一个代理类
2、而为了实现业务逻辑,我们需要一个类来实现InvocationHandler,这个类在实现被代理类的功能下,增加了业务逻辑
3、我们将事务处理器以及被代理类的接口传入Proxy类中,最后就会产生并编译代理类,最后返回给我们一个代理对象
五、总结
总结一下本篇文章所学到的知识:
代理概念、分类及应用场景
静态代理(继承、聚合)
JDK动态代理(动态代理不仅可以使用JDK动态代理,还有其他的,如:cglib动态代理)
JDK动态代理需要被代理类实现接口类,cglib动态代理是基于一个普通的类来实现动态代理
模拟JDK动态代理的实现过程
动态代理是有实际意义的:当我们使用一个包中的类的某个方法(无法修改),就需要使用代理模式
这种方式也叫做AOP(面向切面编程):
在不改变原有类的基础上,增加一些额外的业务逻辑
- 本文链接:http://siii0.github.io/java%E4%BB%A3%E7%90%86%E6%A8%A1%E5%BC%8F/
- 版权声明:本博客所有文章除特别声明外,均默认采用 许可协议。