代理设计模式
🍅 作者:程序员小王
🍅 程序员小王的博客:程序员小王的博客
🍅 扫描主页左侧二维码,加我微信 一起学习、一起进步
🍅 欢迎点赞 👍 收藏 ⭐留言 📝
🍅 如有编辑错误联系作者,如果有比较好的文章欢迎分享给我,我会取其精华去其糟粕
🍅java自学的学习路线:java自学的学习路线
一、结构型模式
结构型模式描述如何将类或对象按某种布局组成更大的结构。它分为类结构型模式和对象结构型模式,前者采用继承机制来组织接口和类,后者釆用组合或聚合来组合对象。
由于组合关系或聚合关系比继承关系耦合度低,满足“合成复用原则”,所以对象结构型模式比类结构型模式具有更大的灵活性。
结构型模式分为以下 7 种:
-
代理模式
-
适配器模式
-
装饰者模式
-
桥接模式
-
外观模式
-
组合模式
-
享元模式
二、现有开发中存在的问题
- 问题:在JavaEE分层开发开发中,那个层次对于我们来讲最重要?
DAO ---> Service --> Controller
JavaEE分层开发中,最为重要的是Service层
1、定义业务层接口
public interface EmpService {
/**
* 分页
* @return 当前页的员工
*/
public List<Emp> selectEmpByPage(int pageNumber);
/**
* 总的页数
* @return 数据库有多少条员工数据
*/
public int selectTotalPage();
/**
* 添加员工
* @param emp
*/
public void addEmp(Emp emp);
/**
* 根据id删除员工
* @param id
*/
public void dropEmp(Integer id);
/**
* 根据id修改员工信息
* @param emp
*/
public void update(Emp emp);
/**
* 批量删除员工
* @param ids
*/
public void plDeleteEmp(List<Integer> ids);
/**
* 更具id查询员工,用于数据回显
* @param id
* @return
*/
public Emp queryEmp(Integer id);
}
2、实现业务接口
public class EmpServiceImpl implements EmpService {
@Override
public List<Emp> selectEmpByPage(int pageNumber) {
EmpDao mapper = (EmpDao) MybatisUtil.getMapper(EmpDao.class);
//设置每页展示的页数
int rows = 3;
//设置第几页开始的起始条数
int begin = (pageNumber - 1) * rows;
List<Emp> emps = mapper.selectEmpByPage(begin, rows);
MybatisUtil.close();
return emps;
}
@Override
public int selectTotalPage() {
EmpDao mapper = (EmpDao) MybatisUtil.getMapper(EmpDao.class);
int i = mapper.selectTotalCount();
int rows = 3;
int page = 0;
if (i % rows == 0) {
page = i / rows;
} else {
page = i / rows + 1;
}
MybatisUtil.close();
return page;
}
@Override
public void addEmp(Emp emp) {
EmpDao mapper = (EmpDao) MybatisUtil.getMapper(EmpDao.class);
mapper.insertEmp(emp);
MybatisUtil.commit();
}
@Override
public void dropEmp(Integer id) {
EmpDao mapper = (EmpDao) MybatisUtil.getMapper(EmpDao.class);
mapper.deleteEmp(id);
MybatisUtil.commit();
}
@Override
public void update(Emp emp) {
EmpDao mapper = (EmpDao) MybatisUtil.getMapper(EmpDao.class);
mapper.update(emp);
MybatisUtil.commit();
}
@Override
public void plDeleteEmp(List<Integer> ids) {
EmpDao mapper = (EmpDao) MybatisUtil.getMapper(EmpDao.class);
mapper.plDeleteEmp(ids);
MybatisUtil.commit();
}
@Override
public Emp queryEmp(Integer id) {
EmpDao mapper = (EmpDao) MybatisUtil.getMapper(EmpDao.class);
Emp emp = mapper.queryEmp(id);
MybatisUtil.close();
return emp;
}
}
3、Service层中包含了哪些代码?
Service层中 = 核心功能(几十行 上百代码) + 额外功能(附加功能)
1. 核心功能
业务运算
DAO调用
2. 额外功能
1. 不属于业务
2. 可有可无
3. 代码量很小
事务、日志、性能...
从下图中可以看出,现有业务层中
控制事务代码出现了大量的冗余
,如何解决现有业务层出现的冗余问题?
4、额外功能书写在Service层中好不好?
Service层的调用者的角度(Controller):需要在Service层书写额外功能。
软件设计者:Service层不需要额外功能
5、现实生活中的解决方式
-
住酒店不一定需要亲自到酒店去,还可以通过微信支付下的同程艺龙来订酒店。
-
我们可以通过中介去找房子,不用直接跟房东沟通(现实生活中,我们更希望直接跟房东沟通)
-
春运买票买不到,我们可以找黄牛替我们抢票
-
想访问国外的网站,可以使用代理服务器进行访问。
三、代理设计模式
单词 proxy [ˈprɑːksi] 代理
1、概述
由于某些原因需要给某对象提供一个代理以控制对该对象的访问。这时,访问对象不适合或者不能直接引用目标对象,代理对象作为访问对象和目标对象之间的中介。
Java中的代理按照代理类生成时机不同 又分为静态代理和动态代理。
-
"静态代理"代理类在编译期就生成
-
"动态代理"代理类则是在Java运行时动态生成。动态代理又有JDK代理和CGLib代理两种。
2、结构
代理(Proxy)模式分为三种角色:
-
抽象主题(Subject)类: 通过接口或抽象类 声明真实主题和代理对象实现的业务方法。
-
真实主题(Real Subject)类: 实现了抽象主题中的具体业务,是代理对象所代表的真实对象,是最终要引用的对象。
-
代理(Proxy)类 : 提供了与真实主题相同的接口,其内部含有对真实主题的引用,它可以访问、控制或扩展真实主题的功能。
1、什么是代理? 中介
代理是java中的一种设计模式:代理设计模式
2、为什么需要代理?
有没有代理都可以完成当前的功能,但是存在代理之后,更加专注于核心的业务功能,额外功能可以交给代理去做,实现功能的解耦合
- 生活案例:小红正在做饭,但是男朋友小李给他发消息,他如果边做饭边发消息,影响做饭的效率,他就可以口述给她的闺蜜让她闺蜜和男朋友聊天,自己认真做饭,闺蜜就是代理,"如花"就是一个接口
3、代理的好处?
保证核心功能完成的前提下,同时兼顾额外功能
4、怎么开发代理对象?
1、代理对象一定依赖于核心业务对象
2、代理对象和核心业务对象一定要实现相同的接口
四、静态代理
1、什么是静态代理
-
静态代理:手工为某个类开发一个代理类,一个类对应一个代理类
-
通过代理类,为原始类(目标)增加额外的功能
-
好处:利于原始类(目标)的维护
2、名词解释
-
目标类 原始类 被代理的对象
指的是 业务类 (核心功能 --> 业务运算 DAO调用) -
目标方法,原始方法
目标类(原始类)中的方法 就是目标方法(原始方法) -
额外功能 (附加功能)
日志,事务,性能,数据清洗
3、代理开发的核心要素
代理类 = 目标类(原始类) + 额外功能 + 原始类(目标类)实现相同的接口
房东 ---> public interface UserService{
m1
m2
}
UserServiceImpl implements UserService{
m1 ---> 业务运算 DAO调用
m2
}
UserServiceProxy implements UserService
m1
m2
4、编码(静态代理的开发)
开发代理的原则: 代理类和目标类功能一致且实现相同的接口,同时代理类中依赖于目标类对象
(1)UserService接口
public interface UserService {
void save(String name);
void delete(String id);
void update();
String findAll(String name);
String findOne(String id);
}
(2)开发静态代理类
/**
* @author 王恒杰
* @version 1.0
* @date 2021/11/11 11:14
* @email 1078993387@qq.com
* @Address 天津
* @Description:开发原则:代理类和目标类实现相同接口,依赖于真正的目标类
*/
public class UserServiceStaticProxy implements UserService{
/**
* 真正的目标类 (target 原始业务逻辑对象)
*/
private UserService userService;
public void setUserService(UserService userService) {
this.userService = userService;
}
@Override
public void save(String name) {
try {
System.out.println("开启事务");
userService.save(name);
System.out.println("提交事务");
}catch (Exception e){
System.out.println("回滚事务");
e.printStackTrace();
}
}
@Override
public void delete(String id) {
try {
System.out.println("开启事务");
userService.delete(id);
System.out.println("提交事务");
}catch (Exception e){
System.out.println("回滚事务");
e.printStackTrace();
}
}
@Override
public void update() {
try {
System.out.println("开启事务");
userService.update();
System.out.println("提交事务");
}catch (Exception e){
System.out.println("回滚事务");
e.printStackTrace();
}
}
@Override
public String findAll(String name) {
String all=null;
try {
System.out.println("开启事务");
all = userService.findAll(name);
System.out.println("提交事务");
}catch (Exception e){
System.out.println("回滚事务");
e.printStackTrace();
}
return all;
}
@Override
public String findOne(String id) {
String one=null;
try {
System.out.println("开启事务");
one= userService.findOne(id);
System.out.println("提交事务");
}catch (Exception e){
System.out.println("回滚事务");
e.printStackTrace();
}
return one;
}
}
(3)修改目标实现类
public class UserServiceImpl implements UserService {
@Override
public void save(String name) {
System.out.println("处理存储业务逻辑,调用DAO~~~");
}
@Override
public void delete(String id) {
System.out.println("处理删除业务逻辑,调用DAO~~~");
}
@Override
public void update() {
System.out.println("处理更新业务逻辑,调用DAO~~~");
}
@Override
public String findAll(String name) {
System.out.println("处理查找所有业务逻辑,调用DAO~~~");
return name;
}
@Override
public String findOne(String id) {
System.out.println("处理查询其中一个业务逻辑,调用DAO~~~");
return id;
}
}
(4)配置静态代理类
<!--配置目标类-->
<bean id="userService" class="StaticProxy.UserServiceImpl"></bean>
<!--配置代理类-->
<bean id="userServiceStaticProxy" class="StaticProxy.UserServiceStaticProxy">
<!--setter入-->
<property name="userService" ref="userService"></property>
</bean>
(5)调用代理方法
@Test
public void StaticProxyText() {
ClassPathXmlApplicationContext context =
new ClassPathXmlApplicationContext("ApplicationContext.xml");
UserService userService =
(UserService) context.getBean("userServiceStaticProxy");
userService.delete("1");
}
}
a.开发静态代理类
5、静态代理存在的问题
存在问题:造成程序中存在过多的代理类,不利于后期额外功能的维护
- 静态类文件数量过多,不利于项目管理
UserServiceImpl——》 UserServiceProxy
OrderServiceImpl——》 OrderServiceProxy
- 额外功能维护性差
代理类中 额外功能修改复杂(麻烦)
五、动态代理
1、什么动态态代理
-
动态代理:在程序运行的过程中,JVM动态的为某个类生成代理对象
-
概念:通过代理类为原始类(目标类)增加额外功能
-
好处:利于原始类(目标类)的维护
-
常用的动态代理技术
-
JDK 代理 : 基于接口的动态代理技术
-
cglib 代理:基于父类的动态代理技术
-
DK动态代理是利用反射机制生成一个实现代理接口的匿名类 ,在调用具体方法前调用InvokeHandler (调用处理程序的意思)来处理。
-
而cglib动态代理是利用asm开源包 ,对代理对象类的class文件加载进来,通过修改其字节码生成子类来处理。
-
使用场景
1、如果目标对象实现了接口,默认情况下会采用JDK的动态代理实现AOP
2、如果目标对象实现了接口,可以强制使用CGLIB实现AOP
3、如果目标对象没有实现了接口,必须采用CGLIB库 ,spring会自动在JDK动态代理和CGLIB之间转换
- DK动态代理和CGLIB字节码生成的区别?
(1)JDK动态代理只能对实现了接口的类生成代理,而不能针对类
(2)CGLIB是针对类实现代理,主要是对指定的类生成一个子类,覆盖其中的方法
因为是继承,所以该类或方法最好不要声明成final
2、名词解释
-
目标对象:被代理的对象,称之为目标对象
-
目标方法: 核心业务对象中实现的核心方法
-
额外功能 (附加功能)
日志处理,事务,性能
3、搭建开发环境
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
<version>5.1.14.RELEASE</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjrt</artifactId>
<version>1.8.8</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.8.3</version>
</dependency>
4、JDK动态代理的开发
(1)目标类接口
public interface EmpService {
public void method();
}
(2)目标类
public class EmpServiceImpl implements EmpService{
@Override
public void method() {
System.out.println("Service方法被执行了");
}
}
(3)JDK动态代理代码并调用代理对象的方法测试
@Test
public void JDKDynamicProxyTest() {
//生成动态代理对象 1、目标对象和代理对象实现相同的接口 2、代理对象一定依赖目标对象
final EmpService empService = new EmpServiceImpl();
//获取动态代理前的对象
System.out.println(empService.getClass());
// 创建代理对象
InvocationHandler invocationHandler;
//动态代理 o就是静态代理对象
/**
* 参数1: 类加载器(class.getClassLoad()) 动态代理JVM依据字节码技术完成 加载.class文件
* Thread.currentThread():当前的线程
* Thread.currentThread().getContextClassLoader()等价于DynamicProxyTest.class.getClassLoader(),
* 参数2:目标对象实现的接口的类对象 数组 目标对象实现的所有的接口类型的数组
* 参数3:额外功能 目标对象中的目标方法需要执行的额外方法
*/
EmpService empService1 = (EmpService) Proxy.newProxyInstance(
Thread.currentThread().getContextClassLoader(),
new Class[]{EmpService.class},
new InvocationHandler() {
/**
* 参数1 proxy: 当前的动态代理对象
* 参数2 method: 当前代理对象执行的方法的对象
* 参数3 args: 当前代理对象执行的方法的参数
*
* @param proxy
* @param method
* @param args
* @return
* @throws Throwable
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("这是EmpServiceImpl目标对象的动态代理");
//调用目标方法 参数1:目标对象 参数2:目标方法执行时传递的参数
Object invoke = method.invoke(empService, args);
return null;
}
}
);
empService1.method();
//动态代理后的对象
System.out.println(empService1.getClass());
}
5、cglib 动态代理
需要导入两个jar(maven依赖)包,asm.jar,cglib.jar。版本自行选择·
- 所需相关依赖
<!--asmasm开源包-->
<dependency>
<groupId>asm</groupId>
<artifactId>asm</artifactId>
<version>3.3.1</version>
</dependency>
<!--cglib包-->
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>3.3.0</version>
</dependency>
(1)目标类
public void method() {
System.out.println("Target 运行了....");
}
(2)动态代理代码及调用代理对象的方法测试
/**
* CjLib动态代理
* 如果目标对象没有实现了接口,
* 必须采用CGLIB库,spring会自动在JDK动态代理和CGLIB之间转换
*/
@Test
public void CjLibTest() {
//创建目标对象
final Target target = new Target();
//创建增强器
Enhancer enhancer = new Enhancer();
// 设置父类
enhancer.setSuperclass(Target.class);
//设置回调
enhancer.setCallback(new MethodInterceptor() {
/**
* 参数1 proxy: 当前的动态代理对象
* 参数2 method: 当前代理对象执行的方法的对象
* 参数3 args: 当前代理对象执行的方法的参数
*/
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
System.out.println("这是Target目标对象的动态代理");
//调用目标方法 参数1:目标对象 参数2:目标方法执行时传递的参数
Object invoke = method.invoke(target, objects);
return invoke;
}
});
//创建代理对象
Target t = (Target) enhancer.create();
t.method();
}
六、三种代理的对比
1、jdk代理和CGLIB代理的区别
使用CGLib实现动态代理,CGLib底层采用ASM字节码生成框架,使用字节码技术生成代理类,在JDK1.6之前比使用Java反射效率要高。唯一需要注意的是,CGLib不能对声明为final的类或者方法进行代理,因为CGLib原理是动态生成被代理类的子类 。
在JDK1.6、JDK1.7、JDK1.8逐步对JDK动态代理优化之后,在调用次数较少的情况下,JDK代理效率高于CGLib代理效率 ,只有当进行大量调用的时候,JDK1.6和JDK1.7比CGLib代理效率低一点,但是到JDK1.8的时候,JDK代理效率高于CGLib代理 。所以如果有接口使用JDK动态代理,如果没有接口使用CGLIB代理。
2、动态代理和静态代理的区别?
动态代理与静态代理相比较,最大的好处是接口中声明的所有方法都被转移到调用处理器一个集中的方法中处理(InvocationHandler.invoke)。这样,在接口方法数量比较多的时候,我们可以进行灵活处理,而不需要像静态代理那样每一个方法进行中转。
如果接口增加一个方法,静态代理模式除了所有实现类需要实现这个方法外,所有代理类也需要实现此方法。增加了代码维护的复杂度。而动态代理不会出现该问题
七、代理模式的优缺点
1、代理模式的优点:
-
代理模式在客户端与目标对象之间起到一个中介作用和保护目标对象的作用;
-
代理对象可以扩展目标对象的功能;
-
代理模式能将客户端与目标对象分离,在一定程度上降低了系统的耦合度;
2、代理模式的缺点:
- 增加了系统的复杂度;
八、代理模式使用场景
1、远程(Remote)代理
本地服务通过网络请求远程服务。为了实现本地到远程的通信,我们需要实现网络通信,处理其中可能的异常。为良好的代码设计和可维护性,我们将网络通信部分隐藏起来,只暴露给本地服务一个接口,通过该接口即可访问远程服务提供的功能,而不必过多关心通信部分的细节。
2、防火墙(Firewall)代理
当你将浏览器配置成使用代理功能时,防火墙就将你的浏览器的请求转给互联网;当互联网返回响应时,代理服务器再把它转给你的浏览器。
3、保护(Protect or Access)代理
控制对一个对象的访问,如果需要,可以给不同的用户提供不同级别的使用权限。