
Spring AOP
代理模式通过“中间层”实现了对真实对象的间接访问,是解决权限控制资源管理和功能扩展问题的利器。无论是静态代理还是动态代理(如 JDK/CGLIB),其本质均是通过代理对象间接操作目标对象,从而在调用链中插入自定义逻辑。
目录
一、代理模式
1、什么是代理模式?
代理模式是一种结构型的设计模式,其核心思想是通过一个“代理对象”来控制对真是对象的访问。从而在不修改原始对象的前提下,实现对目标对象的功能增强或访问控制。他是面向对象设计中实现“间接访问”的经典模式
2、代理模式的核心结构
- Subject 抽象主题
- 定义真实对象和代理对象的共同接口,客户端通过该接口操作真实对象
- RealSubject 真实主题
- 实际执行业务逻辑的对象,是代理模式最重要访问的目标
- Proxy 代理
- 持有真实对象的引用,负责控制对真实对象的访问,并可在调用真实对象前后插入额外的逻辑。
3、典型应用场景
虚拟代理
延迟加载大资源(如图片、文件),直到真正需要时才创建真实对象。
示例:网页中图片的占位符,滚动到可见区域时才加载真实图片。保护代理
控制对真实对象的访问权限(如身份验证)。
示例:敏感操作需验证用户角色后才能执行。远程代理
为不同地址空间的对象提供本地代理(如 RPC 调用)。
示例:分布式系统中通过代理对象调用远程服务。日志/监控代理
记录方法调用信息或性能指标。
示例:记录接口耗时、调用次数等。
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"); } }
关键特性
-
基于接口:被代理类必须实现接口,否则无法生成代理。
-
运行时增强:动态生成字节码,无需手动编写代理类。
-
灵活扩展:通过
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。 关键说明
默认作用域:
singleton
是 Spring 容器的默认作用域,适用于无状态的共享 Bean(如工具类、服务类)。原型作用域:
prototype
适用于需要每次获取新实例的场景(如包含状态的临时对象)。Web 相关作用域:
request
、session
、application
、websocket
仅在 Web 环境中生效(如 Spring MVC 应用)。线程安全问题:
非单例作用域(如
prototype
、request
)的 Bean 需开发者自行管理线程安全。
更多推荐
所有评论(0)