本章内容

  • 代理的概念及理解
  • 如何实现静态代理
  • 如何实现动态代理
  • 静态代理与动态代理有什么区别与优缺点
  • JDK动态代理如何实现
  • cglib动态代理如何实现
  • JDK动态代理与cglib动态代理的区别
  • SpringAOP中关键概念有哪些
  • 如何实现Spring中的AOP

一、代理的概述

1.1 什么是代理?代理的好处?

代理(Proxy)是一种设计模式, 提供了对目标对象另外的访问方式;即通过代理访问目标对象。

这样做的好处: 可以在目标对象实现的基础上,增强额外的功能操作。(扩展目标对象的功能)。

用通俗的语言来说,代理其实和现实世界中,明星的经纪人很类似。真正接受工作,要去表演的其实还是明星,但是明星一个人不可能直接和你对接,我们一般找到的都是明星的经纪人。右经济人帮明星筛选接收工作。也就是明星真正做事之前和做事之后的工作都由经纪人帮忙完成了。

  1. 经纪人接收工作(洽谈工作,签合约,安排时间,前期宣传等等...)
  2. 明星演戏唱歌
  3. 经纪人收尾工作(收尾款,后续宣传等等...)

经纪人就是代理,实际上台唱歌、表演的还是明星

代理是一种常用的设计模式,其目的就是为其他对象提供一个代理以控制对某个对象的访问。代理类负责为委托类预处理消息,过滤消息并转发消息,以及进行消息被委托类执行后的后续处理。

代理模式的UML图

为了保持行为的一致性,代理类和委托类通常会实现相同的接口,所以在访问者看来两者没有丝毫的区别。通过代理类这中间一层,能有效控制对委托类对象的直接访问,也可以很好地隐藏和保护委托类对象,同时也为实施不同控制策略预留了空间,从而在设计上获得了更大的灵活性。

更通俗的说,代理解决的问题当两个类需要通信时,引入第三方代理类,将两个类的关系解耦,让我们只了解代理类即可,而且代理的出现还可以让我们完成与另一个类之间的关系的统一管理,但是切记,代理类和委托类要实现相同的接口,因为代理真正调用的还是委托类的方法。

1.2 使用场合

如果需要委托类处理某一业务,那么我们就可以先在代理类中统一处理然后在调用具体实现类

1.3 分类

按照代理的创建时期,代理类可以分为两种:

静态:由程序员创建代理类或特定工具自动生成源代码再对其编译。在程序运行前代理类的.class文件就已经存在了。

动态:在程序运行时运用反射机制动态创建而成。

二、静态代理

比如已经有一个我们常写的IUserDao接口:

// 接口
public interface IUserDao {void save();
}

实现类UserDaoImpl:

public class UserDaoImpl implements IUserDao{@Overridepublic void save() {System.out.println("-----保存数据------");}
}

现在,要在save()方法保存数据前开启事务、保存数据之后关闭事务…当然啦,在业务少的时候,直接在save()方法中介绍事务就可以了

public void save() {System.out.println("开启事务");System.out.println("-----保存数据------");System.out.println("关闭事务");
}

但是,如果我有好多个业务方法都需要开启事务、关闭事务呢?

public void save() {System.out.println("开启事务");System.out.println("-----保存数据-----");System.out.println("关闭事务");
}
public void delete() {System.out.println("开启事务");System.out.println("-----删除数据-----");System.out.println("关闭事务");
}
public void update() {System.out.println("开启事务");System.out.println("-----更新数据-----");System.out.println("关闭事务");
}
public void login() {System.out.println("开启事务");System.out.println("-----登录-----");System.out.println("关闭事务");
}

这样,就有了很多很多的重复代码了…

于是呢,我们就请了一个代理,但是要注意两点:

  • 这个代理要和IUserDao有相同的方法
  • 代理只是对IUserDao进行增强,真正做事的还是UserDao 因此,代理就要实现IUserDao接口,这样的话,代理就跟IUserDao有相同的方法了。
public class UserDaoProxy implements IUserDao{//实现IUserDao,保持和IUserDao一样的方法//并且将IUserDao作为自身的一个属性,这里实际需要的是IUserDao的实现类UserDaoImplprivate IUserDao target;public UserDaoProxy(IUserDao target) {this.target = target;}@Overridepublic void save() {System.out.println("开始事务...");target.save();          // 执行目标对象的方法System.out.println("提交事务...");}
}

外界并不是直接去找UserDaoImpl,而是要通过代理才能找到UserDaoImpl

public static void main(String[] args) {// 目标对象IUserDao target = new UserDaoImpl();// 代理IUserDao proxy = new UserDaoProxy(target);proxy.save();  // 执行的是,代理的方法
}

这样在UserDaoImpl里面就不用在每个方法中都写入事务这些重复的代码了

三、JDK动态代理

3.1 分析利弊

静态代理的不足:

  • 如果接口改了,代理的也要跟着改...这样很不方便
  • 因为代理对象,需要与目标对象实现一样的接口。所以会有很多代理类,反而显得很不方便。

动态代理比静态代理好的地方:

  • 代理对象,不需要实现接口,这样就不会有太多的代理类了
  • 代理对象的生成,是利用JDKAPI, 动态地在内存中构建代理对象

Java提供了一个Proxy类,调用它的newInstance方法可以生成某个对象的代理对象,该方法需要三个参数:

@CallerSensitive
public static Object newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h)throws IllegalArgumentException

其中

  • loader表示生成代理对象使用哪个类装载器
  • interfaces表示生成哪个对象的代理对象,通过接口指定,指定要代理类的接口
  • h表示生成的代理对象的方法里干什么事,实现handler接口

在编写动态代理之前,要明确几个概念:

  • 代理对象拥有目标对象相同的方法,因为参数二指定了对象的接口
  • 用户调用代理对象的什么方法,都是在调用处理器的invoke方法。
  • 使用JDK动态代理必须要有接口

3.2 JDK动态代理实例

Person.java接口

public interface Person {void sing(String name);void dance(String name);
}

Andy Lau是一个明星,实现了人的接口: AndyLau.java对象:

public class AndyLau implements Person {@Overridepublic void sing(String name) {System.out.println("Andy Lau 唱" + name);}@Overridepublic void dance(String name) {System.out.println("Andy Lau 跳" + name);}
}

AndyLau的代理类AndyLauProxy.java:

public class AndyLauProxy {//代理只是一个经纪人,实际干活的还是andyLau,于是需要在代理类上维护andyLau这个变量AndyLau andyLau = new AndyLau();//返回代理对象public Person getProxy() {/*** 参数一:代理类的类加载器* 参数二:被代理对象的接口* 参数三:InvocationHandler实现类*/return (Person)Proxy.newProxyInstance(AndyLauProxy.class.getClassLoader(), andyLau.getClass().getInterfaces(), new InvocationHandler() {/*** proxy : 把代理对象自己传递进来* method:把代理对象当前调用的方法传递进来* args:把方法参数传递进来*/@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {//如果别人想要让AndyLau唱歌if (method.getName().equals("sing")) {System.out.println("please pay money $10000000");//实际上唱歌的还是AndyLaumethod.invoke(andyLau, args);}return null;}});}
}

测试

public static void main(String[] args) {//外界通过代理才能让Andy Lau唱歌AndyLauProxy andyLauProxy = new AndyLauProxy();Person proxy = andyLauProxy.getProxy();proxy.sing("爱你一万年");
}

四、cglib代理

4.1 概念

由于静态代理需要实现目标对象的相同接口,那么可能会导致代理类会非常非常多....不好维护,因此出现了动态代理

动态代理也有个约束:目标对象一定是要有接口的,没有接口就不能实现动态代理.....因此出现了cglib代理

cglib代理也叫子类代理,从内存中构建出一个子类来扩展目标对象的功能!

CGLIB是一个强大的高性能的代码生成包,它可以在运行期扩展Java类与实现Java接口。它广泛的被许多AOP的框架使用,例如Spring AOP,为他们提供方法的interception(拦截)

4.2 实现cglib代理

怎么写cglib代理:

  • 需要引入cglib.jar文件, 但是spring的核心包中已经包括了cglib功能,所以直接引入spring-core-xxxx.jar即可。
  • 引入功能包后,就可以在内存中动态构建子类
  • 代理的类不能为final,否则报错
  • 目标对象的方法如果为final/static, 那么就不会被拦截,即不会执行目标对象额外的业务方法。

比如有下面的代码:

UserDaoImpl.java类,注意这里并没有接口

public class UserDaoImpl {public void save(){System.out.println("正在保存");}
}

cglib实现代理ProxyFactory.java类:

import org.springframework.cglib.proxy.Enhancer;
import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;
public class ProxyFactory implements MethodInterceptor {// 维护目标对象private Object target;public ProxyFactory(Object target){this.target = target;}// 给目标对象创建代理对象public Object getProxyInstance(){//1. 工具类Enhancer en = new Enhancer();//2. 设置父类en.setSuperclass(target.getClass());//3. 设置回调函数en.setCallback(this);//4. 创建子类(代理对象)return en.create();}@Overridepublic Object intercept(Object obj, Method method, Object[] args,MethodProxy proxy) throws Throwable {System.out.println("==================开始事务==================");// 执行目标对象的方法Object returnValue = method.invoke(target, args);System.out.println("==================提交事务==================");return returnValue;}
}

测试类

public class Test {@org.junit.Testpublic void cglibProxy(){UserDaoImpl userDaoImpl = new UserDaoImpl();UserDaoImpl factory = (UserDaoImpl) new ProxyFactory(userDaoImpl).getProxyInstance();factory.save();}
}

五、Spring AOP

5.1 基本概念

什么是AOP?

  1. Aop: aspect object programming 面向切面编程
  2. 功能: 让关注点代码与业务代码分离!
  3. 面向切面编程就是指: 对很多功能都有的重复的代码抽取,再在运行的时候往业务方法上动态植入“切面类代码”。
  4. 关注点:重复代码就叫做关注点。

举例说明

// 保存一个用户
public void add(User user) { Session session = null; Transaction trans = null; try { session = HibernateSessionFactoryUtils.getSession();   // 【关注点代码】trans = session.beginTransaction();    // 【关注点代码】session.save(user);     // 核心业务代码trans.commit();     //【关注点代码】} catch (Exception e) {     e.printStackTrace(); if(trans != null){ trans.rollback();   //【关注点代码】} } finally{ HibernateSessionFactoryUtils.closeSession(session);   //【关注点代码】}
}

什么是切面?

关注点形成的类,就叫切面(类)

public class AOP {public void begin() {System.out.println("==========开始事务==========");}public void close() {System.out.println("==========关闭事务==========");}
}

切入点:

  • 执行目标对象方法,动态植入切面代码
  • 可以通过切入点表达式,指定拦截哪些类的哪些方法; 给指定的类在运行的时候植入切面类代码
  • 指定哪些类的哪些方法被拦截

5.2 Spring 注解方式实现AOP编程

我们之前手动的实现AOP编程是需要自己来编写代理工厂的,现在有了Spring,就不需要我们自己写代理工厂了。Spring内部会帮我们创建代理工厂。

也就是说,不用我们自己写代理对象了。

因此,我们只要关心切面类、切入点、编写切入表达式指定拦截什么方法就可以了!

第一步:导入相应的依赖包

<dependency><groupId>org.springframework</groupId><artifactId>spring-core</artifactId><version>3.2.18.RELEASE</version>
</dependency>
<dependency><groupId>org.springframework</groupId><artifactId>spring-aop</artifactId><version>3.2.18.RELEASE</version>
</dependency>
<dependency><groupId>org.springframework</groupId><artifactId>spring-context</artifactId><version>3.2.18.RELEASE</version>
</dependency>
<dependency><groupId>org.aspectj</groupId><artifactId>aspectjweaver</artifactId><version>1.7.4</version>
</dependency>

第二步:spring配置文件applicationContext.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xmlns:p="http://www.springframework.org/schema/p"xmlns:context="http://www.springframework.org/schema/context"xmlns:aop="http://www.springframework.org/schema/aop"xsi:schemaLocation="http://www.springframework.org/schema/beanshttp://www.springframework.org/schema/beans/spring-beans.xsdhttp://www.springframework.org/schema/contexthttp://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd"><!-- 定义包扫描器,指定到哪个包下面找到bean --><context:component-scan base-package="com.yingside"/><!-- 开启aop注解方式 --><aop:aspectj-autoproxy></aop:aspectj-autoproxy>
</beans>

第三步:定义IUserDao接口

public interface IUserDao {public void save();
}

第四步:定义UserDaoImpl实现类

import org.springframework.stereotype.Component;
@Component(value = "userDao")
public class UserDaoImpl implements IUserDao{public void save(){System.out.println("正在保存");}
}

上面的UserDaoImpl.java文件里面的save()方法很明显是我们要执行的方法,现在我们需要的是在save()方法上添加代理,有了Spring管理之后,就不需要我们再像之前人为的编写Proxy代码了。现在直接转变思维,需要关注的是切面类,其实也就是像之前一样需要加到save()方法前后的一些重复代码。只不过把这些提取出来,容易形成一个类,就叫做切面类

import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component;
@Component
@Aspect//指定为切面类
public class AOP {//里面的值为切入点表达式@Before("execution(* com.yingside..*.*(..))")public void begin() {System.out.println("=====开始事务=====");}@After("execution(* com.yingside..*.*(..))")public void close() {System.out.println("=====关闭事务=====");}
}

解释一下上面的切入点表达式: 格式:

  • execution(modifiers-pattern? ret-type-pattern declaring-type-pattern? name-pattern(param-pattern)throws-pattern?)
  • 括号中各个pattern分别表示:
  • * 修饰符匹配(modifier-pattern?)
  • * 返回值匹配(ret-type-pattern)可以为*表示任何返回值,全路径的类名等
  • * 类路径匹配(declaring-type-pattern?)
  • * 方法名匹配(name-pattern)可以指定方法名 或者 *代表所有, set* 代表以set开头的所有方法
  • * 参数匹配((param-pattern))可以指定具体的参数类型,多个参数间用“,”隔开,各个参数也可以用“*”来表示匹配任意类型的参数,如(String)表示匹配一个String参数的方法;(*,String) 表示匹配有两个参数的方法,第一个参数可以是任意类型,而第二个参数是String类型;可以用(..)表示零个或多个任意参数
  • * 异常类型匹配(throws-pattern?)
  • * 其中后面跟着“?”的是可选项

比如

1)execution(* *(..))   表示匹配所有方法

2)execution(public * com.yingside.service.UserService.*(..))   表示匹配com.yingside.server.UserService中所有的公有方法

3)execution(* com.yingside..*.*(..))  表示匹配com.yingside包及其子包下的所有方法

测试

@org.junit.Test
public void sprintAop(){ApplicationContext ac =new ClassPathXmlApplicationContext("applicationContext.xml");//这里得到的是代理对象....IUserDao iUser = (IUserDao) ac.getBean("userDao");System.out.println(iUser.getClass());iUser.save();
}

Spring系列之静态代理、动态代理、cglib代理与Spring AOP的处理相关推荐

  1. 代理模式(静态代理模式、动态代理模式、cgLib代理模式、拦截器)

    目录 一.什么是代理? 二.代理的好处 三.实现代理的步骤 四.静态代理 五.动态代理

  2. 设计模式之代理模式、动态代理模式、Cglib代理模式

    代理(Proxy)是一种设计模式,提供了对目标对象另外的访问方式;即通过代理对象访问目标对象.这样做的好处是:可以在目标对象实现的基础上,增强额外的功能操作,即扩展目标对象的功能. 这里使用到编程中的 ...

  3. Spring系列(四)、设计模式之代理模式

    4 代理模式 代理模式是java中常用的设计模式,代理类与委托类有同样的接口,代理类主要负责为委托类预处理消息.过滤信息.把消息转发给委托类.以及事后处理信息等.代理类与委托类之间通常会存在关联关系, ...

  4. Spring 系列,第 2 部分: 当 Hibernate 遇上 Spring

    为什么80%的码农都做不了架构师?>>>    在这个系列的 前一期中,我介绍了 Spring 框架的 7 个模块,包括 Spring AOP 和控制反转(IOC)容器.然后我用一个 ...

  5. Spring系列:代理(jdk动态代理,cglib代理)

    使用代理的目的: 1)为其他对象提供一种代理以控制对这个对象的访问. 2)方便系统的扩展和测试. 举例两个具体情况: (1)如果那个对象是一个是很大的图片,需要花费很长时间才能显示出来,那么当这个图片 ...

  6. 基于Spring AOP的JDK动态代理和CGLIB代理

    一.AOP的概念  在软件业,AOP为Aspect Oriented Programming的缩写,意为:面向切面编程,通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术.AOP是OOP的 ...

  7. 设计模式-代理模式(静态代理、动态代理、cglib代理)

    文章目录 代理模式 静态代理 动态代理 cglib代理 应用 代理模式 代理模式(Proxy Pattern)是一种结构性模式.代理模式为一个对象提供了一个替身,以控制对这个对象的访问.即通过代理对象 ...

  8. 代理模式——静态代理,动态代理(JDK代理和CGLib代理)

    概述 由于某些原因需要给某对象提供一个代理以控制对该对象的访问. 这时,访问对象不适合或者不能直接引用目标对象,代理对象作为访问对象和目标对象之间的中介. Java中的代理按照代理类生成时机不同又分为 ...

  9. Spring原理学习(七)JDK动态代理与CGLIB代理底层实现

    AOP 底层实现方式之一是代理,由代理结合通知和目标,提供增强功能. 除此以外,aspectj 提供了两种另外的 AOP 底层实现: 第一种是通过 ajc 编译器在编译 class 类文件时,就把通知 ...

最新文章

  1. cesium雷达图_20个简化开发任务的 JavaScript库
  2. 高等数学下-赵立军-北京大学出版社-题解-练习10.5
  3. 选定用户与用户组启动流程(学习笔记)
  4. php 筛选搜索,筛选——搜索
  5. 【C语言】二维数组指针,字符数组指针输出程序实例(注释详细)
  6. 用户画像 客户基本属性表
  7. FME 应用cad处理
  8. 【QT】FillRect填充的区域实现部分区域透明
  9. ionic4--下拉刷新(refresher)
  10. 线性拟合——离群点outliers的处理
  11. 用了几个月Macnbsp;OSnbsp;X,发现很多东西…
  12. 【CISSP备考笔记】第2章:资产安全
  13. pmp考试是什么?有没有含金量?值得考吗?(附2023 年考试时间、备考资料)
  14. 就让烟花来代替我对你的祝福叭 ~ 【生日快乐-简单烟花祝福特效】
  15. 计算机试图启动宏,win7打开word提示“您试图运行的函数包含宏或需要宏语言支持的内容”如何解决...
  16. linux查看GPU使用情况 linux如何查看GPU使用情况
  17. abi:用json解析和对比linux平台下elf文件
  18. Java实现上传(支持多个文件同时上传)和下载
  19. Shader各种效果
  20. 网络 -- TCP三次握手和四次挥手

热门文章

  1. [terry笔记]Python字符串
  2. 全球第一个完整的Angular 2.0系列视频教程,大漠穷秋
  3. 去除最新版迅雷下载器右侧浏览器
  4. 大童保险确认获得投资:德弘资本等出资15亿元,获得约33%股权
  5. wifi计费认证系统php,TP-LINK认证计费系统 - TP-LINK官方网站
  6. qt离线下载地址5.14.2
  7. 全渠道营销与多渠道营销:定义、比较、示例
  8. Centos 7 freeradius 搭建企业wifi认证服务
  9. 主机名 域名 网站名 URL
  10. Flink源码剖析:回撤流