1.什么是代理?

  对类或对象(目标对象)进行增强功能,最终形成一个新的代理对象,(Spring Framework中)当应用调用该对象(目标对象)的方法时,实际调用的是代理对象增强后的方法,比如对功能方法login实现日志记录,可以通过代理实现;

  PS:目标对象--被增强的对象;代理对象--增强后的对象;

  2.为什么需要代理?

  一些类里面的方法有相同的代码或类中有相同的功能,可以将这些相同抽取出来形成一个公共的方法或功能,但Java有两个重要的原则:单一职责(对类来说的,即一个类应该只负责一项职责)和开闭原则(开放扩展,修改关闭),如果每个类的每个功能都调用了公共功能,就破坏了单一职责,如下图;如果这个类是别人已经写好的,你动了这个代码,同时也破坏了开闭原则(同时改动代码很麻烦,里面可能涉及其他很多的调用,可能带出无数的bug,改代码比新开发功能还难/(ㄒoㄒ)/~~);

  由于有上面的问题存在,使用代理来实现是最好的解决办法;

  3.Java实现代理有哪些?

  (1)静态代理:通过对目标方法进行继承或聚合(接口)实现;(会产生类爆炸,因此在不确定的情况下,尽量不要使用静态代理,避免产生类爆炸)

    1)继承:代理对象继承目标对象,重写需要增强的方法;

//业务类(目标对象)
public class UserServiceImpl {
public void query(){
System.out.println("业务操作查询数据库....");
}
}
//日志功能类
public class Log {
public static void info(){
System.out.println("日志功能");
}
}
//继承实现代理(代理对象)
public class UserServiceLogImpl extends UserServiceImpl {
public void query(){
Log.info();
super.query();
}
}

    从上面代码可以看出,每种增强方法会产生一个代理类,如果现在增强方法有日志和权限,单个方法增强那需要两个代理类(日志代理类和权限代理类),如果代理类要同时拥有日志和权限功能,那又会产生一个代理类,同时由于顺序的不同,可能会产生多个类,比如先日志后权限是一个代理类,先权限后日志又是另外一个代理类。

    由此可以看出代理使用继承的缺陷:产生的代理类过多(产生类爆炸),非常复杂,维护难;

    2)聚合:代理对象和目标对象都实现同一接口,使用装饰者模式,提供一个代理类构造方法(代理对象当中要包含目标对象),参数是接口,重写目标方法;

//接口
public interface UserDao {
void query();
}
//接口实现类:目标对象
public class UserDaoImpl implements UserDao {
@Override
public void query() {
System.out.println("query......");
}
}
//日志功能类
public class Log {
public static void info(){
System.out.println("日志功能");
}
}
//聚合实现代理:同样实现接口,使用装饰者模式;(代理对象)
public class UserDaoLogProxy implements UserDao {
UserDao userDao;
public UserDaoLogProxy(UserDao userDao){//代理对象包含目标对象
this.userDao = userDao;
}
@Override
public void query() {
Log.info();
userDao.query();
}
}
//测试
public static void main(String[] args) {
UserDaoImpl target = new UserDaoImpl();
UserDaoLogProxy proxy = new UserDaoLogProxy(target);
proxy.query();
}

    聚合由于利用了面向接口编程的特性,产生的代理类相对继承要少一点(虽然也是会产生类爆炸,假设有多个Dao,每个Dao产生一个代理类,所以还是会产生类爆炸),如下案例:

//时间记录功能
public class Timer {
public static void timer(){
System.out.println("时间记录功能");
}
}
//代理类:时间功能+业务
public class UserDaoTimerProxy implements UserDao { UserDao userDao;
public UserDaoTimerProxy(UserDao userDao){
this.userDao = userDao;
}
@Override
public void query() {
Timer.timer();
userDao.query();
}
}
//测试
public static void main(String[] args) {
//timer+query
UserDao target = new UserDaoTimerProxy(new UserDaoImpl());
//log+timer+query
UserDao proxy = new UserDaoLogProxy(target);
proxy.query();
}
public static void main(String[] args) {
//log+query
UserDao target = new UserDaoLogProxy(new UserDaoImpl());
//timer+log+query
UserDao proxy = new UserDaoTimerProxy(target);
proxy.query();
}

  PS:

    装饰和代理的区别:代理不需要指定目标对象,可以对任何对象进行代理;装饰需要指定目标对象,所以需要构造方法参数或set方法指定目标对象;

    几个io的Buffer流(BufferedInputStream、BufferedOutputStream、BufferedReader、BufferedWriter)就是使用了装饰模式的静态代理;

public class BufferedReader extends Reader {
private Reader in;
........
public BufferedReader(Reader in, int sz) {
super(in);
if (sz <= 0)
throw new IllegalArgumentException("Buffer size <= 0");
this.in = in;
cb = new char[sz];
nextChar = nChars = 0;
} public BufferedReader(Reader in) {
this(in, defaultCharBufferSize);
}
}

  (2)动态代理:Java有JDK动态代理和CGLIB代理;(Spring Framework通过Spring AOP实现代理,底层还是使用JDK代理和CGLIB代理来实现)

    模拟动态代理:不需要手动创建类文件(因为一旦手动创建类文件,会产生类爆炸),通过接口反射生成一个类文件,然后调用第三方的编译技术,动态编译这个产生的类文件成class文件,然后利用URLclassLoader把这个动态编译的类加载到jvm中,然后通过反射把这个类实例化。(通过字符串产生一个对象实现代理);

    PS:Java文件 -> class文件 -> byte字节(JVM)-> class对象(类对象)-> new(对象);

    所以步骤如下:

      1)代码实现一个内容(完整的Java文件内容,包含包名、变量、构造方法、增强后的方法等),使其通过IO产生一个Java文件;
      2)通过第三方编译技术产生一个class文件;
      3)加载class文件利用反射实例化一个代理对象出来;
public class ProxyUtil {
/**
* content --->string
* |
* |生成
* v
* .java <-----通过io产生
* .class <-----Java文件编程产生
*
* .new <-----class文件反射产生实例对象
* @return
*/
public static Object newInstance(Object target){
Object proxy=null;
//根据对象获取接口
Class targetInf =target.getClass().getInterfaces()[0];
//获取接口的所有方法
//getMethods(),该方法是获取本类以及父类或者父接口中所有的公共方法(public修饰符修饰的)
//getDeclaredMethods(),该方法是获取本类中的所有方法,包括私有的(private、protected、默认以及public)的方法
Method[] declaredMethods = targetInf.getDeclaredMethods();
String line="\n";
String tab ="\t";
//接口名称
String targetInfName = targetInf.getSimpleName();
String content ="";
//包位置
String packageContent = "package com;"+line;
//接口
String importContent = "import "+targetInf.getName()+";"+line;
String clazzFirstLineContent = "public class $Proxy implements "+targetInfName+"{"+line;
//属性
String filedContent =tab+"private "+targetInfName+" target;"+line;
//构造方法
String constructorContent =tab+"public $Proxy ("+targetInfName+" target){" +line
+tab+tab+"this.target =target;"
+line+tab+"}"+line;
//方法
String methodContent = "";
for(Method method:declaredMethods){
//返回值
String returnTypeName = method.getReturnType().getSimpleName();
//方法名
String methodName = method.getName();
// Sting.class String.class 参数类型
Class<?>[] parameterTypes = method.getParameterTypes();
String argsContent = "";
String paramsContent="";
int flag = 0;
for(Class args :parameterTypes){
//String,参数类型
String simpleName = args.getSimpleName();
//String p0,Sting p1,
argsContent+=simpleName+" p"+flag+",";
paramsContent+="p"+flag+",";
flag++;
}
if (argsContent.length()>0){
argsContent=argsContent.substring(0,argsContent.lastIndexOf(",")-1);
paramsContent=paramsContent.substring(0,paramsContent.lastIndexOf(",")-1);
}
methodContent+=tab+"public "+returnTypeName+" "+methodName+"("+argsContent+") {"+line
//增强方法先写死
+tab+tab+"System.out.println(\"log\");"+line;
if(returnTypeName.equals("void")){
methodContent+=tab+tab+"target."+methodName+"("+paramsContent+");"+line
+tab+"}"+line;
}else{
methodContent+=tab+tab+"return target."+methodName+"("+paramsContent+");"+line
+tab+"}"+line;
}
}
content+=packageContent+importContent+clazzFirstLineContent+filedContent
+constructorContent+methodContent+"}";
//生成Java文件
File file = new File("D:\\com\\$Proxy.java");
try{
if(!file.exists()){
file.createNewFile();
}
//创建
FileWriter wr = new FileWriter(file);
wr.write(content);
wr.flush();
wr.close();
//编译Java文件
JavaCompiler compiler = ToolProvider.getSystemJavaCompiler(); StandardJavaFileManager fileMgr = compiler.getStandardFileManager(null, null, null);
Iterable units = fileMgr.getJavaFileObjects(file); JavaCompiler.CompilationTask t = compiler.getTask(null, fileMgr, null, null, null, units);
t.call();
fileMgr.close();
//new --> 反射
URL[] urls = new URL[]{new URL("file:D:\\\\")};
//加载外部文件
URLClassLoader classLoader = new URLClassLoader(urls);
Class<?> proxyClass = classLoader.loadClass("com.$Proxy");
Constructor constructor = proxyClass.getConstructor(targetInf);
//构造方法创建实例
proxy = constructor.newInstance(target);
//clazz.newInstance();//根据默认构造方法创建对象
//Class.forName()
}catch (Exception e){
e.printStackTrace();
}
return proxy;
}
}

  自定义代理的缺点:生成Java文件;动态编译文件;需要一个URLClassLoader;涉及到了IO操作,软件的最终性能体现到了IO操作,即IO操作影响到软件的性能;

案例:

  1)接口:

public interface UserDao {
void query();
void query(String name);
String getName(String id);
}

  2)实现类:

public class UserService implements UserDao {
@Override
public void query() {
System.out.println("query");
} @Override
public void query(String name) {
System.out.println(name);
} @Override
public String getName(String id) {
System.out.println("id:"+id);
return "李四";
}
}

  3)测试:

public static void main(String[] args) {
UserDao dao = (UserDao) ProxyUtil.newInstance(new UserService());
dao.query();
dao.query("张三");
}
--------结果--------
log
query
log
张三

          

package com;
import com.hrh.dynamicProxy.dao.UserDao;
public class $Proxy implements UserDao{//自定义动态代理生成的文件
private UserDao target;
public $Proxy (UserDao target){
this.target =target;
}
public String getName(String p) {
System.out.println("log");
return target.getName(p);
}
public void query(String p) {
System.out.println("log");
target.query(p);
}
public void query() {
System.out.println("log");
target.query();
}
}

  JDK动态代理:基于反射实现,通过接口反射得到字节码,然后将字节码转成class,通过一个native(JVM实现)方法来执行;

  案例实现:实现 InvocationHandler 接口重写 invoke 方法,其中包含一个对象变量和提供一个包含对象的构造方法;

public class MyInvocationHandler implements InvocationHandler {
Object target;//目标对象
public MyInvocationHandler(Object target){
this.target=target;
}
/**
* @param proxy 代理对象
* @param method 目标对象的目标方法
* @param args 目标方法的参数
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("log");
return method.invoke(target,args);
}
}
public class MyInvocationHandlerTest {
public static void main(String[] args) {
//参数: 当前类的classLoader(保证MyInvocationHandlerTest当前类可用)
// 接口数组:通过接口反射得到接口里面的方法,对接口里面的所有方法都进行代理
// 实现的InvocationHandler:参数是目标对象
UserDao jdkproxy = (UserDao) Proxy.newProxyInstance(MyInvocationHandlerTest.class.getClassLoader(),
new Class[]{UserDao.class},new MyInvocationHandler(new UserService()));
jdkproxy.query("query");
//-----结果------
//log
//query
}
}

  下面查看底层JDK生成的代理类class:

        byte[] bytes = ProxyGenerator.generateProxyClass("$Proxy18", new Class[]{UserDao.class});

        try {
FileOutputStream fileOutputStream = new FileOutputStream("xxx本地路径\\$Proxy18.class");
fileOutputStream.write(bytes);
fileOutputStream.flush();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}

  或在代码的最前面添加下面代码:

System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, "xxx本地路径");

  代理类class反编译后的内容:下面的内容验证了JDK动态代理为什么基于聚合(接口)来的,而不能基于继承来的?因为JDK动态代理底层已经继承了Proxy,而Java是单继承,不支持多继承,所以以接口来实现;

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
// import com.hrh.dao.UserDao;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.UndeclaredThrowableException; public final class $Proxy18 extends Proxy implements UserDao {
private static Method m1;
private static Method m4;
private static Method m5;
private static Method m2;
private static Method m0;
private static Method m3; public $Proxy18(InvocationHandler var1) throws {
super(var1);
} public final boolean equals(Object var1) throws {
try {
return (Boolean)super.h.invoke(this, m1, new Object[]{var1});
} catch (RuntimeException | Error var3) {
throw var3;
} catch (Throwable var4) {
throw new UndeclaredThrowableException(var4);
}
} public final void query(String var1) throws {
try {
super.h.invoke(this, m4, new Object[]{var1});
} catch (RuntimeException | Error var3) {
throw var3;
} catch (Throwable var4) {
throw new UndeclaredThrowableException(var4);
}
} public final void query() throws {
try {
super.h.invoke(this, m5, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
} public final String toString() throws {
try {
return (String)super.h.invoke(this, m2, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
} public final int hashCode() throws {
try {
return (Integer)super.h.invoke(this, m0, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
} public final String getName(String var1) throws {
try {
return (String)super.h.invoke(this, m3, new Object[]{var1});
} catch (RuntimeException | Error var3) {
throw var3;
} catch (Throwable var4) {
throw new UndeclaredThrowableException(var4);
}
} static {
try {
m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
m4 = Class.forName("com.hrh.dao.UserDao").getMethod("query", Class.forName("java.lang.String"));
m5 = Class.forName("com.hrh.dao.UserDao").getMethod("query");
m2 = Class.forName("java.lang.Object").getMethod("toString");
m0 = Class.forName("java.lang.Object").getMethod("hashCode");
m3 = Class.forName("com.hrh.dao.UserDao").getMethod("getName", Class.forName("java.lang.String"));
} catch (NoSuchMethodException var2) {
throw new NoSuchMethodError(var2.getMessage());
} catch (ClassNotFoundException var3) {
throw new NoClassDefFoundError(var3.getMessage());
}
}
}

  CGLIB代理:借助asm(一个操作字节码的框架)实现代理操作;CGLIB基于继承来的(前文有CGLIB代理类class的反编译可以看出);

public class UserService {
public void query(){
System.out.println("query");
} public static void main(String[] args) {
// 通过CGLIB动态代理获取代理对象的过程
Enhancer enhancer = new Enhancer();
// 设置enhancer对象的父类
enhancer.setSuperclass(UserService.class);
// 设置enhancer的回调对象
enhancer.setCallback(new MethodInterceptor() {
@Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
System.out.println("before method run...");
Object result = proxy.invokeSuper(obj, args);
System.out.println("after method run...");
return result;
}
});
//创建代理对象
UserService bean = (UserService) enhancer.create();
bean.query();
}
}
//-----------结果------
before method run...
query
after method run...

  PS:如果只是对项目中一个类进行代理,可以使用静态代理,如果是多个则使用动态代理;

  关于代理的其他相关知识介绍可参考前文:Spring(11) - Introductions进行类扩展方法Spring笔记(3) - SpringAOP基础详解和源码探究

Java代理简述的更多相关文章

  1. JAVA代理方式使用示例总结

    JAVA代理方式使用示例总结 一.    代理方式概括 Java的代理方式主要包含了静态代理,动态代理两种方式,其中,动态代理根据实现的方式不同,又可以划分为jdk动态代理和cglib动态代理. 二. ...

  2. Java代理模式

    java代理模式及动态代理类 1.      代理模式 代理模式的作用是:为其他对象提供一种代理以控制对这个对象的访问.在某些情况下,一个客户不想或者不能直接引用另一个对象,而代理对象可以在客户端和目 ...

  3. java 代理的三种实现方式

    Java 代理模式有如下几种实现方式: 1.静态代理. 2.JDK动态代理. 3.CGLIB动态代理. 示例,有一个打招呼的接口.分别有两个实现,说hello,和握手.代码如下. 接口: public ...

  4. java代理的深入浅出(二)-CGLIB

    java代理的深入浅出(二)-CGLIB 1.基本原理 CGLIB的原理就是生成一个要代理类的子类,子类重写要代理的类的所有不是final的方法.在子类中拦截所有父类方法的调用,拦截下来交给设置的Me ...

  5. java代理的深入浅出(一)-Proxy

    java代理的深入浅出(一)-Proxy 1.什么是代理 代理模式是常用的java设计模式,他的特征是代理类与委托类有同样的接口,代理类主要负责为委托类预处理消息.过滤消息.把消息转发给委托类,以及事 ...

  6. Java代理(静态/动态 JDK,cglib)

    Java的代理模式是应用非常广泛的设计模式之一,也叫作委托模式,其目的就是为其他的对象提供一个代理以控制对某个对象的访问和使用,代理类负责为委托类预处理消息,过滤消息并转发消息,以及对消息执行后续处理 ...

  7. JAVA代理机制

    JAVA代理相关主要知识如下: (1)利用代理可以在运行时创建一个实现了一组给定接口的新类.         这种功能只有在编译时无法确定需要实现哪个接口时才有必要使用. (2)假设有一个表示接口的C ...

  8. ajax发送请求跨域 - uri java代理

    问题:ajax发送请求出现cors跨域 解决办法:可以通过java代理的方式,后台发送请求 1.get请求 public void proxyGet(String url) { try { URL r ...

  9. java代理通俗简单解析

    1         代理 1.1            代理的概念和作用 代理的概念很好理解,就像黄牛代替票务公司给你提供票,经纪人代理艺人和别人谈合作.Java的代理是指实现类作为代理类的属性对象, ...

随机推荐

  1. DOM及相关操作

    1.背景介绍        什么是DOM?简单地说,DOM是一套对文档的内容进行抽象和概念化的方法, 在现实世界里,人们对所谓的'世界对象模型'都不会陌生,例如,当用'汽车'.'房子'和'树'等名词来 ...

  2. TERSUS无代码开发(笔记09)-简单实例前端样式设计

    前端常用样式设计 =========================================================================================== ...

  3. CentOS7安装Kafka2.6.0

    1:下载 wget https://mirror.bit.edu.cn/apache/kafka/2.6.0/kafka_2.12-2.6.0.tgz 点击前往官网 2:解压 tar -zxvf ka ...

  4. 在 c++ 程序中出现CtrIsValidHeapPointer问题

    在c++程序中出现CtrIsValidHeapPointer问题, 我发现的原因是申请了大量动态数组但是并没有把他们初始化 为数组赋初始值便可以很好解决这一问题.

  5. Python3.x 基础练习题100例(11-20)

    练习11: 题目: 古典问题:有一对兔子,从出生后第3个月起每个月都生一对兔子,小兔子长到第三个月后每个月又生一对兔子,假如兔子都不死,问每个月的兔子总数为多少? 分析: 兔子的规律为数列1,1,2, ...

  6. Python列表元组和字典解析式

    目录 列表解析式List comprehensive 集合解析式Set comprehensive 字典解析式Dict comprehensive 总结 以下内容基于Python 3x 列表解析式Li ...

  7. 使用CSS计数器美化数字有序列表

    在web设计中,使用一种井井有条的方法来展示数据是十分重要的,这样用户就可以很清晰的理解网站所展示的数据结构和内容,使用有序列表就是实现数据有组织的展示的一种简单方法. 如果你需要更加深入地控制有序列 ...

  8. C# webapi跨域

    C# webapi跨域   第一种在Web.config中<system.webServer>节点中配置(不支持多个域名跨域) 1 <httpProtocol> 2 <c ...

  9. 【Arduino学习笔记06】上拉电阻和下拉电阻

    为什么要用上拉电阻和下拉电阻?--避免输入引脚处于"悬空"状态 下图是一个没有使用上拉电阻/下拉电阻的电路图: 在按键没有按下时,要读取的输入引脚没有连接到任何东西,这种状态就称为 ...

  10. linux_MYSQL 数据库自动备份并压缩和删除历史备份

    1. 创建shell脚本 #! /bin/bash# MySQL用户user="root"# MySQL密码userPWD="123456789"# 需要定时备 ...