首页 > 编程语言 >Spring源码系列六:AOP

Spring源码系列六:AOP

时间:2024-08-10 12:28:24浏览次数:15  
标签:return Spring 代理 源码 AOP new 方法 public before

更多请关注:https://t.zsxq.com/fhroW

文章目录

ProxyFactory

ProxyFactory是Spring中的代理工具,其中封装了CGLIB和JDK动态代理,会自动判断使用哪种代理,AOP就是通过ProxyFactory实现的。

使用ProxyFactory

UserService userService =new UserService();
ProxyFactory proxyFactory =new ProxyFactory();
proxyFactory.setTarget(userService);
proxyFactory.addAdvice(new MethodBeforeAdvice() {
	@Override
	public void before(Method method, Object[] argsObject target) throws Throwable {
		System.out.println("before");
	}
});
UserService userProxyService = (UserServiceproxyFactory.getProxy();
userProxyService.test();

如果通过setInterfaces方法设置了接口,将会使用JDK动态代理,getProxy方法返回的对象就会是接口类型。

Advice

通过addAdvice可以设置增强类,该方法接收Advice接口类型的参数,Advice接口的实现类,常用的有以下几种

  • before MethodBeforeAdvice
  • after AfterReturningAdvice
  • around MethodInterceptor
  • throw ThrowsAdvice

before和after不用多说,就是方法执行之前和执行之后执行

ThrowsAdvice

方法抛出异常后执行,该接口支持处理不同类型的异常,比如只处理空指针异常。为了做到支持不同类型的异常,该接口中没有定义方法声明。而是约定该类的实现类中有一个afterThrowing方法。
该方法有两种:

  • void afterThrowing(Exception ex);
  • void afterThrowing(Method method, Object[] args, Object target, Exception ex)

只是参数不同,没有其他含义。使用ThrowAdvice增强会在执行完afterThrowing方法后再抛出异常,而不是在原方法中直接抛出。

MethodInterceptor

环绕增强,注意是org.springframework.aop.MethodBeforeAdvice包下的,而不是CGLIB包下的,Spring拷贝了一份CGLIB的代码放在源码汇总,CGLIB也有一个MethodInterceptor。
使用其他的增强时不需要手动执行原方法,但是在环绕增强中需要手动执行原方法。所以涉及到执行顺序问题

proxyFactory.addAdvice(new MethodInterceptor() {
	@Nullable
	@Override
	public Object invoke(@NotNull MethodInvocatioinvocation) throws Throwable {
		System.out.println("before");
		invocation.proceed();
		System.out.println("after");
		return null;
	}
});

如果添加了多个增强,那么执行顺序应该是怎样的?
如果添加了多个前置增强,执行顺序就是添加顺序。
如果添加了多个环绕增强,执行顺序是链式执行的,比如填了两个环绕增强A、B,添加了一个前置增强C,添加顺序为A->C->B

proxyFactory.addAdvice(new MethodInterceptor() {
	public Object invoke(@NotNull MethodInvocatioinvocation) throws Throwable {
		System.out.println("aroundA before");
		Object proceed = invocation.proceed();
		System.out.println("aroundA after");
		return proceed;
	}
});

proxyFactory.addAdvice(new MethodBeforeAdvice() {
	public void before(Method method, Object[args, Object target) throws Throwable {
		System.out.println("before");
	}
});

proxyFactory.addAdvice(new MethodInterceptor() {
	public Object invoke(@NotNull MethodInvocatioinvocation) throws Throwable {
		System.out.println("aroundB before");
		Object proceed = invocation.proceed();
		System.out.println("aroundB after");
		return proceed;
	}
});

打印顺序为:

aroundA before
before
aroundB before
test
aroundB after
aroundA after

理解为:
执行链上有A–>C–>B三个增强器。先从链上拿到A增强器,执行A的invoke方法,执行process方法时,又去执行链上拿增强器(此时A并未执行完),此时拿到C增强器,执行C的before方法,再去执行链上拿增强器,拿到B的增强器并执行B的invoke方法,执行B的prcess方法后又去执行链上拿增强器,发现没有了,就执行完自己的invoke方法。然后又回到A的aroundA after。

和递归一样,每次执行process方法就进行一次递归。

Advisor

使用advice会增强被代理对象中的所有方法,而Advisor可以对方法进行筛选,做到对类中的某些方法进行增强,可以理解为增加了切点

proxyFactory.addAdvisor(new PointcutAdvisor() {
	@Override
	public Pointcut getPointcut() {
		return new StaticMethodMatcherPointcut() {
			@Override
			public boolean matches(Method method, Class<?> targetClass) {
				return method.getName().equals("test");
			}
		};
	
	@Override
	public Advice getAdvice() {
		return new MethodBeforeAdvice() {
			@Override
			public void before(Method method, Object[] args, Objectarget) throws Throwable {
				System.out.println("before");
			}
		};
	
	@Override
	public boolean isPerInstance() {
		return false;
	}
});

getPointcut方法最终返回Pointcut类型,Pointcut的实现类有很多,对应不同的筛选方式,比如表达式筛选,代码中的StaticMethodMatcherPointcut支持方法名或类的类型进行筛选

getAdvice方法即添加增强器。
所以advisor和advice的区别就在于advisor添加了切点pointCut,可以实现对某个方法进行增强。

通过spring产生代理对象

以上都是我们使用ProxyFactory手动创建代理对象的,spring也提供了对应的Bean,来帮助我们创建代理对象,有很多,比如:

  • 针对单个bean进行代理,ProxyFactoryBean就是Spring提供的类
@Bean
public ProxyFactoryBean userServiceProxy(){
	UserService userService = new UserService();

	ProxyFactoryBean proxyFactoryBean = new ProxyFactoryBean();
	proxyFactoryBean.setTarget(userService);
	proxyFactoryBean.addAdvice(new MethodInterceptor() {
		@Override
		public Object invoke(MethodInvocation invocation) throws Throwable {
			System.out.println("before...");
			Object result = invocation.proceed();
			System.out.println("after...");
			return result;
		}
	});
	return proxyFactoryBean;
}
  • 通过beanname产生代理对象
@Bean
public BeanNameAutoProxyCreator beanNameAutoProxyCreator() {
	BeanNameAutoProxyCreator beanNameAutoProxyCreator = new BeanNameAutoProxyCreator();
	beanNameAutoProxyCreator.setBeanNames("userSe*");
	beanNameAutoProxyCreator.setInterceptorNames("zhouyuAroundAdvice");
	beanNameAutoProxyCreator.setProxyTargetClass(true);

    return beanNameAutoProxyCreator;
}
  • 用户指定切点和增强器,Spring生成代理
@Bean
public DefaultPointcutAdvisor defaultPointcutAdvisor(){
	NameMatchMethodPointcut pointcut = new NameMatchMethodPointcut();
	pointcut.addMethodName("test");

    DefaultPointcutAdvisor defaultPointcutAdvisor = new DefaultPointcutAdvisor();
	defaultPointcutAdvisor.setPointcut(pointcut);
	defaultPointcutAdvisor.setAdvice(new ZhouyuAfterReturningAdvice());

    return defaultPointcutAdvisor;
}

@Bean
public DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator() {
	return new DefaultAdvisorAutoProxyCreator();;
}

这里的DefaultAdvisorAutoProxyCreator最终实现了BeanPostProcessor,所以在Bean创建时会调用这里对应的逻辑生成代理对象。
这里就和AOP有点像了,用户只需要指定切点和增强逻辑,Spring就会帮助用户生成代理对象。

TargetSource

setTarget传入被代理对象,该方法会将被代理对象生成TargetSource对象。
用户也可以通过setTargetSource直接设置一个TargetSource对象,TargetSource类中有一个方法getTarget可以获取被代理对象,执行被代理对象的方法时会调用getTarget.

Spring Aop

创建流程

在配置类中添加@EnableAspectJAutoProxy注解表示开启AOP,这个注解的作用就是注册AnnotationAwareAspectJAutoProxyCreator,这个类最终实现了SmartInstantiationAwareBeanPostProcessor。
所以@EnableAspectJAutoProxy注解就是添加了一个BeanPostProcessor,会在Spring创建Bean初始化后的步骤调用。

在SmartInstantiationAwareBeanPostProcessor中会创建代理对象。步骤:

  • 找到所有切面Bean(添加了@Aspect注解的类),解析其中添加了AOP注解的方法,并将每个方法封装为一个Advisor
  • 判断当前Bean是否符合Advisor,如果符合,使用ProxyFactory创建代理对象(有多个符合的Advisor时,使用AddAdvisor方法添加即可)

通过这两步就创建好代理对象了

判断ProxyFactory使用JDK代理还是CGLIB代理

// config就是ProxyFactory对象

// optimize为true,或proxyTargetClass为true,或用户没有给ProxyFactory对象添加interface
if (config.isOptimize() || config.isProxyTargetClass() || hasNoUserSuppliedProxyInterfaces(config)) {
	Class<?> targetClass = config.getTargetClass();
	if (targetClass == null) {
		throw new AopConfigException("TargetSource cannot determine target class: " +
				"Either an interface or a target is required for proxy creation.");
	}
    // targetClass是接口,直接使用Jdk动态代理
	if (targetClass.isInterface() || Proxy.isProxyClass(targetClass)) {
		return new JdkDynamicAopProxy(config);
	}
    // 使用Cglib
	return new ObjenesisCglibAopProxy(config);
}
else {
    // 使用Jdk动态代理
	return new JdkDynamicAopProxy(config);
}

optimize属性和proxyTargetClass属性默认都是false,可以手动设置。
hasNoUserSuppliedProxyInterfaces会判断有没有添加接口,即

proxyFactory.setInterfaces()

optimize或proxyTargetClass为true,或者设置了接口则使用cglib代理,反之使用JDK动态代理。如果代理类实现了接口,但没有通过setInterfaces方法设置接口,hasNoUserSuppliedProxyInterfaces返回的是true,也就是没有接口。
Spring中如果发现代理类实现了接口,会通过setInterfaces方法设置接口

拓展

  1. AspectJ
    AOP是一种思想,SpringAOP是一种实现,AspectJ是另一个实现AOP思想的项目,Spring中的@Before、@After这些注解都是AspectJ中的注解,spring对这些注解实现了自己的逻辑。
    所以@Before这些注解是在org.aspectj.lang.annotation包下的,所以允许使用@Before的配置类注解叫@EnableAspectJAutoProxy
    另外,结合上文中使用的DefaultAdvisorAutoProxyCreator,注册了这个Bean只会去找Spring中定义的DefaultPointcutAdvisor。添加了EnableAspectJAutoProxy注解(实际上也是注册了一个类)就会去找AspectJ注解的增强

  2. 一个切面类中,同一个方法有两个After增强,如何判断顺序?
    根据方法名称进行判断A->Z

标签:return,Spring,代理,源码,AOP,new,方法,public,before
From: https://blog.csdn.net/qq_43081232/article/details/141088900

相关文章

  • Spring源码系列五:循环依赖
    更多请关注:https://t.zsxq.com/fhroW文章目录什么是循环依赖?解决思想spring如何解决循环依赖三级缓存补充什么是循环依赖?循环依赖是spring中的问题,普通的java项目不会有循环依赖。Spring中出现的循环依赖有三种情况:自我依赖、循环依赖、多组依赖什么是循环......
  • 【Java毕设选题推荐】基于SpringBoot的springbootJAVA的邮件过滤系统设计与实现
    前言:我是IT源码社,从事计算机开发行业数年,专注Java领域,专业提供程序设计开发、源码分享、技术指导讲解、定制和毕业设计服务......
  • 【Java毕设选题推荐】基于SpringBoot的O2O生鲜食品订购小程序
    前言:我是IT源码社,从事计算机开发行业数年,专注Java领域,专业提供程序设计开发、源码分享、技术指导讲解、定制和毕业设计服务......
  • Spring Boot集成Redis
    目录1.Redis简介2.添加依赖3.项目配置4.使用RedisStringTestSetTestListTestHashTestZSetTest5.测试结果1.Redis简介Redis是一款基于内存的使用K-V结构存取数据的NoSQL非关系型数据库。Redis的主要作用就是缓存数据,通常,会将关系型数据库(例如MySQL等)中的数据读......
  • 毕业设计:基于Springboot的口腔医院微信小程序【代码+论文+PPT】
    全文内容包括:1、采用技术;2、系统功能;3、系统截图;4、配套内容。索取方式见文末微信号,欢迎关注收藏!一、采用技术语言:Java1.8框架:SpringBoot数据库:MySQL5.7、8.0开发工具:IntelliJIDEA旗舰版、微信开发工具其他:Maven3.8以上二、系统功能系统首页:展示口腔医院微信小程序的主......
  • Springboot+Mybatis实现最简单的增删改查
    1.创建一个SpringBoot项目 2.导入依赖勾选上web下面的SpringWeb和sql下面的MySQL和Mybatis依赖 然后等待依赖下载导入2.1 依赖导入后的页面3.创建需要的包和Mybatis配置文件 3.1在原来的com.mybatis包下创建了entity,controller,service,mapper四个包3.2在resource......
  • [网络] 关键类及源码分析:java.net包
    0序1java.net包概述2关键类及源码分析2.1java.net.InetAddresspublicstaticvoidmain(String[]args)throwsUnknownHostException{ //1.获取本机的InetAddress对象 InetAddresslocalHost=InetAddress.getLocalHost(); System.out.println(localHost);//LAPT......
  • Spring AI 更新:支持OpenAI的结构化输出,增强对JSON响应的支持
    就在昨晚,SpringAI发了个比较重要的更新。由于最近OpenAI推出了结构化输出的功能,可确保AI生成的响应严格遵守预定义的JSON模式。此功能显着提高了人工智能生成内容在现实应用中的可靠性和可用性。SpringAI紧随其后,现在也可以对OpenAI的结构化输出完美支持了。下图展示了本......
  • 【深入理解SpringCloud微服务】Ribbon源码解析
    【深入理解SpringCloud微服务】Ribbon源码解析Ribbon的原理RestTemplate中的拦截器链Ribbon的拦截器如何将拦截器放入到RestTemplate中Ribbon中的核心类LoadBalancerAutoConfigurationLoadBalancerInterceptorLoadBalancerClientILoadBalancerServerListIRuleIPingRibb......
  • OpenCV与AI深度学习 | 手把手教你用Python和OpenCV搭建一个半自动标注工具(详细步骤 +
    本文来源公众号“OpenCV与AI深度学习”,仅用于学术分享,侵权删,干货满满。原文链接:手把手教你用Python和OpenCV搭建一个半自动标注工具(详细步骤+源码)导 读    本文将手把手教你用Python和OpenCV搭建一个半自动标注工具(包含详细步骤+源码)。背景介绍    样本标......