目录

一、代理模式

1、什么是代理模式?

2、代理模式的核心结构

3、典型应用场景

4、代理模式 vs 装饰器模式

5、总结

1.1、静态代理

1.2、动态代理

1.2.1 jdk动态代理

核心方法及参数含义

示例:

关键特性

二、SpringAOP

2.1 核心概念

三、AOP - Schema based 方式

四、AOP - AspectJ 方式

五、注解实现AOP

六、bean的作用域


一、代理模式

1、什么是代理模式?

代理模式是一种结构型的设计模式,其核心思想是通过一个“代理对象”来控制对真是对象的访问。从而在不修改原始对象的前提下,实现对目标对象的功能增强访问控制。他是面向对象设计中实现“间接访问”的经典模式


2、代理模式的核心结构

  • Subject 抽象主题
    • 定义真实对象和代理对象的共同接口,客户端通过该接口操作真实对象
  • RealSubject 真实主题
    • 实际执行业务逻辑的对象,是代理模式最重要访问的目标
  • Proxy 代理
    • 持有真实对象的引用,负责控制对真实对象的访问,并可在调用真实对象前后插入额外的逻辑。

3、典型应用场景

  1. 虚拟代理
    延迟加载大资源(如图片、文件),直到真正需要时才创建真实对象。
    示例:网页中图片的占位符,滚动到可见区域时才加载真实图片。

  2. 保护代理
    控制对真实对象的访问权限(如身份验证)。
    示例:敏感操作需验证用户角色后才能执行。

  3. 远程代理
    为不同地址空间的对象提供本地代理(如 RPC 调用)。
    示例:分布式系统中通过代理对象调用远程服务。

  4. 日志/监控代理
    记录方法调用信息或性能指标。
    示例:记录接口耗时、调用次数等。


4、代理模式 vs 装饰器模式

特性 代理模式 装饰器模式
核心目的 控制访问(如权限、延迟加载) 动态添加功能(增强对象行为)
关注点 限制或管理对对象的访问 扩展对象的功能
对象关系 代理类与真实对象通常是1:1关系 装饰器与被装饰对象可多层嵌套
初始化时机 代理类可延迟创建真实对象(如虚拟代理)

装饰器需依赖已存在的被装饰对象


5、总结

代理模式通过“中间层”实现了对真实对象的间接访问,是解决权限控制资源管理功能扩展问题的利器。无论是静态代理还是动态代理(如 JDK/CGLIB),其本质均是通过代理对象间接操作目标对象,从而在调用链中插入自定义逻辑。

1.1、静态代理

1、什么是静态代理

静态代理是手动编写代理类的方式,代理类和被代理类在编译器就已经确定了。每个被代理类都需要一个对应的代理类。通过组合或继承实现方法增强。

2、实现示例

// 接口
public interface UserService {
    void save(String name);
}

// 被代理类(真实对象)
public class UserServiceImpl implements UserService {
    @Override
    public void save(String name) {
        System.out.println("保存用户: " + name);
    }
}

// 静态代理类(手动编写)
public class UserServiceProxy implements UserService {
    private final UserService target;  // 组合被代理对象

    public UserServiceProxy(UserService target) {
        this.target = target;
    }

    @Override
    public void save(String name) {
        System.out.println("方法调用前");
        target.save(name);  // 调用真实对象的方法
        System.out.println("方法调用后");
    }
}

// 使用
public class Main {
    public static void main(String[] args) {
        UserService target = new UserServiceImpl();
        UserService proxy = new UserServiceProxy(target);
        proxy.save("Alice");
    }
}


1.2、动态代理

1.2.1 jdk动态代理

JDK动态代理是JAVA中基于接口实现代理的一种机制,允许在运行时动态创建代理类。常用于AOP。其核心依赖 java.lang.reflect.Proxy java.lang.reflect.InvocationHandler .


核心方法及参数含义

1、 Proxy.newProxyInstance()

public static Object newProxyInstance(
    ClassLoader loader,          // 类加载器
    Class<?>[] interfaces,       // 被代理类实现的接口
    InvocationHandler handler    // 方法调用处理器
)
  • ClassLoader
    • 通常使用被代理类的类加载器(eg. target.getClass().getClassLoader()),用于加载代理类
  • Class<?>[ ] interface
    • 指定代理类需要实现的接口(eg. target.getClass.getInterfaces()).JDK动态代理需要求被代理对象必须实现至少一个接口
  • InvocationHandler
    • 代理逻辑的核心,所有方法调用会被转发到 InvocationHandler.invoke() 方法。(代理对象真正需要做的事,必须自己指定)

2、InvocationHandler.invoke()

作用:处理代理对象的方法调用,实现增强逻辑

参数:

public Object invoke(
    Object proxy,      // 代理对象本身(通常不直接使用,避免递归调用)
    Method method,     // 被调用的方法对象 --谁调用的代理
    Object[] args      // 被调用的方法参数  --被代理对象方法的参数
) throws Throwable;
示例:

1、定义接口和实现类

public interface UserService {
    void save(String name);
}

public class UserServiceImpl implements UserService {
    @Override
    public void save(String name) {
        System.out.println("保存用户: " + name);
    }
}

2、实现InvocationHandler

public class LogHandler implements InvocationHandler {
    private final Object target;  // 被代理对象

    public void setTarget(Object target){
        this.target = target;
    }

    // 创建代理对象
    public Object getProxy(){
        return Proxy.newProxyInstance(this.getClass().getClassLoader(),
            target.getClass().getInterfaces(), this);
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("方法调用前: " + method.getName());
        Object result = method.invoke(target, args);  // 反射调用真实方法
        System.out.println("方法调用后");
        return result;
    }
}

3、创建代理对象

public class Main {
    public static void main(String[] args) {
        UserService target = new UserServiceImpl();
        LogHandler handler = new LogHandler();
        handler.setTarget(target);
        UserService proxy= (UserService)handler.getProxy();

        // 调用代理方法
        proxy.save("Alice");
    }
}

关键特性
  1. 基于接口:被代理类必须实现接口,否则无法生成代理。

  2. 运行时增强:动态生成字节码,无需手动编写代理类。

  3. 灵活扩展:通过 InvocationHandler 统一处理多个方法的增强逻辑。


二、SpringAOP

实现AOP的两种方式

在Spring中提供了两种方式实现AOP:

  • Schema-based:所有的通知都需要实现特定类型的接口。

  • AspectJ:可以使用普通Java类结合特定的配置标签实现通知。

 核心概念(不用死记)

  • 切面 Aspect: 封装横切逻辑的模块(eg. 日志工具类),通过 @Aspect 注解定义。

  • 连接点Join Point:程序执行过程中的点(eg. 方法调用、异常抛出)。

  • 通知 Advice:切面在连接点执行的动作,分为五种类型。

  • 切入点 Pointcut:通过表达式匹配连接点,确定通知的应用位置。

  • 目标对象 Target:被代理的原始对象。

  • 代理 Proxy:Spring 生成的增强对象,用于插入切面逻辑。

  • 织入 Weaving: 将切面应用到目标对象的过程(Spring AOP在运行时完成)


三、AOP - Schema based 方式

Schema-based:所有的通知都需要实现特定类型的接口

1. 前置通知 — 在切点方法前加入的功能  — 通知需要实现MethodBeforeAdvice接口
2. 后置通知 — 在切点方法后加入的功能 — 通知需要实现AfterReturningAdvice接口
3. 异常通知 —在切点方法发生异常后加入的功能 — 通知需要实现ThrowsAdvice接口。
4. 环绕通知 — 前置通知 + 后置通知 + 异常通知 — 通知需要实现MethodInterceptor接口


四、AOP - AspectJ 方式

Schema-based方式的缺点: 在使用Schema-based方式实现功能扩展时,每个通知对应一个类,每个类都需要实现接口,这样造成代码的结构体系过于繁杂。

重点看五、注解实现


五、注解实现AOP

SpringAOP也给出了使用注解方式来配置AOP,但是AOP的注解方式只支持对AspectJ的简化

 service接口:

public interface UserService {
    void save(String name);
}

service实现类:

public class UserServiceImpl implements UserService {
    @Override
    public void save(String name) {
        System.out.println("UserServiceImpl.save,保存用户:" + name);
    }
}

通知类

package com.msb.advice;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;

/**
 * @Component注解,就是为了构建MyAspectJAdvic 的bean对象
 * @Aspect注解代表我现在要加入、简化AspectJ方式的注解
 */
@Component
@Aspect
public class MyAspectJAdvice {

    /*配置切点:随便定义一个方法,名字是什么都可以,我就用a方法
    * 在a方法前加入注解来配置切点*/
    @Pointcut("execution(* com.msb.service.impl.*.*(..))")
    public void a(){

    }

    /*不同的通知方法,加入对应的注解即可
    * 但是别忘了配置对应的切点,将上面切点对应的方法传入注解的参数即可*/
    // 定义前置通知的方法
    @Before("a()")
    public void before(){
        System.out.println("------前置通知-------");
    }

    // 定义后置通知的方法:
    @After("a()")
    public void after(){
        System.out.println("------后置通知-------" );
    }
    // 定义异常通知的方法:
    @AfterThrowing(pointcut="a()",throwing = "ex")
    public void mythrow(Exception ex){
        System.out.println("------异常通知-------,当前异常的类型为:" + ex.getClass().getName());
    }

    // 环绕通知
    @Around("a()")
    public Object around(ProceedingJoinPoint p) throws Throwable {
        System.out.println("-----环绕通知的前置通知----" );
        // 执行切点方法:
        Object o = p.proceed();
        return o;
    }
}

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:context="http://www.springframework.org/schema/context"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context
        https://www.springframework.org/schema/context/spring-context.xsd
        http://www.springframework.org/schema/aop
        https://www.springframework.org/schema/aop/spring-aop.xsd">

    <!--扫描@Service注解所在的包,以便构建service层对象
    扫描@Component注解所在的包,以便MyAspectJAdvic对象    -->
    <context:component-scan base-package="com.service,com.advice"></context:component-scan>

    <!--扫描AOP的注解-->
    <aop:aspectj-autoproxy expose-proxy="true"></aop:aspectj-autoproxy>

</beans>

测试类

public class App 
{
    public static void main( String[] args )
    {
        // 解析xml:
        ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext.xml");
        // 得到对象:
        UserService us = (UserService)ac.getBean("userServiceImpl");
        // 调用b方法:
        us.save("乐游");
    }
}


六、bean的作用域

作用域(Scope) 描述(Description)
singleton (默认)将单个 Bean 定义限定为每个 Spring IoC 容器的单个对象实例
prototype 将单个 Bean 定义限定为任意数量的对象实例。每次通过容器获取该 Bean 时都会创建一个新实例。
request 将单个 Bean 定义限定为单个 HTTP 请求的生命周期。每个 HTTP 请求会基于同一 Bean 定义创建独立的实例。仅适用于支持 Web 的 Spring ApplicationContext。
session 将单个 Bean 定义限定为HTTP 会话(Session)的生命周期。仅适用于支持 Web 的 Spring ApplicationContext。
application 将单个 Bean 定义限定为ServletContext 的生命周期。仅适用于支持 Web 的 Spring ApplicationContext。
websocket 将单个 Bean 定义限定为WebSocket 的生命周期。仅适用于支持 Web 的 Spring ApplicationContext。

关键说明

  1. 默认作用域

    • singleton 是 Spring 容器的默认作用域,适用于无状态的共享 Bean(如工具类、服务类)。

  2. 原型作用域

    • prototype 适用于需要每次获取新实例的场景(如包含状态的临时对象)。

  3. Web 相关作用域

    • requestsessionapplicationwebsocket 仅在 Web 环境中生效(如 Spring MVC 应用)。

  4. 线程安全问题

    • 非单例作用域(如 prototyperequest)的 Bean 需开发者自行管理线程安全。

Logo

助力广东及东莞地区开发者,代码托管、在线学习与竞赛、技术交流与分享、资源共享、职业发展,成为松山湖开发者首选的工作与学习平台

更多推荐