目录
今天要学习的是:
java开发者必会的设计模式之一:代理模式
首先代理这个词很容易理解,日常生活我们也总是寻求代理去解决问题。先看一看两个名词:
代理对象:增强后的对象
目标对象:被增强的对象
一、静态代理
继承:
我们可以使用继承来实现一种代理。实现方式:用代理对象继承我们的目标对象,然后重写目标对象的方法。
现在我们来写一个《苦B勇者吃肉记》(为了简化内容,代码规范格式就不管了)
public class Person {
public String name;
public String gender;
public int money;
public Map <String ,Integer> AttributeValue;
public List<String> smallBag;
public Person(){};
public Person(String name, String gender){
this.name = name;
this.gender = gender;
this.money = 500;
this.smallBag = new ArrayList<>(5);
this.AttributeValueMap = new HashMap<>();
this.AttributeValueMap.put("HP",100);
this.AttributeValueMap.put("饱腹感",20);
}
public void eat(String food) {
System.out.println("吃了"+food+"。");
eated(food);
}
public void eated(String food){
if(food.equals("生肉")){
System.out.println("勇者吃了生肉之后感觉不太好,就去世了。");
}else if(food.equals("熟肉")){
System.out.println("味道棒极了,勇者神清气爽,活蹦乱跳,饱腹感+10 开心度+10");
}
}
}
上面就是我们的主角:苦B勇者,的各个属性;上帝赐给了勇者一块只卖34块5毛一斤的猪腿肉。
勇者看着这块肉,陷入了沉思:
public class Main {
public static void main(String[] args) throws Exception {
Person person = new Person("苦B勇者","男");
person.eat("生肉");
}
}
/*
吃了生肉。
勇者吃了生肉之后感觉不太好,就去世了。
*/
勇者想到自己直接吃了这块生肉可能就活不过这篇博客了,幸运的是他在垃圾桶旁发现了一个(锅)!于是他制作了一个简单的炊具(代理对象):
public class MakingFoodProxy extends Person {
public void eat(String food) {
System.out.println("烹饪"+food+"...");
food = "熟肉";
System.out.println("吃了"+food+"。");
eated(food);
}
}
这个时候,神奇的事情发生了。
public class Main {
public static void main(String[] args) throws Exception {
Person makingFoodProxy = new MakingFoodProxy();
makingFoodProxy.eat("生肉");
}
}
/*
烹饪生肉...
吃了熟肉。
味道棒极了,勇者神清气爽,活蹦乱跳,饱腹感+10 开心度+10
*/
好了,相信看完的大家已经知道什么是静态代理的继承方式了。
但是这种方式是由明显的缺陷的。首先我们必须去继承目标对象,这点就很让人不爽了。不可能我来一个代理就继承一下吧。
而且java是单继承的,这个方法就难以扩展。
接下来我们使用一个好一点方式:
聚合
这里勇者写了一个接口,并且让勇者自己的Person类去实现这个接口,勇者从Person进化成了PersonDaoImpl。
public interface PersonDao {
public void eat(String food);
}
public class PersonDaoImpl implements PersonDao{
//***重复代码不写了
}
勇者使用魔法:聚合--将代理类通过PersonDao依赖自己。实现eat方法中去写代理逻辑完了调用personDao的eat方法。
public class MakingFoodProxy implements PersonDao {
private PersonDao personDao;
public MakingFoodProxy(PersonDao personDao){
this.personDao = personDao;
}
@Override
public void eat(String food) {
System.out.println("烹饪"+food+"...");
food = "熟肉";
personDao.eat(food);
}
}
接着同样可以完成代理:
public static void main(String[] args) throws Exception {
PersonDao makingFoodProxy = new MakingFoodProxy(
new PersonDaoImpl("苦B勇者","男"));
makingFoodProxy.eat("生肉");
}
来回顾下勇者是怎么实现的:我们发现他的的目标对象和代理对象都实现了目标对象的接口。然后代理类将目标接口传进来,再实现代理逻辑,调用传入的接口的后续方法。
但是这样还是会有问题,勇者还是使用了很多类,类变多了管理起来很烦。那么勇者想能不能使用一个方法解决掉这个问题。
二、jdk动态代理
那么这就引入了jdk动态代理。jdk动态代理的使用很简单:如下
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
public class MyHandler implements InvocationHandler {
private PersonDao personDao;
public MyHandler(PersonDao personDao){
this.personDao = personDao;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//前置处理逻辑
before(args);
Object invoke = method.invoke(personDao, args);
//后置处理逻辑
after();
return invoke;
}
public void before( Object[] args){
System.out.println("烹饪"+args[0]+"...");
args[0] = "熟肉";
}
public void after(){}
}
public class Main {
public static void main(String[] args) throws Exception {
PersonDaoImpl personDao = new PersonDaoImpl("苦B勇者", "男");
PersonDao personProxy = (PersonDao) Proxy.newProxyInstance(PersonDao.class.getClassLoader(),
new Class[]{PersonDao.class}, new MyHandler(personDao));
personProxy.eat("生肉");
}
}
这里的前置和后置逻辑就是对目标对象的增强。
这里我们想到了事务,能不能使用这种方法实现?,再前置逻辑中开启事务,后置逻辑提交事务,中间逻辑是怎样还是怎样的。
从这里勇者仿佛快要领悟新的能力:AOP!
《勇者外传小插曲》
手写小jdk动态代理
勇者盲目自信,决定自己动手写一个小小的jdk动态代理。
勇者想怎么可以不使用很多类?勇者知道代理对象存在内存中,如果可以把java文件放在磁盘中,然后用到的时候就取出来一个代理对象那就好了。勇者观察自己造的锅,以后造东西也是遵循差不多的规则,有很多共性,也许可以抽取成一个模板。
于是勇者按照jdk动态代理的逻辑写了计划书:
1、先将一个代理类的文件模板制作好。(得到一个java文件)
2、把代理类文件变成calss文件。
3、用calssLoader加载磁盘上的calss文件。
4、通过魔法反射获得一个代理对象并返回。
勇者开始了苦逼的代码编写:
import javax.tools.JavaCompiler;
import javax.tools.StandardJavaFileManager;
import javax.tools.ToolProvider;
import java.io.File;
import java.io.FileWriter;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.net.URL;
import java.net.URLClassLoader;
public class ProxyUtil {
//得到代理对象的文件
public static Object newProxyInstance(Object target) throws Exception {
//拼接内容
String content = "";
Class<?> targetInfo = target.getClass().getInterfaces()[0];
String targetSimpleName = targetInfo.getSimpleName();
//这边自己定了
String packageContent = "package com.proxydemo;";
String importContent = "import " + targetInfo.getName() + ";";
String calssContnet = "public class $Proxy implements " + targetSimpleName + "{";
String fieldContent = "private " + targetSimpleName + " target;";
String constructorContent = "public $Proxy (" + targetSimpleName + " target){"
+ "this.target = target;"
+ "}";
Method[] methods = targetInfo.getDeclaredMethods();
StringBuilder methodsContent = new StringBuilder();
for (Method m : methods) {
String returnTypeName = m.getReturnType().getSimpleName();
String methodName = m.getName();
Class[] parameterTypes = m.getParameterTypes();
StringBuilder argsContent = new StringBuilder();
StringBuilder paramContent = new StringBuilder();
int varSuffix = 0;
for (Class parameterType : parameterTypes) {
String simpleTypeName = parameterType.getSimpleName();
argsContent.append(simpleTypeName).append(" d").append(varSuffix).append(",");
paramContent.append(" d").append(varSuffix).append(",");
varSuffix++;
}
if (argsContent.length() > 0) {
argsContent = new StringBuilder(argsContent.substring(0, argsContent.lastIndexOf(",") - 1));
paramContent = new StringBuilder(paramContent.substring(0, paramContent.lastIndexOf(",") - 1));
}
methodsContent.append("public ").append(returnTypeName).append(" ").append(methodName).append("(").append(argsContent).append("){").append("System.out.println(\"烹饪\"); ").append("target.").append(methodName).append("(").append(paramContent).append(");}");
}
content += packageContent + importContent + calssContnet + fieldContent + constructorContent + methodsContent + "}";
//写入文件、这里自己写路径
File file = new File("D:\\proxydemo\\$Proxy.java");
if (!file.exists()) {
file.createNewFile();
}
FileWriter fileWriter = new FileWriter(file);
fileWriter.write(content);
fileWriter.flush();
fileWriter.close();
//编译成class文件 jdk1.6的工具
JavaCompiler systemJavaCompiler = ToolProvider.getSystemJavaCompiler();
StandardJavaFileManager standardFileManager = systemJavaCompiler.getStandardFileManager(null, null, null);
//把文件放进去,可以放多个文件
Iterable javaFileObjects = standardFileManager.getJavaFileObjects(file);
JavaCompiler.CompilationTask task = systemJavaCompiler.getTask(null, standardFileManager, null, null, null, javaFileObjects);
task.call();
standardFileManager.close();
//加载文件
URL[] url = new URL[]{new URL("D:\\\\")};
URLClassLoader urlClassLoader = new URLClassLoader(url);
Class<?> aClass = urlClassLoader.loadClass("com.proxydemo.$Proxy");
//用构造方法获取对象返回
Constructor<?> constructor = aClass.getConstructor(targetInfo);
return constructor.newInstance(target);
}
}
勇者仿写了一些步骤,但是还有很多逻辑都没有实现。所以仅供阅读。
你猜勇者最后吃到肉了没?