1.前言

  最近舍友去面试遇到了关于java代理模式的问题。

  我虽然知道怎么使用,但是没有做过正经的总结,因此有了这篇随笔,好好总结一下三大代理模式底层原理。

事实上,在开发项目的时候,基本用不上代理,一般使用代理都是修改以前的代码才用到了,没人闲着在业务层多套一层代理吧???

  为什么使用代理?

  原因是不希望影响原有的业务代码的同时,添加自定义的操作,如写日志、权限拦截、发短信、发邮件等,这样的操作叫做增强操作,
但是这一般是aop【面向切面编程】做的,虽然aop底层就是使用代理做的,但是用aop方便很多,因为使用aop完全不需要改变原有任何的业务代码,
而代理还需要套一层代理类。这就是差距。
  事实上不论是代理还是aop,根本上都是竖向执行业务的,有先后循序,为了解决这个问题,后来有了消息中间件 ,可以横向执行业务,当然这都是后话。
因此 不论是 aop还是代理,更多的是希望在尽可能不改变原有的业务代码而加入其他操作,至于喜欢使用aop还是代理都是看自己的喜好了。

  代理模式分静态代理、动态代理、CGLib代理,

注意了 :

动态代理  也叫  JDK代理 、接口代理 ,
Cglib代理 也叫 子类代理

  那么这些代理模式的关系是什么?

  一开始并没有所谓的代理概念,为了解决增强操作,其实一开始就是目标类外层套一个新类做新的业务,需要执行目标类方法时,直接实例目标类执行而已,
并没有什么特别的东西,后来有心人将这种增强写法规范了格式,在代理类实现了与目标类一样的接口,重写接口内容,在里面做增强业务,不仅可以确保不写错,
还可以不用手写,这就很舒服了,于是就有了现在的静态代理写法,但是这种增强操作缺点也是显然易见的,
因为代理对象需要与目标对象实现一样的接口,所以会有很多代理类,类太多。同时,一旦接口增加方法,目标对象与代理对象都要维护。
  是不是很烦?于是出来了个叫动态代理的东西,解决了静态代理的缺点,而且在代理类还不需要实现与目标类一样的接口就可以达到一样的目的,
可是,虽然代理类没有直接实现在目标类所实现的接口,但是在内存动态的创建代理对象 方法的注入参数 里 需要 目标对象实现的接口的类型 ,
因此目标类必须要最少实现一个接口 ,否则无法使用动态代理。
  那这就出了个很大的问题,有些目标类不是业务层的,仅仅是一个单独的类 ,没有实现任何接口,是不能使用动态代理的。
  这又该怎么办?
  好家伙,又出了个叫 CGLib代理的东西 ,完美解决了 动态代理的缺点,继承了动态代理的优势 ,又不需要目标类需要实现接口的要求,
但是CGLib代理 不是内置在jdk里的,需要第三方jar包导入才能使用,其实就是个底层为 ASM字节码框架 的 代码生成包,不建议直接操作ASM。
【动态代理是内置在jdk的,这就是动态代理也叫jdk代理的原因】

  总结

静态代理总结:
(1)其实,所谓的静态代理,其实就是创建一个类【名为代理类】,然后该类实现了与目标类【业务层对象】一样的接口后,
重写接口内容,里面做增强业务,也就是自定义操作,然后需要执行目标类的方法时,调用注入的目标类[即业务层对象]的方法即可,
说白了就是目标类外再套一层方法而已。
(2)重写目的是保证与目标类的方法一样,不仅可以确保不写错,还可以不用手写,这就很舒服了,
但是并不是说目标类必须要有接口才可以使用静态代理,因此,目标类不论是否有接口都可以使用静态代理。
(3)缺点:因为代理对象需要与目标对象实现一样的接口,所以会有很多代理类,类太多。同时,一旦接口增加方法,目标对象与代理对象都要维护。
动态代理总结:
(1)动态代理又称为jdk代理、接口代理。
(2)动态代理解决静态代理的缺点,代理类不需要再与目标对象实现一样的接口。
(3)实例代理类后,注入目标类对象即可,然后调用代理类的方法会在内存动态的创建代理对象【使用Proxy.newProxyInstance】后将该代理对象返回,
然后强转成目标对象一样的类型即可按照目标类型方法调用,创建代理对象需要输入的参数 分别是 目标类的类加载器 、目标对象实现的接口的类型、事件处理器 ,
然后重写事件处理器里的方法,该方法内写自定义的增强业务,而目标对象的方法调用则固定使用 Object returnValue = method.invoke(target, args);完成 ,
因此,如果需要对调用的当前方法分别做不同的操作,可以使用method.getName()获取当前执行的方法名字后判断,然后执行不同的增强操作,
那么如果需要对不同的目标类做不同的操作呢?那么可以使用target.getClass().getName()获取当前注入的目标类对象的名字。
这么做的好处就是不需要创建大量动态代理类做不同的增强操作,这是与静态代理最大的区别,但是我感觉这样做会变不伦不类了。
这样做的流程是 实例代理类-》注入目标对象-》内存创建代理对象-》判断当前注入对象名称-》判断当前执行的方法-》做增强业务-》返回结果
(4)虽然代理类没有直接实现在目标类所实现的接口,但是在内存动态的创建代理对象 方法的注入参数需要 目标对象实现的接口的类型 ,
因此目标类必须要最少实现一个接口 ,否则无法使用动态代理。
(5)缺点:有些类不是业务层的,仅仅是一个单独的类 ,没有实现任何接口,是不能使用动态代理的。
CGLib代理总结:
(1)记得需要导入 cglib依赖包 ,这其实是个代码生成包,在内存中构建一个子类对象从而实现对目标对象功能的扩展。
(2)Cglib 代理 又称为 子类代理。
(3)代理类需要实现MethodInterceptor接口 ,用于重写拦截方法【intercept()】,内部的增强操作方式 与 动态代理十分相似。
(4)创建代理对象需要使用工具类Enhancer。
(5)允许代理那些那么没有实现任何接口的目标类,解决了动态代理的缺点。
(6)底层是ASM字节码框架,不建议直接操作ASM。

2.静态代理

(1)目录结构

服务层接口

package com.example.javabaisc.proxy.mstatic.service;

public interface UserService {
void getname();
}

接口实现类【即目标类】

package com.example.javabaisc.proxy.mstatic.service.serviceImpl;

import com.example.javabaisc.proxy.mstatic.service.UserService;

public class UserServiceImpl implements UserService {
@Override
public void getname() {
System.out.println("老王跑啦");
}
}

静态代理类

package com.example.javabaisc.proxy.mstatic.mproxy;

import com.example.javabaisc.proxy.mstatic.service.UserService;
import com.example.javabaisc.proxy.mstatic.service.serviceImpl.UserServiceImpl; import java.util.Date; /**
* 代理类
*/
public class UserServiceProxy implements UserService {
//final可有可不有 ,但是最后idea提示最好加上去
//类型定义必须与注入类型对应,否则需要强转
private final UserServiceImpl target; //有参构造函数,用于注入业务层的实现类
public UserServiceProxy(UserServiceImpl target){
this.target = target;
} @Override
public void getname() {
System.out.println("进入静态代理类,准备开始增强业务");
System.out.println("老王人哪去了?");
target.getname();
System.out.println("准备退出静态代理类,增强业务结束,"+new Date());
}
}

测试类

package com.example.javabaisc.proxy.mstatic;

import com.example.javabaisc.proxy.mstatic.mproxy.UserServiceProxy;
import com.example.javabaisc.proxy.mstatic.service.UserService;
import com.example.javabaisc.proxy.mstatic.service.serviceImpl.UserServiceImpl;
import org.junit.jupiter.api.Test; /**
* 静态代理
*/ public class MSTest { //无静态代理
@Test
public void t() {
//实例业务层对象【类型也可以使用接口的】
UserServiceImpl userService = new UserServiceImpl();
//执行业务层
userService.getname();
} //使用静态代理
@Test
public void t2() {
//实例业务层对象【类型也可以使用接口的】
UserServiceImpl userService = new UserServiceImpl();
//实例代理类,并注入业务层对象
UserServiceProxy userServiceProxy = new UserServiceProxy(userService);
//执行代理业务
userServiceProxy.getname();
} }

(2)执行测试类 t()方法

打印结果

(3)执行测试类 t2()方法

打印结果

3.动态代理

(1)目录结构

服务层接口

package com.example.javabaisc.proxy.mdynamic.service;

public interface UserService {
void getname();
}

接口实现类【即目标类】

package com.example.javabaisc.proxy.mdynamic.service.serviceImpl;

import com.example.javabaisc.proxy.mdynamic.service.UserService;

public class UserServiceImpl implements UserService {
@Override
public void getname() {
System.out.println("老王跑啦");
}
}

动态代理类

package com.example.javabaisc.proxy.mdynamic.mproxy;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.Date; /**
* 代理类
*/ public class DynamicProxy { //目标对象
//final可有可不有 ,但是最后idea提示最好加上去
private final Object target; //有参构造函数,用于注入业务层的实现类
public DynamicProxy(Object target) {
this.target = target;
}
//方法名可随意定义,这个无关紧要,目的是调用newProxyInstance去内存动态创建代理对象
public Object getProxyInstance() {
//到内存动态创建代理对象,参数分别是:目标类的类加载器 、目标对象实现的接口的类型、事件处理器
return Proxy.newProxyInstance(
target.getClass().getClassLoader(),
target.getClass().getInterfaces(),
new InvocationHandler() {
//重写事件处理器的调用方法
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("进入动态代理类,准备开始增强业务");
System.out.println("老王人哪去了?");
System.out.println("目标类名字:"+target.getClass().getName());
System.out.println("方法名:"+method.getName());
//执行目标对象方法,参数分别是 目标类对象 、当前方法注入参数
Object returnValue = method.invoke(target, args);
System.out.println("准备退出动态代理类,增强业务结束," + new Date());
return returnValue;
}
});
} }

测试类

package com.example.javabaisc.proxy.mdynamic;

import com.example.javabaisc.proxy.mdynamic.mproxy.DynamicProxy;
import com.example.javabaisc.proxy.mdynamic.service.UserService;
import com.example.javabaisc.proxy.mdynamic.service.serviceImpl.UserServiceImpl;
import org.junit.jupiter.api.Test; /**
* 动态代理 ,也叫jdk代理 、接口代理
*/
public class MDTest { @Test
public void t(){ //实例目标对象【类型是接口或实现类都可以,我喜欢用接口】
UserService userService = new UserServiceImpl();
//实例动态代理对象,注入目标对象,然后在内存创建该目标对象的代理对象,返回结果强转成目标对象一样的类型
//这样好处就是只要增强操作是一样的,则可以统一使用这个动态代理类
UserService userServiceProxy = (UserService)new DynamicProxy(userService).getProxyInstance();
//执行代理对象,方法则与目标类的相同
userServiceProxy.getname(); }
}

(2)启动测试方法

打印结果

4.CGLib代理

(1)目录结构

添加依赖包,目前最新版本3.3.0

<!-- Cglib代理 依赖包-->
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>3.3.0</version>
</dependency>

无实现接口的目标类

package com.example.javabaisc.proxy.mCglib.Util;

import org.springframework.stereotype.Service;

@Service
public class Mail {
public void send(){
System.out.println("发送邮件成功");
} }

CGLib代理类

package com.example.javabaisc.proxy.mCglib.mproxy;

import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy; import java.lang.reflect.Method;
import java.util.Date; public class CglibProxy implements MethodInterceptor {
//目标对象
//final可有可不有 ,但是最后idea提示最好加上去
private final Object target; //有参构造函数,用于注入业务层的实现类
public CglibProxy(Object target) {
this.target = target;
} //给目标对象创建一个代理对象
public Object getProxyInstance(){
//工具类
Enhancer en = new Enhancer();
//设置父类
en.setSuperclass(target.getClass());
//设置回调函数
en.setCallback(this);
//创建子类(代理对象)
return en.create(); } //重写拦截方法
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
System.out.println("进入Cglib代理类,准备开始增强业务");
System.out.println("准备发送邮件");
System.out.println("目标类名字:"+target.getClass().getName());
System.out.println("方法名:"+method.getName());
//执行目标对象方法,参数分别是 目标类对象 、当前方法注入参数
Object returnValue = method.invoke(target, objects);
System.out.println("准备退出Cglib代理类,增强业务结束," + new Date());
return returnValue;
}
}

启动类

package com.example.javabaisc.proxy.mCglib;

import com.example.javabaisc.proxy.mCglib.Util.Mail;
import com.example.javabaisc.proxy.mCglib.mproxy.CglibProxy; import org.junit.Test; import org.junit.runner.RunWith;
import org.junit.runner.Runner;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner; @SpringBootTest
@RunWith(SpringRunner.class)
public class CGTest { @Test
public void t() {
//实例目标类
Mail mail = new Mail();
//实例 Cglib 代理类 ,注入目标对象 ,创建代理对象,返回结果强转成目标对象一样的类型
Mail mailProxy = (Mail) new CglibProxy(mail).getProxyInstance();
//执行代理对象,方法则与目标类的相同
mailProxy.send(); } @Autowired
private Mail mail; /**
*使用javabean注解,不使用new实例化目标类
*/
@Test
public void t2() { //实例 Cglib 代理类 ,注入目标对象 ,创建代理对象,返回结果强转成目标对象一样的类型
Mail mailProxy = (Mail) new CglibProxy(mail).getProxyInstance();
//执行代理对象,方法则与目标类的相同
mailProxy.send(); }
}

(2)启动测试方法 t()

打印结果

(3)如果使用 javabean注解来实例化 目标类,

在单元测试记得启动spring注解

启动测试方法 t2()

打印结果

java 代理模式 总结的更多相关文章

  1. Java代理模式

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

  2. Java代理模式示例程序

    Java代理模式示例程序 当然不是我想出来的,是我看的一个网上教程里的. 模拟的是一个对电脑公司的代理 真实类的接口: public interface SaleComputer { public S ...

  3. 浅谈java代理模式

    讲解java代理模式 目录 讲解java代理模式 何谓代理模式 静态代理 动态代理 JDK动态代理 CGLIB动态代理 何谓代理模式 代理模式,即Proxy Pattern,23种java常用设计模式 ...

  4. Java代理模式/静态代理/动态代理

    代理模式:即Proxy Pattern,常用的设计模式之一.代理模式的主要作用是为其他对象提供一种代理以控制对这个对象的访问. 代理概念 :为某个对象提供一个代理,以控制对这个对象的访问. 代理类和委 ...

  5. JAVA代理模式与动态代理模式

    1.代理模式 所谓代理,就是一个人或者一个机构代表另一个人或者另一个机构采取行动.在一些情况下,一个客户不想或者不能够直接引用一个对象,而代理对象可以在客户端和目标对象之前起到中介的作用.代理模式给某 ...

  6. java 代理模式一: 静态代理

    代理模式: 代理模式的作用:为其他对象提供一种代理以控制对 特定对象  的访问. 某种情况下,一个客户不想或者直接引用另一个对象,而代理对象可以在客户端和目标对象之间起到中介的作用:通过代理对象引用. ...

  7. 18 java 代理模式 (转)

    静态代理 1.新建一个接口,这个接口所提供的方法是关于数据库操作的 public interface EmployeeDao { public void updateSalary(); } 2.建一个 ...

  8. JAVA 代理模式(Proxy)

    1.代理模式 代理模式的作用是:为其他对象提供一种代理以控制对这个对象的访问.在某些情况下,一个客户不想或者不能直接引用另一个对象,而代理对象可以在客户端和目标对象之间起到中介的作用. 代理模式一般涉 ...

  9. Java代理模式——静态代理模式

    一:代理模式 代理模式的作用是:为其他对象提供一种代理以控制这个对象的访问.在某些情况下,一个客户不想或者不能直接引用另一个对象,而代理对象可以在客户端和目标对象之间起到中介的作用. 二:代理模式设计 ...

随机推荐

  1. 【C/C++】例题5-4 反片语/算法竞赛入门经典/C++与STL入门/映射:map

    本题是映射:map的例题. map:键值对. [题目] 输入一些单词,找出所有满足如下条件的单词:该单词不能通过字母重排,得到输入文本中的另外一个单词. 在判断是否满足条件时,字母不分大小写,但在输出 ...

  2. Java中的循环结构进阶

    循环结构进阶 学习本章用到的单词 triangle:三角形 circle:圆形 diamond:钻石 password:密码 row:行.排列 二重循环结构 简单的说:二重循环就是一个循环体内又包含另 ...

  3. CF1144A Diverse Strings 题解

    Content 我们定义一个字符串是合法的,当且仅当这个字符串是"连续排列"(按照字母表顺序排序).现在给出 \(n\) 个字符串 \(s_1,s_2,s_3,...,s_n\), ...

  4. LuoguP7715 「EZEC-10」Shape 题解

    Content 有一个 \(n\times m\) 的网格,网格上的格子被涂成了白色或者黑色. 设两个点 \((x_1,y_1)\) 和 \((x_2,y_2)\),如果以下三个条件均满足: \(1\ ...

  5. winpcap 静默安装

    前几天做一个小工具用到winpcap,由于有些用户系统未必安装过这个而领导要求尽量减少用户点击,于是只好想办法静默安装了,csdn搜了,貌似没有好用的,求助stackoverflow,还好,在某篇解答 ...

  6. 大型网站高可用架构之CAP原理

    在讨论高可用数据服务架构之前,必须先讨论的一个话题是,为了保证数据的高可用,网站通常会牺牲另一个也很重要的指标:数据一致性. CAP原理认为,一个提供数据服务的存储系统无法同时满足数据一致性.数据可用 ...

  7. 快速上手FastJSON

    前言 作为一名后端开发而言肯定会接触数据,把数据提供给前端或者把数据存储起来,目前比较火热的传输格式是json,给前端传json是再常见不过啦,甚至是往db里面直接存入json. 在java层面来说j ...

  8. 【LeetCode】383. Ransom Note 解题报告(Java & Python)

    作者: 负雪明烛 id: fuxuemingzhu 个人博客: http://fuxuemingzhu.cn/ 目录 题目描述 题目大意 解题方法 Java解法 Python解法 日期 [LeetCo ...

  9. 【LeetCode】890. Find and Replace Pattern 解题报告(Python & C++)

    作者: 负雪明烛 id: fuxuemingzhu 个人博客: http://fuxuemingzhu.cn/ 目录 题目描述 题目大意 解题方法 字典+set 单字典 日期 题目地址:https:/ ...

  10. 【授课录屏】JavaScript高级(IIFE、js中的作用域、闭包、回调函数和递归等)、MySQL入门(单表查询和多表联查)、React(hooks、json-server等) 【可以收藏】

    一.JavaScript授课视频(适合有JS基础的) 1.IIFE 2.js中的作用域 3.闭包 4.表达式形式函数 5.回调函数和递归 资源地址:链接:https://pan.baidu.com/s ...