Spring之AOP源码(一)

文章目录

    • 一、动态代理
      • 1. 概念
      • 2. Cglib动态代理的使用
      • 3. JDK动态代理的使用
    • 二、SpringAOP
      • 1. 简介
      • 2. Spring AOP使用

一、动态代理

1. 概念

动态代理(Dynamic Proxy)是一种在运行时动态生成代理对象的技术。它是一种设计模式,用于在不修改原始对象的情况下,通过代理对象来间接访问原始对象,并在访问前后执行额外的操作。

在Spring中实现动态代理无非就两种方式,分别是Cglib和Jdk动态代理。

2. Cglib动态代理的使用

CGLIB(Code Generation Library)是一个功能强大且高性能的代码生成库,它是用Java编写的,用于在运行时生成字节码并创建新的类。CGLIB广泛用于AOP(面向切面编程)和其他需要在运行时生成类的场景。以下是CGLIB的一些主要特点和用法:

继承式代理: CGLIB使用继承方式生成代理类,而不是像JDK动态代理那样要求目标类必须实现接口。这意味着你可以代理没有实现任何接口的类。

字节码生成: CGLIB通过使用ASM库来生成字节码,实现了在运行时创建新类的功能。这使得可以在运行时修改类的行为,例如添加新的方法、修改字段等。

性能高效: 由于CGLIB是直接生成字节码,相对于反射,它的性能通常更高。这使得它在一些要求高性能的场景中得到广泛应用。

AOP支持: CGLIB常用于实现AOP,通过在运行时生成代理类,在目标方法的前后添加额外的逻辑。

Spring框架中的应用: Spring框架的AOP模块就使用了CGLIB,当目标对象没有实现接口时,Spring会默认使用CGLIB来创建代理类。

下面是Cglib使用的一个案例

UserService target = new UserService();
//构建增强器对象
Enhancer enhancer = new Enhancer(); // cglib
//设置要代理的那个类
enhancer.setSuperclass(UserService.class);
//设置代理逻辑
enhancer.setCallbacks(new Callback[]{new MethodInterceptor() { 
@Override
      public Object intercept(Object o, Method method, Object[] objects, MethodProxy
  methodProxy) throws Throwable {
          System.out.println("before...");
          //执行被代理对象的方法
          Object result = methodProxy.invoke(target, objects);
          System.out.println("after...");
          return result;
}
}});
//获得一个代理对象
UserService userService = (UserService) enhancer.create(); UserService
userService.test();

在这里插入图片描述
cglib底层是操作ASM码的,这部分源码在我的这篇博客详细分析了,现在我们来理解上的代码,首先我们知道Object result = methodProxy.invoke(target, objects);,target是我们实际被代理的对象,Object o就是我们生成的代理对象。下面对于这句代码,我提供3种写法,来分析一下:

//1. 
Object result = method.invoke(target, objects);
//2.
Object result = method.invoke(o, objects);
//3. 
Object result = methodProxy.invokeSuper(o, objects);

对于写法一其实和案例中正常写法一样,也是执行代理目标对象的方法,写法二这个会报错,它会执行代理对象的方法也就是,它会执行new MethodInterceptor() { 后面的代码,由于内部一直调用自己会陷入死循环,最后一个就是执行代理对象的父类的方法,这个是没问题的,我们前面说过cglib动态代理是基于继承的机制,这里想代理的目标对象是UserService类,所以执行它父类的方法就等同于执行UserService方法。

下面讲解一下cgblib其它的一些功能,我们知道如果我们代理了一个类,那么通常这个类中的所有的方法都会被代理,现在我们修改Userservice类。

public class UserService {

	public void test(){
		System.out.println("执行UserService的test方法");
	}
	public void a(){
		System.out.println("执行UserService的a方法");
	}
}

现在提出一个需求,只需要代理test方法而不需要代理a方法,或者执行test方法是一段代理逻辑,执行a方法是另一段代理逻辑。思路是每个方法对应一个执行器:

public class Test {

	public static void main(String[] args) {
		UserService target = new UserService();
		Enhancer enhancer = new Enhancer(); // cglib
		enhancer.setSuperclass(UserService.class);
		enhancer.setCallbacks(new Callback[]{new MethodInterceptor() {
			@Override
			public Object intercept(Object o, Method method, Object[] objects, MethodProxy
					methodProxy) throws Throwable {
				System.out.println("before...");
				Object result = methodProxy.invoke(target, objects);
				System.out.println("after...");
				return result;
			}
		}, NoOp.INSTANCE});
		enhancer.setCallbackFilter(new CallbackFilter() {
			@Override
			public int accept(Method method) {
			//返回值对应Callback中的方法处理器的下标
				if(method.getName().equals("test"))
				  return 0;
				else{
					return 1;
				}
			}
		});

		UserService userService = (UserService) enhancer.create();
		userService.test();
		userService.a();
	}
}

NoOp.INSTANCE你可以理解为啥事也没干就行了。我们看一下输出结果:
在这里插入图片描述

3. JDK动态代理的使用

JDK动态代理是Java提供的一种实现动态代理的机制。它是通过Java反射机制来实现的,主要用于在运行时动态生成代理类和对象。动态代理使得我们可以在不事先知道目标对象的情况下,通过代理类来调用目标对象的方法。以下是JDK动态代理的一些关键概念和使用方式:

接口必须实现: JDK动态代理要求目标对象必须实现一个或多个接口。代理类会实现这些接口,并且在调用方法时将请求委托给实际的目标对象。

Proxy类和InvocationHandler接口: JDK动态代理通过Proxy类和InvocationHandler接口实现。Proxy类提供了创建代理对象的静态方法,而InvocationHandler接口中有一个方法invoke,该方法在代理对象的方法被调用时执行。

newProxyInstance方法: Proxy类的newProxyInstance方法用于创建代理对象。该方法接受三个参数:ClassLoader(用于加载代理类的类加载器)、Class数组(目标对象实现的接口列表)、和InvocationHandler接口的实现类实例。

public class Test {

	public static void main(String[] args) {
		UserService target = new UserService();
		Aservice aservice = (Aservice) Proxy.newProxyInstance(Test.class.getClassLoader(), new Class[]{Aservice.class}, new InvocationHandler() {
			@Override
			public Object invoke(Object o, Method method, Object[] objects) throws Throwable {
				System.out.println("before");
				//执行被Userservice的方法
				method.invoke(target,objects);
				System.out.println("after");
				return null;
			}
		});
		aservice.test();
	}
}

注意JDK动态代理被代理的对象其实是那个接口类型。

二、SpringAOP

1. 简介

上面介绍了两种常用的动态代理方式,而Spring底层就对这两种代理方式进行了封装,关键对象是ProxyFactory。下面介绍一下这个类的使用:

public static void main(String[] args) {
		UserService target = new UserService();
		//创建代理工厂
		ProxyFactory proxyFactory=new ProxyFactory();
		//设置代理对象
		proxyFactory.setTarget(target);
		//设置代理逻辑
		proxyFactory.addAdvice(new MethodBeforeAdvice() {
			@Override
			public void before(Method method, Object[] args, Object target) throws Throwable {
				System.out.println("before");
			}
		});
		//返回代理对象
		UserService proxy = (UserService) proxyFactory.getProxy();
		proxy.test();
	}

ProxyFactory默认是使用Cglib动态代理技术
在这里插入图片描述

我们稍微改一下,它就会使用jdk动态代理技术

public static void main(String[] args) {
		UserService target = new UserService();
		//创建代理工厂
		ProxyFactory proxyFactory=new ProxyFactory();
		//设置代理对象
		proxyFactory.setTarget(target);
		//设置接口
		proxyFactory.setInterfaces(Aservice.class);
		//设置代理逻辑
		proxyFactory.addAdvice(new MethodBeforeAdvice() {
			@Override
			public void before(Method method, Object[] args, Object target) throws Throwable {
				System.out.println("before");
			}
		});
		//返回代理对象
		Aservice proxy = (Aservice) proxyFactory.getProxy();
		proxy.test();
	}

在这里插入图片描述
此时我们可以发现,我们压根就不需要了解我们到底使用的是哪种代理模式,我们只需要知道通过这个代理工厂我们就可以拿到我们所需要的代理对象。

关于通知(Advice)的逻辑我们上面只使用了MethodBeforeAdvice,即方法执行前被调用,当然还有很多种,这里我们先稍微了解一下:

  • AfterReturningAdvice:方法执行完之后执行的代理逻辑
  • ThrowsAdvice:被代理方法执行抛异常之后执行的代理逻辑(这个接口没有提供重写方法,我们可以自己写方法逻辑,关键点是方法中的参数要和抛出的异常匹配才会调用这个方法)

在这里插入图片描述

  • MethodInterceptor(环绕通知):这个灵活性就很强我们可以收到去实现自己的代理逻辑下面用代码介绍一下这个通知的使用
public class Test {

	public static void main(String[] args) {
		UserService target = new UserService();
		//创建代理工厂
		ProxyFactory proxyFactory=new ProxyFactory();
		//设置代理对象
		proxyFactory.setTarget(target);
		//设置接口
		proxyFactory.setInterfaces(Aservice.class);
		//设置代理逻辑
		proxyFactory.addAdvice(new MethodInterceptor() {
			@Nullable
			@Override
			public Object invoke(@NotNull MethodInvocation invocation) throws Throwable {
				System.out.println("before1");
				Object proceed = invocation.proceed();
				System.out.println("after1");
				return null;
			}
	    });
		proxyFactory.addAdvice(new MethodInterceptor() {
			@Nullable
			@Override
			public Object invoke(@NotNull MethodInvocation invocation) throws Throwable {
				System.out.println("before2");
				Object proceed = invocation.proceed();
				System.out.println("after2");
				return null;
			}
		});
		//返回代理对象
		Aservice proxy = (Aservice) proxyFactory.getProxy();
		proxy.test();
	}
}

上面代码我给UserService加了两段代理逻辑,然后我们看一下输出:
在这里插入图片描述
分析输出我们大致能猜想出上面代理逻辑的执行顺序:

  1. 执行顺序是按照添加顺序来的
  2. 在执行前面一个代理逻辑的Object proceed = invocation.proceed();时候,它里面会执行后面添加的代理逻辑,就像一个洋葱一样,后面的代理逻辑被前面的代理逻辑包住,这也是环绕通知这一名字的由来。

还是回到上面提到的一个问题,如果我们需要一些方法被代理,而其它方法不被代理或被其它代理逻辑代理,那么在AOP中该怎么做?其实就是AOP中的切点:

public static void main(String[] args) {
		UserService target = new UserService();
		//创建代理工厂
		ProxyFactory proxyFactory=new ProxyFactory();
		//设置代理对象
		proxyFactory.setTarget(target);
		//设置接口
		proxyFactory.setInterfaces(Aservice.class);
		//添加切点
		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, Object target) throws Throwable {
						System.out.println("before test ");
					}
				};
			}

			@Override
			public boolean isPerInstance() {
				return false;
			}
		});
		//添加切点
		proxyFactory.addAdvisor(new PointcutAdvisor() {
			//获得切点
			@Override
			public Pointcut getPointcut() {
				return new StaticMethodMatcherPointcut() {
					@Override
					public boolean matches(Method method, Class<?> targetClass) {
						return method.getName().equals("a");
					}
				};
			}

			@Override
			public Advice getAdvice() {
				return new MethodBeforeAdvice() {
					@Override
					public void before(Method method, Object[] args, Object target) throws Throwable {
						System.out.println("before a ");
					}
				};
			}

			@Override
			public boolean isPerInstance() {
				return false;
			}
		});
		//返回代理对象
		Aservice proxy = (Aservice) proxyFactory.getProxy();
		proxy.test();
		proxy.a();
	}

在这里插入图片描述

Advisor=切点+通知

2. Spring AOP使用

前面介绍了ProxyFactory来创建代理对象的过程,但前面我们始终没有和Spring联系起来,都是我们收动的在创建自己的代理对象,添加自己的代理逻辑,这一部分我们将Spring和AOP紧密联系起来进行分析。

  • 方式一:

使用FactoryBean将代理对象作为一个bean存到AOP容器中,底层类型是ProxyFactoryBean

    @Bean
	public ProxyFactoryBean userService(){
		ProxyFactoryBean proxyFactoryBean=new ProxyFactoryBean();
		proxyFactoryBean.addAdvice(new MethodBeforeAdvice() {
			@Override
			public void before(Method method, Object[] args, Object target) throws Throwable {
				System.out.println("before");
			}
		});
		proxyFactoryBean.setTarget(new UserService());
		return proxyFactoryBean;
	}

在这里插入图片描述

  • 方式二

先说需求,如果我们要被代理的对象UserService是一个Bean,然后我们的代理逻辑也是一个Bean,那么怎么将这两个bean联系起来,生成UserService的代理对象。首先我们先将通知(Advice)封装成一个bean。

@Component
public class Aservice implements MethodBeforeAdvice {

	@Override
	public void before(Method method, Object[] args, Object target) throws Throwable {
		System.out.println("方法执行前的逻辑");
	}
}

@Component
public class UserService {


	public void test() {
		System.out.println("test");
	}

	public void a() {
		System.out.println("a");
	}
}

要达到上面的需求我们要使用到一个Bean,为BeanNameAutoProxyCreator

	@Bean
	public BeanNameAutoProxyCreator beanNameAutoProxyCreator(){
		BeanNameAutoProxyCreator beanNameAutoProxyCreator=new BeanNameAutoProxyCreator();
		//要被代理的Bean的名称前缀
		beanNameAutoProxyCreator.setBeanNames("User*");
		//设置代理逻辑的bean
		beanNameAutoProxyCreator.setInterceptorNames("AService");
		return beanNameAutoProxyCreator;
	}

其实BeanNameAutoProxyCreator本质上就是一个BeanPostProcessor。通过BeanNameAutoProxyCreator可以对批量的Bean进行AOP,并且指定了代理逻辑,指定了一个 InterceptorName,也就是一个Advise,前提条件是这个Advise也得是一个Bean,这样Spring才能找到的,但是BeanNameAutoProxyCreator的缺点很明显,它只能根据beanName来指定想要代理 的Bean。

  • 方式三

除了使用BeanNameAutoProxyCreator,还可以使用DefaultAdvisorAutoProxyCreator

@ComponentScan("com.zhouyu")
@EnableScheduling
@PropertySource("classpath:spring.properties")
@EnableTransactionManagement
@Configuration
public class AppConfig {
	@Bean
	public DefaultPointcutAdvisor defaultPointcutAdvisor(){
		NameMatchMethodPointcut pointcut = new NameMatchMethodPointcut();
		pointcut.addMethodName("test");
		DefaultPointcutAdvisor defaultPointcutAdvisor = new DefaultPointcutAdvisor();
		defaultPointcutAdvisor.setPointcut(pointcut);
		defaultPointcutAdvisor.setAdvice(new AService());
		return defaultPointcutAdvisor;
	}
	@Bean
	public DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator() {
		DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator = new
				DefaultAdvisorAutoProxyCreator();
		return defaultAdvisorAutoProxyCreator;
	}
}

上面DefaultAdvisorAutoProxyCreator也是一个BeanPostProcessor在bean的生命周期中,调用DefaultAdvisorAutoProxyCreator,然后判断当前创建bean有没有test方法,如果有就执行AService代理逻辑。

OOP表示面向对象编程,是一种编程思想,AOP表示面向切面编程,也是一种编程思想,而我们上 面所描述的就是Spring为了让程序员更加方便的做到面向切面编程所提供的技术支持,换句话说,就 是Spring提供了一套机制,可以让我们更加容易的来进行AOP,所以这套机制我们也可以称之为 Spring AOP。但是值得注意的是,上面所提供的注解的方式来定义Pointcut和Advice,Spring并不是首创,首创是 AspectJ,而且也不仅仅只有Spring提供了一套机制来支持AOP,还有比如 JBoss 4.0、aspectwerkz 等技术都提供了对于AOP的支持。而刚刚说的注解的方式,Spring是依赖了AspectJ的,或者说, Spring是直接把AspectJ中所定义的那些注解直接拿过来用,自己没有再重复定义了,不过也仅仅只 是把注解的定义赋值过来了,每个注解具体底层是怎么解析的,还是Spring自己做的,所以我们在用 Spring时,如果你想用@Before、@Around等注解,是需要单独引入aspecj相关jar包的。

意思是,AOP中的这些概念不是Spring特有的,不幸的是,AOP中的概念不是特别直观的,但是, 如果Spring重新定义自己的那可能会导致更加混乱

  1. Aspect:表示切面,比如被@Aspect注解的类就是切面,可以在切面中去定义Pointcut、 Advice等等
  2. Join point:表示连接点,表示一个程序在执行过程中的一个点,比如一个方法的执行,比如一 个异常的处理,在Spring AOP中,一个连接点通常表示一个方法的执行。
  3. Advice:表示通知,表示在一个特定连接点上所采取的动作。Advice分为不同的类型,后面详 细讨论,在很多AOP框架中,包括Spring,会用Interceptor拦截器来实现Advice,并且在连接 点周围维护一个Interceptor链
  4. Pointcut:表示切点,用来匹配一个或多个连接点,Advice与切点表达式是关联在一起的, Advice将会执行在和切点表达式所匹配的连接点上
  5. Introduction:可以使用@DeclareParents来给所匹配的类添加一个接口,并指定一个默认实现
  6. Target object:目标对象,被代理对象
  7. AOP proxy:表示代理工厂,用来创建代理对象的,在Spring Framework中,要么是JDK动态 代理,要么是CGLIB代理
  8. Weaving:表示织入,表示创建代理对象的动作,这个动作可以发生在编译时期(比如 Aspejctj),或者运行时,比如Spring AOP

你可能感兴趣的:(重温Spring源码系列,spring,java,后端)