【spring源码系列-AOP原理探索一】

AOP概述

spring的核心功能一共两个IOC和AOP,AOP(Aspect orient programming) 直译过来就是面向切面工程,AOP是面向对象编程的一个补充。

AOP的作用

AOP可以拦截指定的方法并且对方法增强,而且无需侵入到业务代码中,使业务与非业务处理逻辑分离,比如Spring的事务,通过事务的注解配置,Spring会自动在业务方法中开启、提交业务,并且在业务处理失败时,执行相应的回滚策略。
AOP 常用的场景主要包括日志、事务、监测、权限。

AOP实现机制

spring里面是通过jdk动态代理、动态字节码增强(cglib)来实现aop的。

动态代理

jdk动态代理,可以在代码运行期间,给接口生成一个代理对象,我们可以将我们想要添加的功能(例如日志、事务、监测、权限)等功能在不影响功能的前提下,添加到代码里面。这么说可能有点抽象,我们代码实战一下。

下面代码里有一个接口CodingService,一个接口接口实现类,最后就是我们的动态代理实现类。

public interface CodingService {
    public void createBug();
    public void modifyBug();
}
public class CodingServiceImpl implements CodingService {
    @Override
    public void createBug() {
        System.out.println("写bug");
    }
    @Override
    public void modifyBug() {
        System.out.println("改bug");
    }
}
public class JDKProxyFactory implements InvocationHandler {
    private Object target;

    public JDKProxyFactory(Object target) {
        super();
        this.target = target;
    }

    // 创建代理对象
    public Object createProxy() {
        // 1.得到目标对象的类加载器
        ClassLoader classLoader = target.getClass().getClassLoader();
        // 2.得到目标对象的实现接口
        Class<?>[] interfaces = target.getClass().getInterfaces();
        // 3.第三个参数需要一个实现invocationHandler接口的对象
        Object newProxyInstance = Proxy.newProxyInstance(classLoader, interfaces, this);
        return newProxyInstance;
    }


    // 第一个参数:代理对象.一般不使用;第二个参数:需要增强的方法;第三个参数:方法中的参数
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("这是增强方法前......");
        Object invoke = method.invoke(target, args);
        System.out.println("这是增强方法后......");
        return invoke;
    }

    public static void main(String[] args) {
        // 1.创建对象
        CodingServiceImpl codingService = new CodingServiceImpl();
        // 2.创建代理对象
        JDKProxyFactory proxy = new JDKProxyFactory(codingService);
        // 3.调用代理对象的增强方法,得到增强后的对象
        CodingService createProxy = (CodingService) proxy.createProxy();
        createProxy.createBug();
    }

}
运行结果:
这是增强方法前......
写bug
这是增强方法后......

我们可以看到,将接口进行增强实现并创建出来后,调用的方法会自动实现invoke方法里的逻辑。这也就是动态代理。
实现jdk动态代理有几个注意的地方。
Interface:对于JDK Proxy,业务类是需要一个Interface的,这是一个缺陷;
Proxy:Proxy类是动态产生的,这个类在调用Proxy.newProxyInstance()方法之后,产生一个Proxy类的实力。实际上,这个Proxy类也是存在的,不仅仅是类的实例,这个Proxy类可以保存在硬盘上;
Method:对于业务委托类的每个方法,现在Proxy类里面都不用静态显示出来。
InvocationHandler:这个类在业务委托类执行时,会先调用invoke方法。invoke方法在执行想要的代理操作,可以实现对业务方法的再包装。

cglib动态字节码增强

实现的效果与jdk动态代理功能一样,但是实现原理略有不同,如名字说的,cglib是对class文件进行一个修改,从而实现增强效果。代码示例如下:

public class CglibProxyFactory implements MethodInterceptor {
    //得到目标对象
    private Object target;

    //使用构造方法传递目标对象
    public CglibProxyFactory(Object target) {
        super();
        this.target = target;
    }

    //创建代理对象
    public Object createProxy(){
        //1.创建Enhancer
        Enhancer enhancer = new Enhancer();
        //2.传递目标对象的class
        enhancer.setSuperclass(target.getClass());
        //3.设置回调操作
        enhancer.setCallback(this);

        return enhancer.create();
    }


    //参数一:代理对象;参数二:需要增强的方法;参数三:需要增强方法的参数;参数四:需要增强的方法的代理
    public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
        System.out.println("这是增强方法前......");
        Object invoke = methodProxy.invoke(target, args);
        System.out.println("这是增强方法后......");
        return invoke;
    }

    public static void main(String[] args) {
        // 1.创建对象
        FoodServiceImpl foodService = new FoodServiceImpl();
        // 2.创建代理对象
        CglibProxyFactory proxy = new CglibProxyFactory(foodService);
        // 3.调用代理对象的增强方法,得到增强后的对象
        FoodService createProxy = (FoodService) proxy.createProxy();
        createProxy.makeChicken();
    }
}


AOP核心类

(1)切面(Aspect)

定义 AOP 是针对某个统一的功能的,这个功能就叫做一个切面,比如用户登录功能或方法的统计日志,他们就各是一个切面。切面是由切点和通知组成的

(2)连接点(Join Point)

所有可能触发 AOP(拦截方法的点)就称为连接点

(3)切点(Pointcut)

切点的作用就是提供一组规则来匹配连接点,给满足规则的连接点添加通知,总的来说就是,定义 AOP 拦截的规则的

切点相当于保存了众多连接点的一个集合(如果把切点看成一个表,而连接点就是表中一条一条的数据)

(4)通知(Advice)

切面的工作就是通知

通知:规定了 AOP 执行的时机和执行的方法

Spring 切面类中,可以在方法上使用以下注解,会设置方法为通知方法,在满足条件后悔通知本方法进行调用

前置通知 @Before:通知方法会在目标方法调用之前执行
后置通知 @After:通知方法会在目标方法返回或者抛出异常后调用
返回之后通知 @AfterReturning:通知方法会在目标方法返回后调用
抛异常后通知:@AfterThrowing:通知方法会在目标方法爬出异常之后调用
环绕通知:@Around:通知包裹了被通知的方法,在被通知的方法通知之前和调用之后执行自定义的行为

举个例子:
这里的@Aspect注解的类就是一个切面,@Pointcut就是切入点,可以对指定类的指定方法进行设置。 而后所有的通知注解,例如最常用的@Around,就可以对先前设置的切入点进行操作。

@Aspect // 当前类是一个切面
@Component
public class UserAspect {
    // 定义一个切点(设置拦截规则)
    @Pointcut("execution(* com.example.gushi.controller.ConceptController.*(..))")
    public void pointcut() {
    }

    @Around("pointcut()")
    public void aroundAdvice(ProceedingJoinPoint proceedingJoinPoint) {
        System.out.println("before");
        try {
            proceedingJoinPoint.proceed();
        } catch (Throwable t) {
            t.printStackTrace();
        }
        System.out.println("after");
    }
    
    // 定义 pointcut 切点的前置通知
    @Before("pointcut()")
    public void doBefore() {
        System.out.println("执行前置通知");
    }

    // 后置通知
    @After("pointcut()")
    public void doAfter() {
        System.out.println("执行后置通知");
    }

    // 返回之后通知
    @AfterReturning("pointcut()")
    public void doAfterReturning() {
        System.out.println("执行返回之后通知");
    }

    // 抛出异常之后通知
    @AfterThrowing("pointcut()")
    public void doAfterThrowing() {
        System.out.println("执行抛出异常之后通知");
    }

}

AOP源码

前面我们讲过了IOC的整个流程,从applicationcontext到beanFactory整个bean生命周期的讲解,现在会过头来看这个aop应该就会好很多了。
一切化繁为简,aop本质就是通过jdk与cglib两种方法进行切面织入,spring做的就是在bean初始化的时候将其改造为ProxyFactory,来生成对应的动态代理方法,然后再匹配不同通知例如@Around,@Before的逻辑,就可以了。可能这样还是有点抽象,我们直接看代码。

AnnotationAwareAspectJAutoProxyCreator

我们先来看一个类AnnotationAwareAspectJAutoProxyCreator的继承结构
AnnotationAwareAspectJAutoProxyCreator结成结构
这个类实现了aware和BeanPostProcessor两个接口,我们之前按讲过aware是进行一个感知通过属性添加功能,而BeanPostProcessor则是插件,增加一些业务扩展点,而这里要实现的业务扩展点是InstantiationAwareBeanPostProcessor实现的。
实在哪里调用的呢?

调用位置

我们直接到AbstractAutowireCapableBeanFactory . createBean方法里看。这个方法在之前讲过是BeanFactory.getBean()后获取不到缓存后进行创建实例的实现方法。

protected Object createBean(String beanName, RootBeanDefinition mbd, @Nullable Object[] args){
//...省略
	Object bean = resolveBeforeInstantiation(beanName, mbdToUse);
	Object beanInstance = doCreateBean(beanName, mbdToUse, args);
	return beanInstance;
}

我们之前没有提到过resolveBeforeInstantiation,现在我们回过头来看这个方法,里面的主要 就是在这里进行了一个业务扩展。

if (targetType != null) {
	bean = applyBeanPostProcessorsBeforeInstantiation(targetType, beanName);
if (bean != null) {
	bean = applyBeanPostProcessorsAfterInitialization(bean, beanName);
}

如何处理?

从几个继承类里找到 postProcessor的扩展方法,看一下里面的业务逻辑。

public Object postProcessBeforeInstantiation(Class<?> beanClass, String beanName) {
		
			if (isInfrastructureClass(beanClass) || shouldSkip(beanClass, beanName)) {
				this.advisedBeans.put(cacheKey, Boolean.FALSE);
				return null;
			}
		}
		TargetSource targetSource = getCustomTargetSource(beanClass, beanName);
		if (targetSource != null) {
			if (StringUtils.hasLength(beanName)) {
				this.targetSourcedBeans.add(beanName);
			}
			Object[] specificInterceptors = getAdvicesAndAdvisorsForBean(beanClass, beanName, targetSource);
			Object proxy = createProxy(beanClass, beanName, specificInterceptors, targetSource);
			this.proxyTypes.put(cacheKey, proxy.getClass());
			return proxy;
		}

		return null;
}
//关键功能点
protected boolean shouldSkip(Class<?> beanClass, String beanName) {
		//找到所有通知
		List<Advisor> candidateAdvisors = findCandidateAdvisors();
		for (Advisor advisor : candidateAdvisors) {
			if (advisor instanceof AspectJPointcutAdvisor &&
					((AspectJPointcutAdvisor) advisor).getAspectName().equals(beanName)) {
				return true;
			}
		}
		return super.shouldSkip(beanClass, beanName);
	}
	//获取所有通知并进行缓存
protected List<Advisor> findCandidateAdvisors() {
		List<Advisor> advisors = super.findCandidateAdvisors();
		if (this.aspectJAdvisorsBuilder != null) {
			advisors.addAll(this.aspectJAdvisorsBuilder.buildAspectJAdvisors());
		}
		return advisors;
	}

为了防止你提前看懵,我这边还是要重复讲解一下,这里主要还是为创建代理对象进行准备工作, 比如:获取所有的@PointCut ,@Arount 通知 等方法信息,这样后面进行代理的时候才可以知道怎样进行。这里的List candidateAdvisors = findCandidateAdvisors();就是获取所有通知。 aspectJAdvisorsBuilder用构造者模式进行创建advice通知,主要还是通过反射获取属性,这里就不一一细讲,感兴趣的可以打断点自己进去看看。我们继续往下看

还记得我们之前讲的pointcut吗?
里面有一个重要的匹配机制就是 匹配类和匹配方法
主要实现接口时MethodMatcher以及ClassFilter (这里不过多讲解) 下面主要进行匹配到对应的方法,下面的findAdvisorsThatCanApply方法以及canApply都是在做匹配这件事

protected List<Advisor> findEligibleAdvisors(Class<?> beanClass, String beanName) {
		List<Advisor> candidateAdvisors = findCandidateAdvisors();
		// 
		List<Advisor> eligibleAdvisors = findAdvisorsThatCanApply(candidateAdvisors, beanClass, beanName);
		extendAdvisors(eligibleAdvisors);
		if (!eligibleAdvisors.isEmpty()) {
			eligibleAdvisors = sortAdvisors(eligibleAdvisors);
		}
		return eligibleAdvisors;
	}

public static List<Advisor> findAdvisorsThatCanApply(List<Advisor> candidateAdvisors, Class<?> clazz) {
		List<Advisor> eligibleAdvisors = new ArrayList<>();
		for (Advisor candidate : candidateAdvisors) {
			if (candidate instanceof IntroductionAdvisor && canApply(candidate, clazz)) {
				eligibleAdvisors.add(candidate);
			}
		}
		boolean hasIntroductions = !eligibleAdvisors.isEmpty();
		for (Advisor candidate : candidateAdvisors) {
			if (candidate instanceof IntroductionAdvisor) {
				// already processed
				continue;
			}
			if (canApply(candidate, clazz, hasIntroductions)) {
				eligibleAdvisors.add(candidate);
			}
		}
		return eligibleAdvisors;
	}
//查看是否能够匹配 classFilter match方法
public static boolean canApply(Advisor advisor, Class<?> targetClass, boolean hasIntroductions) {
		if (advisor instanceof IntroductionAdvisor) {
			return ((IntroductionAdvisor) advisor).getClassFilter().matches(targetClass);
		}
		else if (advisor instanceof PointcutAdvisor) {
			PointcutAdvisor pca = (PointcutAdvisor) advisor;
			return canApply(pca.getPointcut(), targetClass, hasIntroductions);
		}
		else {
			// It doesn't have a pointcut so we assume it applies.
			return true;
		}
	}
	//methodMatcher 匹配方法
public static boolean canApply(Pointcut pc, Class<?> targetClass, boolean hasIntroductions) {
		
		MethodMatcher methodMatcher = pc.getMethodMatcher();
		

		IntroductionAwareMethodMatcher introductionAwareMethodMatcher = null;
		if (methodMatcher instanceof IntroductionAwareMethodMatcher) {
			introductionAwareMethodMatcher = (IntroductionAwareMethodMatcher) methodMatcher;
		}

		Set<Class<?>> classes = new LinkedHashSet<>();
		if (!Proxy.isProxyClass(targetClass)) {
			classes.add(ClassUtils.getUserClass(targetClass));
		}
		classes.addAll(ClassUtils.getAllInterfacesForClassAsSet(targetClass));

		for (Class<?> clazz : classes) {
			Method[] methods = ReflectionUtils.getAllDeclaredMethods(clazz);
			for (Method method : methods) {
				if (introductionAwareMethodMatcher != null ?
						introductionAwareMethodMatcher.matches(method, targetClass, hasIntroductions) :
						methodMatcher.matches(method, targetClass)) {
					return true;
				}
			}
		}

		return false;
	}

匹配完之后,其实就已经做好了aop aspect的准备工作。获取了所有advice通知信息以及pointcut切入点位置,接下来就是创建对象了,我们下一期讲。

  • 13
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值