1、静态代理

| 1. 静态类⽂件数量过多, 不利于项⽬管理 |
| UserServiceImpl --> UserServiceProxy |
| OrderServiceImpl --> OrderServiceProxy |
| 2. 额外功能维护性差 |
| 要对每一个代理类修改功能(麻烦) |
2、Spring 的动态代理开发
动态代理不需要定义类⽂件,都是在 JVM 运行过程中动态创建的,所以不会造成类文件数量过多,影响项目管理的问题
| <dependency> |
| <groupId>org.springframework</groupId> |
| <artifactId>spring-aop</artifactId> |
| <version>5.1.14.RELEASE</version> |
| </dependency> |
| <dependency> |
| <groupId>org.aspectj</groupId> |
| <artifactId>aspectjrt</artifactId> |
| <version>1.8.8</version> |
| </dependency> |
| <dependency> |
| <groupId>org.aspectj</groupId> |
| <artifactId>aspectjweaver</artifactId> |
| <version>1.8.3</version> |
| </dependency> |
2.1、UserServiceImpl
| public class UserServiceImpl implements UserService { |
| |
| @Override |
| public void login(String name, String password) { |
| System.out.println("UserServiceImpl.login"); |
| } |
| |
| @Override |
| public void register(User user) { |
| System.out.println("UserServiceImpl.register"); |
| } |
| } |
2.2、MethodBeforeAdvice
前置通知
| public class Before implements MethodBeforeAdvice { |
| |
| |
| |
| |
| @Override |
| public void before(Method method, Object[] args, Object target) throws Throwable { |
| System.out.println("-----method before advice log------"); |
| } |
| } |
2.3、MethodInterceptor
环绕通知
| public class Arround implements MethodInterceptor { |
| |
| @Override |
| public Object invoke(MethodInvocation invocation) throws Throwable { |
| Object ret = null; |
| try { |
| System.out.println("------前-----"); |
| ret = invocation.proceed(); |
| System.out.println("------后-----"); |
| } catch (RuntimeException e) { |
| System.out.println("------异常-----"); |
| } finally { |
| System.out.println("------最终-----"); |
| } |
| return ret; |
| } |
| } |
2.4、配置
Spring 的工厂通过原始对象的 id 值获得的是代理对象
| |
| <bean id="userService" class="xxx.UserServiceImpl"/> |
| |
| |
| <bean id="before" class="xxx.Before"/> |
| |
| <aop:config proxy-target-class="true"> |
| <aop:pointcut id="pc" expression="execution(* *(..))"/> |
| <aop:advisor advice-ref="before" pointcut-ref="pc"/> |
| </aop:config> |
| |
| <bean id="userService" class="xxx.UserServiceImpl"/> |
| |
| |
| <bean id="arround" class="xxx.Arround"/> |
| |
| <aop:config proxy-target-class="true"> |
| <aop:pointcut id="pc" expression="execution(* *(..))"/> |
| <aop:advisor advice-ref="arround" pointcut-ref="pc"/> |
| </aop:config> |
3、切入点详解
3.1、切入点表达式
方法
| * 第一个代表修饰符 + 返回值,第二个代表方法名 |
| `* *(..)` 所有方法 |
| `* login(..)` 所有 login 方法 |
| `* login(String,..)` .. 可以和具体的参数类型连用 |
| `* login(String,String)` 所有 login(String, String) 方法 |
| `* register(com.zzw.proxy.User)` 非 java.lang 包中的类型, 必须要写全限定名 |
| |
| * 精准方法 |
| * 第一个代表修饰符 + 返回值,第二个代表包 + 类 + 方法名 |
| `* com.zzw.proxy.UserServiceImpl.login(..)` |
| `* com.zzw.proxy.UserServiceImpl.login(String,String)` |
类
| `* com.zzw.proxy.UserServiceImpl.*(..)` 类中的所有方法 |
| `* *.UserServiceImpl.*(..)` 一级包的 UserServiceImpl 类 |
| `* *..UserServiceImpl.*(..)` 多级包的 UserServiceImpl 类 |
包
| `* com.zzw.proxy.*.*(..)` 不包括子包 |
| `* com.zzw.proxy..*.*(..)` 包括子包 |
3.2、切入点函数
execution
最为重要的切入点函数,功能最全
| * 最为重要的切入点函数,功能最全 |
| * 执行:方法切入点表达式、类切入点表达式、包切入点表达式 |
| |
| * 弊端:execution 执行切入点表达式,书写麻烦 |
| `execution(* com.zzw.proxy..*.*(..))` |
| |
| * 注意:其他的切入点函数是 execution 书写复杂度的简化,功能上完全一致 |
args
| * 作用: 主要用于函数(方法) 参数的匹配 |
| * 切入点: 方法参数必须得是 2 个字符串类型的参数 |
| |
| `execution(* *(String,String))` |
| `args(String,String)` |
within
| * 作用: 主要用于进行类、包切入点表达式的匹配 |
| * 切⼊点: UserServiceImpl 这个类 |
| |
| `execution(* *..UserServiceImpl.*(..))` |
| `within(*..UserServiceImpl)` |
| |
| `execution(* com.baizhiedu.proxy..*.*(..))` |
| `within(com.baizhiedu.proxy..*)` |
@annotation
| @Target(ElementType.METHOD) |
| @Retention(RetentionPolicy.RUNTIME) |
| public @interface Log { |
| } |
| * 作用: 为具有特殊注解的方法加入额外功能 |
| `<aop:pointcut id="" expression="@annotation(com.zzw.Log)"/>` |
3.3、切入点逻辑运算
and
| * 案例: login, 参数 2个字符串 |
| `execution(* login(String,String))` |
| `execution(* login(..)) and args(String,String)` |
| * 注意: 与操作不同用于同种类型的切入点函数 |
| |
or
| * 案例: register 方法 和 login 方法作为切入点 |
| `execution(* login(..)) or execution(* register(..))` |
| |
4、AOP 的底层实现原理
JDK 和 CGLib 动态代理、BeanPostProcessor
| public class ProxyBeanPostProcessor implements BeanPostProcessor { |
| |
| @Override |
| public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException { |
| return bean; |
| } |
| |
| @Override |
| public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException { |
| InvocationHandler handler = new InvocationHandler() { |
| @Override |
| public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { |
| System.out.println("----- new Log-----"); |
| Object ret = method.invoke(bean, args); |
| return ret; |
| } |
| }; |
| return Proxy.newProxyInstance(ProxyBeanPostProcessor.class.getClassLoader(), bean.getClass().getInterfaces(), handler); |
| } |
| } |
| <bean id="userService" class="com.zzw.factory.UserServiceImpl"/> |
| |
| |
| |
| |
| |
| <bean id="proxyBeanPostProcessor" class="com.zzw.factory.ProxyBeanPostProcessor"/> |
5、基于注解的 AOP 编程
5.1、四大通知
| - 前置通知(before) 在切点运行 "之前" 执行 |
| - 后置通知(after-returning) 在切点正常运行结束 "之后" 执行 |
| - 异常通知(after-throwing) 在切点 "发生异常" 的时候执行 |
| - 最终通知(after) 在切点的 "最终" 执行 |
| try { |
| 前置通知(before) |
| |
| |
| |
| 后置通知(after-returning) |
| } catch (Exception e) { |
| 异常通知(after-throwing) |
| } finally { |
| 最终通知(after) |
| } |
| try { |
| begin(); |
| |
| obj = pjp.proceed(); |
| |
| commit(); |
| } catch (Throwable e) { |
| rollback(); |
| } finally { |
| close(); |
| } |
| return obj; |
5.2、注解实现四大通知
| @Aspect |
| @Component |
| public class Logger { |
| |
| |
| @Pointcut("execution(* com.itheima.service.impl.*.*(..))") |
| private void pt() { |
| } |
| |
| @Before("pt()") |
| public void beforeMethod() { |
| System.out.println("在方法执行之前运行"); |
| } |
| |
| @AfterReturning("pt()") |
| public void afterReturnMethod() { |
| System.out.println("在方法正常返回之后运行"); |
| } |
| |
| @AfterThrowing("pt()") |
| public void afterThrowingMethod() { |
| System.out.println("在方法执行出现异常时运行"); |
| } |
| |
| @After("pt()") |
| public void afterMethod() { |
| System.out.println("在方法最终运行"); |
| } |
| } |
| |
| <context:component-scan base-package="com.zzw"/> |
| |
| |
| <aop:aspectj-autoproxy/> |
| |
| |
| <aop:aspectj-autoproxy proxy-target-class="true"/> |
5.2、注解实现环绕通知
| @Aspect |
| public class MyAspect { |
| |
| |
| @Pointcut("execution(* com.zzw.service.impl.*.*(..))") |
| private void pt() { |
| } |
| |
| |
| |
| @Around("pt()") |
| public Object aroundMethod(ProceedingJoinPoint pjp) { |
| Object obj = null; |
| try { |
| System.out.println("在方法执行之前运行"); |
| |
| obj = pjp.proceed(); |
| |
| System.out.println(new Date().toLocaleString()); |
| System.out.println(Arrays.toString(pjp.getArgs())); |
| System.out.println(obj); |
| MethodSignature methodSignature = (MethodSignature) pjp.getSignature(); |
| System.out.println(methodSignature.getMethod().getName()); |
| |
| System.out.println("在方法正常返回之后运行"); |
| } catch (Throwable e) { |
| System.out.println("在方法执行出现异常时运行"); |
| } finally { |
| System.out.println("在方法最终运行"); |
| } |
| return obj; |
| } |
| } |
| |
| <bean id="arround" class="xxx.MyAspect"/> |
| |
| |
| <aop:aspectj-autoproxy/> |
| |
| |
| <aop:aspectj-autoproxy proxy-target-class="true"/> |
5.3、0 xml 实现
| @Configuration |
| @ComponentScan("com.zzw") |
| @EnableAspectJAutoProxy(proxyTargetClass = true) |
| public class SpringConfig { |
| } |
6、AOP 的坑
| * 坑: 在同⼀个业务类中, 进行业务方法间的相互调用, 只有最外层的方法, 才是加⼊了额外功能(内部的方法, 通过普通的方式调用, 都调用的是原始方法) |
| * 如果想让内层的方法也调用代理对象的方法, 就要实现 AppicationContextAware 获得工厂, 进而获得代理对象 |
| public class UserServiceImpl implements UserService, ApplicationContextAware { |
| |
| private ApplicationContext ctx; |
| |
| @Override |
| public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { |
| this.ctx = applicationContext; |
| } |
| |
| @Override |
| public void register(User user) { |
| System.out.println("UserServiceImpl.register"); |
| |
| |
| UserService userService = (UserService) ctx.getBean("userService"); |
| userService.login("suns", "123456"); |
| } |
| |
| @Override |
| public boolean login(String name, String password) { |
| System.out.println("UserServiceImpl.login"); |
| return true; |
| } |
| } |
7、AOP 阶段知识总结
| - 前置通知(before) 在切点运行 "之前" 执行 |
| - 后置通知(after-returning) 在切点正常运行结束 "之后" 执行 |
| - 异常通知(after-throwing) 在切点 "发生异常" 的时候执行 |
| - 最终通知(after) 在切点的 "最终" 执行 |
| try { |
| 前置通知(before) |
| |
| |
| |
| 后置通知(after-returning) |
| } catch (Exception e) { |
| 异常通知(after-throwing) |
| } finally { |
| 最终通知(after) |
| } |

【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步