• Spring修炼之旅(4)静态/动态代理模式与AOP


    一、代理模式概述

    代理模式

    为什么要学习代理模式,因为AOP的底层机制就是动态代理!

    代理模式:

    • 静态代理

    • 动态代理

    学习aop之前 , 我们要先了解一下代理模式!

    1.1静态代理

    静态代理角色分析

    • 抽象角色 : 一般使用接口或者抽象类来实现

    • 真实角色 : 被代理的角色

    • 代理角色 : 代理真实角色 ; 代理真实角色后 , 一般会做一些附属的操作 .

    • 客户  :  使用代理角色来进行一些操作 .

    代码实现

    Rent . java 即抽象角色

    1. //抽象角色:租房
    2. public interface Rent {
    3. public void rent();
    4. }

     Host . java 即真实角色

    1. //真实角色: 房东,房东要出租房子
    2. public class Host implements Rent{
    3. public void rent() {
    4. System.out.println("房屋出租");
    5. }
    6. }

    Proxy . java 即代理角色

    1. //代理角色:中介
    2. public class Proxy implements Rent {
    3. private Host host;
    4. public Proxy() { }
    5. public Proxy(Host host) {
    6. this.host = host;
    7. }
    8. //租房
    9. public void rent(){
    10. seeHouse();
    11. host.rent();
    12. fare();
    13. }
    14. //看房
    15. public void seeHouse(){
    16. System.out.println("带房客看房");
    17. }
    18. //收中介费
    19. public void fare(){
    20. System.out.println("收中介费");
    21. }
    22. }

    Client . java 即客户

    1. //客户类,一般客户都会去找代理!
    2. public class Client {
    3. public static void main(String[] args) {
    4. //房东要租房
    5. Host host = new Host();
    6. //中介帮助房东
    7. Proxy proxy = new Proxy(host);
    8. //你去找中介!
    9. proxy.rent();
    10. }
    11. }

    分析:在这个过程中,你直接接触的就是中介,就如同现实生活中的样子,你看不到房东,但是你依旧租到了房东的房子通过代理,这就是所谓的代理模式,程序源自于生活,所以学编程的人,一般能够更加抽象的看待生活中发生的事情。

    静态代理的好处:

    • 可以使得我们的真实角色更加纯粹 . 不再去关注一些公共的事情 .

    • 公共的业务由代理来完成 . 实现了业务的分工 ,

    • 公共业务发生扩展时变得更加集中和方便 .

    缺点 :

    • 类多了 , 多了代理类 , 工作量变大了 . 开发效率降低 .

    我们想要静态代理的好处,又不想要静态代理的缺点,所以 , 就有了动态代理 !

    静态代理再理解

    1.2动态代理

    • 动态代理的角色和静态代理的一样 .

    • 动态代理的代理类是动态生成的 . 静态代理的代理类是我们提前写好的

    • 动态代理分为两类 : 一类是基于接口动态代理 , 一类是基于类的动态代理

      • 基于接口的动态代理----JDK动态代理

      • 基于类的动态代理--cglib

      • 现在用的比较多的是 javasist 来生成动态代理 . 百度一下javasist

      • 我们这里使用JDK的原生代码来实现,其余的道理都是一样的

    JDK的动态代理需要了解两个类

    核心 : InvocationHandler     和     Proxy   , 打开JDK帮助文档看看

    【InvocationHandler:调用处理程序】

    Object invoke(Object proxy, 方法 method, Object[] args);
    //参数
    //proxy - 调用该方法的代理实例
    //method -所述方法对应于调用代理实例上的接口方法的实例。方法对象的声明类将是该方法声明的接口,它可以是代理类继承该方法的代理接口的超级接口。
    //args -包含的方法调用传递代理实例的参数值的对象的阵列,或null如果接口方法没有参数。原始类型的参数包含在适当的原始包装器类的实例中,例如java.lang.Integer或java.lang.Boolean 。
    

    【Proxy  : 代理】

     

    1. //生成代理类
    2. public Object getProxy(){
    3. return Proxy.newProxyInstance(this.getClass().getClassLoader(),
    4. rent.getClass().getInterfaces(),this);
    5. }

    代码实现 

    Rent . java 即抽象角色

    1. package com.yanyu.demo;
    2. //抽象角色:租房
    3. public interface Rent {
    4. public void rent();
    5. }

    Host . java 即真实角色

    1. package com.yanyu.demo;
    2. //真实角色: 房东,房东要出租房子
    3. public class Host implements Rent{
    4. public void rent() {
    5. System.out.println("房屋出租");
    6. }
    7. }

    ProxyInvocationHandler. java 即代理角色

    1. package com.yanyu.demo;
    2. import java.lang.reflect.InvocationHandler;
    3. import java.lang.reflect.Method;
    4. import java.lang.reflect.Proxy;
    5. public class ProxyInvocationHandler implements InvocationHandler {
    6. private Rent rent;
    7. public void setRent(Rent rent) {
    8. this.rent = rent;
    9. }
    10. //生成代理类,重点是第二个参数,获取要代理的抽象角色!之前都是一个角色,现在可以代理一类角色
    11. public Object getProxy(){
    12. return Proxy.newProxyInstance(this.getClass().getClassLoader(),
    13. rent.getClass().getInterfaces(),this);
    14. }
    15. // proxy : 代理类 method : 代理类的调用处理程序的方法对象.
    16. // 处理代理实例上的方法调用并返回结果
    17. @Override
    18. public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    19. seeHouse();
    20. //核心:本质利用反射实现!
    21. Object result = method.invoke(rent, args);
    22. fare();
    23. return result;
    24. }
    25. //看房
    26. public void seeHouse(){
    27. System.out.println("带房客看房");
    28. }
    29. //收中介费
    30. public void fare(){
    31. System.out.println("收中介费");
    32. }
    33. }

     Client . java

    1. package com.yanyu.demo;
    2. //客户类,一般客户都会去找代理!
    3. //租客
    4. public class Client {
    5. public static void main(String[] args) {
    6. //真实角色
    7. Host host = new Host();
    8. //代理实例的调用处理程序
    9. ProxyInvocationHandler pih = new ProxyInvocationHandler();
    10. pih.setRent(host); //将真实角色放置进去!
    11. Rent proxy = (Rent)pih.getProxy(); //动态生成对应的代理类!
    12. proxy.rent();
    13. }
    14. }

    核心:一个动态代理 , 一般代理某一类业务 , 一个动态代理可以代理多个类,代理的是接口!、 

    动态代理的好处

    静态代理有的它都有,静态代理没有的,它也有!

    • 可以使得我们的真实角色更加纯粹 . 不再去关注一些公共的事情 .

    • 公共的业务由代理来完成 . 实现了业务的分工 ,

    • 公共业务发生扩展时变得更加集中和方便 .

    • 一个动态代理 , 一般代理某一类业务

    • 一个动态代理可以代理多个类,代理的是接口!

    二、AOP编程

    2.1什么是AOP

    AOP(Aspect Oriented Programming)意为:面向切面编程,通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术。AOP是OOP的延续,是软件开发中的一个热点,也是Spring框架中的一个重要内容,是函数式编程的一种衍生范型。利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。

    2.2Aop在Spring中的作用

    提供声明式事务;允许用户自定义切面

    以下名词需要了解下:

    • 横切关注点:跨越应用程序多个模块的方法或功能。即是,与我们业务逻辑无关的,但是我们需要关注的部分,就是横切关注点。如日志 , 安全 , 缓存 , 事务等等 ....

    • 切面(ASPECT):横切关注点 被模块化 的特殊对象。即,它是一个类。

    • 通知(Advice):切面必须要完成的工作。即,它是类中的一个方法。

    • 目标(Target):被通知对象。

    • 代理(Proxy):向目标对象应用通知之后创建的对象。

    • 切入点(PointCut):切面通知 执行的 “地点”的定义。

    • 连接点(JointPoint):与切入点匹配的执行点。

    SpringAOP中,通过Advice定义横切逻辑,Spring中支持5种类型的Advice:

    即 Aop 在 不改变原有代码的情况下 , 去增加新的功能  

    2.3使用Spring实现Aop

    1. <dependency>
    2. <groupId>org.aspectjgroupId>
    3. <artifactId>aspectjweaverartifactId>
    4. <version>1.9.4version>
    5. dependency>

    通过 Spring API 实现

    首先编写我们的业务接口和实现类

    1. package com.yan.service;
    2. public interface UserService {
    3. public void add();
    4. public void delete();
    5. public void update();
    6. public void search();
    7. }
    1. package com.yan.service;
    2. public class UserServiceImpl implements UserService{
    3. @Override
    4. public void add() {
    5. System.out.println("增加用户");
    6. }
    7. @Override
    8. public void delete() {
    9. System.out.println("删除用户");
    10. }
    11. @Override
    12. public void update() {
    13. System.out.println("更新用户");
    14. }
    15. @Override
    16. public void search() {
    17. System.out.println("查询用户");
    18. }
    19. }

    然后去写我们的增强类 , 我们编写两个 , 一个前置增强 一个后置增强

    1. package com.yan.log;
    2. import org.springframework.aop.MethodBeforeAdvice;
    3. import java.lang.reflect.Method;
    4. public class Log implements MethodBeforeAdvice {
    5. //method : 要执行的目标对象的方法
    6. //objects : 被调用的方法的参数
    7. //Object : 目标对象
    8. @Override
    9. public void before(Method method, Object[] objects, Object o) throws Throwable {
    10. System.out.println( o.getClass().getName() + "的" + method.getName() + "方法被执行了");
    11. }
    12. }

    最后去spring的文件中注册 , 并实现aop切入实现 , 注意导入约束 .

    1. "1.0" encoding="UTF-8"?>
    2. <beans xmlns="http://www.springframework.org/schema/beans"
    3. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    4. xmlns:aop="http://www.springframework.org/schema/aop"
    5. xsi:schemaLocation="http://www.springframework.org/schema/beans
    6. http://www.springframework.org/schema/beans/spring-beans.xsd
    7. http://www.springframework.org/schema/aop
    8. http://www.springframework.org/schema/aop/spring-aop.xsd">
    9. <bean id="userService" class="com.yan.service.UserServiceImpl"/>
    10. <bean id="log" class="com.yan.log.Log"/>
    11. <bean id="afterLog" class="com.yan.log.AfterLog"/>
    12. <aop:config>
    13. <aop:pointcut id="pointcut" expression="execution(* com.yan.service.UserServiceImpl.*(..))"/>
    14. <aop:advisor advice-ref="log" pointcut-ref="pointcut"/>
    15. <aop:advisor advice-ref="afterLog" pointcut-ref="pointcut"/>
    16. aop:config>
    17. beans>

     execution 要执行的位置! (*(修饰词) *(返回值) *(类名) *(方法名) *(参数))

    测试

    1. import com.yan.service.UserService;
    2. import org.junit.Test;
    3. import org.springframework.context.ApplicationContext;
    4. import org.springframework.context.support.ClassPathXmlApplicationContext;
    5. public class MyTest {
    6. @Test
    7. public void test(){
    8. ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
    9. UserService userService = (UserService) context.getBean("userService");
    10. userService.search();
    11. }
    12. }

    Spring的Aop就是将公共的业务 (日志 , 安全等) 和领域业务结合起来 , 当执行领域业务时 , 将会把公共业务加进来 . 实现公共业务的重复利用 . 领域业务更纯粹 , 程序猿专注领域业务 , 其本质还是动态代理

    自定义类来实现Aop

    目标业务类不变依旧是userServiceImpl

    第一步 : 写我们自己的一个切入类

    1. package com.yan.diy;
    2. public class DiyPointcut {
    3. public void before(){
    4. System.out.println("---------方法执行前---------");
    5. }
    6. public void after(){
    7. System.out.println("---------方法执行后---------");
    8. }
    9. }

    .去spring中配置

    1. "1.0" encoding="UTF-8"?>
    2. "http://www.springframework.org/schema/beans"
    3. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    4. xmlns:aop="http://www.springframework.org/schema/aop"
    5. xsi:schemaLocation="http://www.springframework.org/schema/beans
    6. http://www.springframework.org/schema/beans/spring-beans.xsd
    7. http://www.springframework.org/schema/aop
    8. http://www.springframework.org/schema/aop/spring-aop.xsd">
    9. "userService" class="com.yan.service.UserServiceImpl"/>
    10. "log" class="com.yan.log.Log"/>
    11. "afterLog" class="com.yan.log.AfterLog"/>
    12. "diy" class="com.yan.diy.DiyPointcut"/>
    13. "diy">
    14. "diyPonitcut" expression="execution(* com.yan.service.UserServiceImpl.*(..))"/>
    15. "diyPonitcut" method="before"/>
    16. "diyPonitcut" method="after"/>

    测试:

    1. import com.yan.service.UserService;
    2. import org.junit.Test;
    3. import org.springframework.context.ApplicationContext;
    4. import org.springframework.context.support.ClassPathXmlApplicationContext;
    5. public class MyTest {
    6. @Test
    7. public void test(){
    8. ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
    9. UserService userService = (UserService) context.getBean("userService");
    10. userService.search();
    11. }
    12. }

    使用注解实现

    第一步:编写一个注解实现的增强类

    1. package com.yan.diy;
    2. package com.kuang.config;
    3. import org.aspectj.lang.ProceedingJoinPoint;
    4. import org.aspectj.lang.annotation.After;
    5. import org.aspectj.lang.annotation.Around;
    6. import org.aspectj.lang.annotation.Aspect;
    7. import org.aspectj.lang.annotation.Before;
    8. @Aspect
    9. public class AnnotationPointcut {
    10. @Before("execution(* com.yan.service.UserServiceImpl.*(..))")
    11. public void before(){
    12. System.out.println("---------方法执行前---------");
    13. }
    14. @After("execution(* com.yan.service.UserServiceImpl.*(..))")
    15. public void after(){
    16. System.out.println("---------方法执行后---------");
    17. }
    18. @Around("execution(* com.yan.service.UserServiceImpl.*(..))")
    19. public void around(ProceedingJoinPoint jp) throws Throwable {
    20. System.out.println("环绕前");
    21. System.out.println("签名:"+jp.getSignature());
    22. //执行目标方法proceed
    23. Object proceed = jp.proceed();
    24. System.out.println("环绕后");
    25. System.out.println(proceed);
    26. }
    27. }

    第二步:在Spring配置文件中,注册bean,并增加支持注解的配置

    1. "1.0" encoding="UTF-8"?>
    2. <beans xmlns="http://www.springframework.org/schema/beans"
    3. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    4. xmlns:aop="http://www.springframework.org/schema/aop"
    5. xsi:schemaLocation="http://www.springframework.org/schema/beans
    6. http://www.springframework.org/schema/beans/spring-beans.xsd
    7. http://www.springframework.org/schema/aop
    8. http://www.springframework.org/schema/aop/spring-aop.xsd">
    9. <bean id="userService" class="com.yan.service.UserServiceImpl"/>
    10. <bean id="log" class="com.yan.log.Log"/>
    11. <bean id="afterLog" class="com.yan.log.AfterLog"/>
    12. <bean id="annotationPointcut" class="com.yan.diy.AnnotationPointcut"/>
    13. <aop:aspectj-autoproxy/>
    14. beans>

    通过aop命名空间的声明自动为spring容器中那些配置@aspectJ切面的bean创建代理,织入切面。当然,spring 在内部依旧采用AnnotationAwareAspectJAutoProxyCreator进行自动代理的创建工作,但具体实现的细节已经被隐藏起来了

    有一个proxy-target-class属性,默认为false,表示使用jdk动态代理织入增强,当配为时,表示使用CGLib动态代理技术织入增强。不过即使proxy-target-class设置为false,如果目标类没有声明接口,则spring将自动使用CGLib动态代理。

  • 相关阅读:
    RFID技术:钢条加工现场的智能化管理利器
    线程的控制与同步
    聊聊ElasticeSearch并发写的乐观锁机制
    快速恢复notes.ini文件
    2.6W字系统总结,带你实现 Linux 自由!
    【Vue3源码】2. 响应式原理 上 - reactive源码实现
    【LeetCode:1402. 做菜顺序 | 动态规划 + 贪心】
    shell_51.Linux获取用户输入_无显示读取,从文件中读取
    如何解决 Redis 的并发竞争 key 问题
    深度可分离卷积神经网络与卷积神经网络
  • 原文地址:https://blog.csdn.net/qq_62377885/article/details/133438351