小宝爱拍车 發表於 2026-2-9 10:07:00

spring6-代理模式和AOP

<h1 id="jdbctemplate">jdbcTemplate</h1>
<p>jdbcTemplate是spring提供的一个jdbc模板类,是对jdbc的封装。</p>
<p>当然你也可以使用其他框架融入MyBatis、Hibernate。</p>
<h1 id="gof之代理模式">GoF之代理模式</h1>
<h2 id="代理模式的作用">代理模式的作用</h2>
<ol>
<li>当一个对象需要受到保护的时候,可以使用代理对象去完成某个行为。</li>
<li>需要给某个对象进行功能增强的时候,可以找一个代理进行增强。</li>
<li>A对象和B对象无法直接交互时,也可以使用代理模式来完成。</li>
</ol>
<p>代理模式中的三个角色:</p>
<ol>
<li>目标对象</li>
<li>代理对象</li>
<li>目标对象和代理对象的公共接口</li>
</ol>
<p>如果使用代理模式的话,客户端程序是无法察觉的,客户端在使用代理对象的时候就像在使用目标对象。</p>
<p>代理模式分为静态代理和动态代理。</p>
<h2 id="静态代理">静态代理</h2>
<p>目标对象类:</p>
<pre><code class="language-java">// 目标对象
public class OrderServiceImpl implements OrderService{
    @Override
    public void generateOrder() {
      System.out.println("生成订单");
    }

    @Override
    public void modifyOrder() {
      System.out.println("修改订单");
    }

    @Override
    public void detailOrder() {
      System.out.println("查看订单详情");

    }
}
</code></pre>
<p>代理对象类:</p>
<pre><code class="language-java">// 代理对象
public class OrderServiceProxy implements OrderService{
    // 代理对象中含有目标对象的引用
    // 这里使用OrderService类型,因为他耦合度低
    private OrderService orderService;

    // 构造方法传入目标对象
    public OrderServiceProxy(OrderService orderService) {
      this.orderService = orderService;
    }

    @Override
    public void generateOrder() {
      // 功能增强:统计方法执行时间
      long begin = System.currentTimeMillis();
      orderService.generateOrder();
      long end = System.currentTimeMillis();
      System.out.println("生成订单耗时:" + (end - begin) + "ms");
    }

    @Override
    public void modifyOrder() {
      long begin = System.currentTimeMillis();
      orderService.modifyOrder();
      long end = System.currentTimeMillis();
      System.out.println("修改订单耗时:" + (end - begin) + "ms");
    }

    @Override
    public void detailOrder() {
      long begin = System.currentTimeMillis();
      orderService.detailOrder();
      long end = System.currentTimeMillis();
      System.out.println("查看订单详情耗时:" + (end - begin) + "ms");
    }
}
</code></pre>
<p>公共接口:</p>
<pre><code class="language-java">// 订单服务接口
// 目标对象和代理对象的公共接口
public interface OrderService {

    // 生成订单
    void generateOrder();

    // 修改订单
    void modifyOrder();

    // 查看订单详情
    void detailOrder();
}
</code></pre>
<p>测试:</p>
<pre><code class="language-java">// 实现目标对象方法执行时间的统计
public static void main(String[] args) {
    // 创建目标对象
    OrderService orderService = new OrderServiceImpl();
    // 创建代理对象,同时将目标对象传入代理对象中
    OrderServiceProxy orderServiceProxy = new OrderServiceProxy(orderService);
    // 通过代理对象调用目标对象的方法
    orderServiceProxy.generateOrder();
    orderServiceProxy.modifyOrder();
    orderServiceProxy.detailOrder();
}
</code></pre>
<p>静态代理优点:1.解决了ocp问题2.采用代理模式的has a。降低了耦合度。</p>
<p>静态代理的缺点:假设系统中有上千个接口,每个接口都需要写代理类,这样类的数量会急剧膨胀,不好维护。</p>
<p>那怎么解决类爆炸的问题呢?</p>
<p>采用动态代理。动态代理还是代理模式,只不过是在内存中为我们动态的生成一个class字节码,这个字节码就是代理类。</p>
<h2 id="动态代理">动态代理</h2>
<p>在程序运行阶段,在内存中动态生成代理类,成为动态代理。目的是减少代理类的数量。</p>
<p>常见的动态代理技术有:JDK动态代理(只能代理接口)、CGLIB动态代理、Javassist动态代理。</p>
<h3 id="jdk动态代理">JDK动态代理</h3>
<p>公共接口 和目标对象类引用 上面的代码。Jdk动态代理不需要写代理类,jdk会在内存中自动生成代理类,因此直接在客户端代码里直接调用:</p>
<p>客户端代码:</p>
<pre><code class="language-java">public class Client {
    public void main() {
      // 1. 创建目标对象
      OrderService orderService = new OrderServiceImpl();
      // 2. 创建InvocationHandler对象

      // 3. 创建代理对象
      //    1. Proxy.newProxyInstance 的作用是创建代理对象。其实做了两件事:
      //   1). 在内存中动态的构建一个类,
      //   2). 这个类要实现接口, 这个接口就是目标对象的接口,并且new了一个对象
      //2. 这个方法的三个参数:
      //    1). ClassLoader: 类加载器, 用于加载内存中的代理对象类. 和目标对象使用相同的类加载器
      //   2). Class[]: 字节码数组, 代理对象和目标对象实现相同的接口. 用于让代理对象和目标对象具有相同的方法
      //3). InvocationHandler: 调用处理器对象,他是一个接口, 这个调用处理器用于编写增强代码
      OrderService o = (OrderService)Proxy.newProxyInstance(OrderService.class.getClassLoader(),                                                                        orderService.getClass().getInterfaces(),
                            new TimerInvocationHandler(orderService));
      // 4. 通过代理对象调用方法
      // 调用代理对象的代理方法时,如果代理方法的作用是功能增强,那目标对象的目标方法必须执行。
      o.generateOrder();
    }
}
</code></pre>
<p>调用处理器类:</p>
<pre><code class="language-java">/**
* 负责计时的一个调用处理器类
* 在这个调用处理器中编写增强代码
* 这个调用处理器只需要一个就好
*/
public class TimerInvocationHandler implements InvocationHandler {

    // 目标对象,就是要被增强的对象
    private Object target;
        // 构造方法传入目标对象
    public TimerInvocationHandler(Object target) {
            this.target = target;
    }

    // 这个方法必须是invoke()方法,因为jdk在底层会调用这个方法
    // 这个方法什么时候调用?
    //    什么时候通过代理对象调用方法的时候就会调用这个invoke()方法
    // 这个方法的参数:
    //    proxy: 代理对象,就是通过Proxy.newProxyInstance()方法创建的代理对象
    //    method: 目标对象的目标方法,这就是要执行的目标方法
    //    args: 目标方法上的参数
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
      // 功能增强:
      Long begin = System.currentTimeMillis();
      Object invoke = method.invoke(target, args);
      Long end = System.currentTimeMillis();
      System.out.println("方法执行耗时:" + (end - begin) + "ms");
      return invoke;
    }
}
</code></pre>
<h3 id="cglib动态代理">CGLIB动态代理</h3>
<p>既可以代理接口,也可以代理类。底层采用继承的方式实现,因此<strong>被代理的目标类不能被final修饰</strong>。</p>
<p>目标类:</p>
<pre><code class="language-java">// 目标类
public class UserService {
    public boolean login(String username, String password) {
      System.out.println("用户登录,用户名:" + username + ",密码:" + password);
      return "admin".equals(username) &amp;&amp; "123456".equals(password);
    }

    public void logout(String username) {
      System.out.println("用户退出登录,用户名:" + username);
    }
}
</code></pre>
<p>客户端代码:</p>
<pre><code class="language-java">public static void main(String[] args) {
    // 创建字节码增强对象
    // 这个对象是cglib的核心对象,依靠它来生成代理类
    Enhancer enhancer = new Enhancer();
    // 设置父类,也就是目标类
    enhancer.setSuperclass(UserService.class);
    // 设置回调函数(等同于jdk动态代理的中的调用处理器)
    // 在cglib中是实现方法拦截器MethodInterceptor接口
    enhancer.setCallback(new TimerMethodInterceptor());

    UserService userServiceProxy = (UserService)enhancer.create();
    boolean login = userServiceProxy.login("admin", "123456");
    System.out.println(login?"登录成功":"登录失败");
    userServiceProxy.logout("admin");
}
</code></pre>
<p>增强代码写在方法拦截器里面</p>
<pre><code class="language-java">public class TimerMethodInterceptor implements MethodInterceptor {

    @Override
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
      Long startTime = System.currentTimeMillis();
      Object o1 = methodProxy.invokeSuper(o, objects);
      Long endTime = System.currentTimeMillis();
      System.out.println("方法 " + method.getName() + " 执行耗时:" + (endTime - startTime) + "ms");
      return o1;
    }
}
</code></pre>
<p>注意:在jdk17环境下,cglib动态代理功能启动时会报错,需要添加启动时参数设置:</p>
<p>vm 参数:--add-opens java.base/java.lang=ALL-UNNAMED</p>
<p>program 参数:--add-opens java.base/sun.net.util=ALL-UNNAMED</p>
<p><img src="https://img2024.cnblogs.com/blog/3388489/202602/3388489-20260209100631406-1917032911.png"></p>
<h1 id="面向切面编程aop">面向切面编程AOP</h1>
<p>在一个系统中一般会有许多系统服务,如:日志,事务管理、安全等。这些服务成为交叉业务。</p>
<p>这些交叉业务是通用的。</p>
<p>如果在每一个业务处理过程中,都掺杂这些交叉业务代码会出现2个问题:</p>
<ol>
<li>交叉业务代码在多个业务中反复出现。代码没有得到复用,修改这些代码会非常困难。</li>
<li>开发人员无法专业核心业务代码,在编写核心业务代码时还要处理这些交叉业务代码。</li>
</ol>
<p>这就需要使用AOP来解决以上问题。</p>
<p><img src="https://img2024.cnblogs.com/blog/3388489/202602/3388489-20260209100621253-1133728956.png"></p>
<p><strong>总之,AOP就是将与核心业务无关的代码独立的抽取出来。形成一个独立的组件,然后以横向交叉的方式应用到业务流程当中的过程。</strong></p>
<p>aop底层使用动态代理技术实现。</p>
<p>spring AOP 使用的时jdk动态代理+cglib动态代理。spring 在这2种动态代理种灵活切换。如果是代理接口,则使用jdk动态代理,如果代理某个类,则使用cglib。当然也可以手动配置来强制使用cglib。</p>
<h2 id="aop的七大术语">AOP的七大术语</h2>
<h3 id="连接点joinpoint">连接点Joinpoint</h3>
<p>在程序执行流程中,可以织入切面的位置。方法的执行前后,异常抛出之后等位置。</p>
<p>连接点描述的是位置。</p>
<h3 id="切点pointcut">切点Pointcut</h3>
<p>在程序执行流程中,真正织入切面的方法。(一个切点对应多个连接点)</p>
<p>切点描述的是方法。</p>
<h3 id="通知advice">通知Advice</h3>
<p>通知又叫增强,就是具体你要织入的的<strong>代码</strong>。</p>
<p>通知包括:前置通知、后置通知、环绕通知、异常通知、最终通知。</p>
<p>通知描述的是代码。</p>
<h3 id="切面aspect">切面Aspect</h3>
<p>切点+通知就是切面。</p>
<h3 id="织入weaving">织入Weaving</h3>
<p>把通知应用到目标对象上的过程。</p>
<h3 id="代理对象proxy">代理对象Proxy</h3>
<p>一个目标对象被织入通知后产生的新对象。</p>
<h3 id="目标对象target">目标对象Target</h3>
<p>被织入通知的对象。</p>
<pre><code class="language-java">public void main(String[] args) {
    try {
      // Joint point连接点
      do1(); // Pointcut 切点
      // Joint point连接点
      do2();// Pointcut 切点
      // Joint point连接点
      do3();// Pointcut 切点
      // Joint point连接点
      do4();// Pointcut 切点
      // Joint point连接点
      do5();// Pointcut 切点
      // Joint point连接点
    } catch (Exception e) {
      // Joint point连接点
    }
}
</code></pre>
<h2 id="切点表达式">切点表达式</h2>
<p>切点表达式用来定义通知(Advice)往哪些方法上切入</p>
<h2 id="使用spring的aop">使用Spring的AOP</h2>
<p>Spring对AOP的实现包括三种方式:</p>
<ol>
<li>Spring结合AspectJ框架实现的AOP,基于注解方式</li>
<li>Spring结合AspectJ框架实现的AOP,基于XML方式</li>
<li>spring自己实现的AOP,基于xml配置方式</li>
</ol>
<p>常用的是前2种方式。</p>
<h3 id="准备工作">准备工作</h3>
<p>​        引入依赖</p>
<pre><code class="language-xml">&lt;dependency&gt;
    &lt;groupId&gt;org.springframework&lt;/groupId&gt;
    &lt;artifactId&gt;spring-context&lt;/artifactId&gt;
    &lt;version&gt;6.0.4&lt;/version&gt;
&lt;/dependency&gt;
&lt;dependency&gt;
    &lt;groupId&gt;org.springframework&lt;/groupId&gt;
    &lt;artifactId&gt;spring-aspects&lt;/artifactId&gt;
    &lt;version&gt;6.0.4&lt;/version&gt;
&lt;/dependency&gt;
</code></pre>
<p>引入命名空间(context和aop)和配置xml文件</p>
<pre><code class="language-xml">&lt;?xml version="1.0" encoding="UTF-8"?&gt;
&lt;beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
                           http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
                           http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd" &gt;

&lt;!--    组件扫描--&gt;
    &lt;context:component-scan base-package="com.ali.service" /&gt;
&lt;!--    开启aspectj自动代理,spring容器在扫描类的时候,会查看类上是否有@Aspect注解
      如果有。就给这个类生成代理对象 。
      proxy-target-class="true" 表示强制使用cglib动态代理
      proxy-target-class="false" 默认值,表示接口使用jdk动态代理,反之使用cglib动态代理--&gt;
    &lt;aop:aspectj-autoproxy proxy-target-class="true"/&gt;
&lt;/beans&gt;
</code></pre>
<p>编写目标类</p>
<pre><code class="language-java">// 目标类
@Service
public class UserService {
    // 目标方法
    public void login() {
      System.out.println("UserService login....");
    }
}
</code></pre>
<p>编写切面类</p>
<pre><code class="language-java">// 切面类,需要@Aspect 标注
@Aspect
@Component("logAspect")
public class LogAspect {

    // 切面 = 通知+切点
    // 通知就是增强,就是具体要编写的增强代码
    // 这里通知以方法的形式出现。
    // @Before 标注的方法就是一个前置通知
    @Before("execution(* com.ali.service.UserService.*(..))")
    public void advice( ) {
      System.out.println("这是一个前置通知,方法执行前执行....");
    }
   
    // 环绕通知是最大的通知,在前置通知之前,在后置通知之后
    @Around("execution(* com.ali.service.UserService.*(..))")
    public void around(ProceedingJoinPoint joinPoint) throws Throwable {
      System.out.println("around start");
      // 执行目标方法
      joinPoint.proceed();
      System.out.println("around end");
    }
}
</code></pre>
<p>测试代码:</p>
<pre><code class="language-java">@Test
public void test() {
    ApplicationContext context = new ClassPathXmlApplicationContext("spring.xml");
    UserService userService = context.getBean("userService", UserService.class);
    userService.login();
}
</code></pre>
<p>注意:当有多个切面类时,可以使用@Order()注解进行优先级排序,数字越小,优先级越高,就先执行。比如:@Order(2) 比@Order(3) 先执行。</p>
<p>在每个方法上都写一遍切点表达式很麻烦,可以定义一个通用的切点表达式,然后在方法上使用这个通用的表达式即可。</p>
<pre><code class="language-java">// 定义通用的切点表达式,后续通知直接使用方法名来引用切点表达式
// 切点就是一个表达式,定义了在哪些连接点上执行通知
@Pointcut("execution(* com.ali.service.UserService.*(..))")
public void commmonPointcut() {
    // 这个方法只是一个标识,方法体不需要编写任何代码
}

@AfterReturning("commmonPointcut()")
public void afterAdvice( ) {
    System.out.println("这是一个前置通知,方法执行前执行....");
}
</code></pre>
<h3 id="joinpoint的使用">JoinPoint的使用</h3>
<p>通知方法可以加入参数JoinPoint,这个是spring容器自动传入的。</p>
<pre><code class="language-java">@Before("execution(* com.ali.service.UserService.*(..))")
public void advice(JoinPoint joinPoint) {
    System.out.println("这是一个前置通知,方法执行前执行....");
    // 使用JoinPoint对象获取连接点的信息
    // joinPoint.getSignature() 获取连接点的方法签名
    // 通过方法签名可以获取到这个方法的具体信息
    // 获取方法名
    String methodName = joinPoint.getSignature().getName();
    System.out.println("正在执行的方法是:" + methodName);
}
</code></pre>


</div>
<div id="MySignature" role="contentinfo">
    <p>本文来自博客园,作者:NE_STOP,转载请注明原文链接:https://www.cnblogs.com/alineverstop/p/19593787</p><br><br>
来源:https://www.cnblogs.com/alineverstop/p/19593787
頁: [1]
查看完整版本: spring6-代理模式和AOP