AOP的功能分析及设计

Posted 踩踩踩从踩

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了AOP的功能分析及设计相关的知识,希望对你有一定的参考价值。

IOC及DI功能分析与设计_踩踩踩从踩的博客-CSDN博客

前言

本篇文章会基于在上篇文章 IOC及DI的功能设计的基础上,继续实现对bean的增强,来源于对 beanMap 会放置到容器中,做一个功能上的增强,其中最大一个增强也就是本篇文章说的AOP功能的增强。

AOP分析

AOP是 Aspect Oriented Programming面向切面编程,在不改变类的代码的情况下,对类方法进行功能增强。    利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的 耦合度降低,提高程序的可重用性,同时提高了开发的效率。
AOP框架要做的功能:
AOP框架中要向使用用户提供AOP功能,让用户可以通过AOP技术实现对类方法进行功能增强。
这里就是和代理模式 对功能增强具体的实现把,也是对代理类进行控制。
会编译成二进制的代理类。

advice   通知,增强的功能   
Join points 连接点,可选的方法点 
Pointcut    切入点,选择切入的方法点  
Aspect   切面,选择的(多个)方法点 + 增强的功能  
Introduction  引入 :添加新的方法、属性到已存在的类中,就叫引入
Weaving     织入(编织):不改原类的代码,加入功能增强       
这些都需要 在aop框架中需要涉及到的。
根据AOP的定义,得出AOP需要提供的功能特性

advice 和 pointcuts 构成切面  。

Join points 连接点:可以被选择来进行增强的方法点。

Pointcut 切入点:选择增强的方法 指点在那个类进行增强 选择在这里切入进行增强。

特点 

 Advice、Pointcut、Weaving各自有什么特点

Advice
  • 1 用户性:由用户提供增强功能逻辑代码
  • 2 变化的:不同的增强需求,会有不同的逻辑
  • 3 可选时机:可选择在方法功能前、后、异常时进行功能增强
  • 4 多重的:同一个切入点上可以有多重增强
Pointcut
  • 1 用户性:由用户来指定
  • 2 变化的:用户可灵活指定
  • 3 多点性:用户可以选择在多个点上进行增强
Weaving
  • 1 无侵入性,不改原类代码
  • 2 在AOP框架中实现

AOP 设计

我们如何能认识用户提供的东西?用户在我们写好框架后使用我们的框架。
  如何让我们的代码隔绝用户提供的多变?
我们定义一套标准接口,用户通过实现接口来提供他们不同的逻辑。
重要设计原则:如何应对变化,面向接口编程!
这就是怎么来隔绝变化的情况。

Advice设计

定义Advice接口  

Advice的特点:可选时机,可选择在方法功能前、后、异常时进行功能增强
1 有的advice是在方法执行前进行增强; —— 前置增强
2 有的是在方法执行后进行增强; —— 后置增强
3 有的会是之前、之后都进行增强; —— 环绕增强
4 有的则只是在方法执行抛出异常时进行异常增强处理; —— 异常处理增强
对于advice来说  为了不同类型的功能增强。 而且需要根据 不同 的 阶段 增强  因此要拆分开为几种接口,而且都统一继承公共的advice接口。

前置增强

前置增强:在方法执行前进行增强。
目的是对方法进行增强,应该需要的是方法相关的信息;我们使
用它时,能给入它的好像也只有当前要执行方法的信息。
所需要的参数:
  • 方法本身 Method
  • 方法所属的对象 Object
  • 方法的参数 Object[]
在方法执行前进行增强,不需要返回值!

后置增强

在方法执行后进行增强  
所需要的参数:
  • 方法本身  Method
  • 方法所属的对象 Object
  • 方法的参数 Object[]
  • 方法的返回值 Object
在方法执行后进行增强,不需要返回值。

环绕增强

环绕增强:包裹方法进行增强。
它需要什么参数
  • 方法本身 Method
  • 方法所属的对象 Object
  • 方法的参数 Object[]
方法被它包裹,也即方法的将由它来执行,它需要返回方法的返回值。 Object

Advice设计

经前面的分析,我们一共需要定义三个方法,分三个接口,还可通过类型来区分不同的Advice

/**
 * 后置增强接口
 */
public interface AfterReturningAdvice extends Advice 
    /**
     * 方法执行完毕后的增强接口
     * 
     * @param returnValue 方法执行完后的返回值
     * @param method    被执行的方法
     * @param args      方法执行参数
     * @param target    执行方法的对象
     */
    void afterReturning(Object returnValue, Method method, Object[] args, Object target);

 在spring中引用的就是在aspectj中的。

Pointcut

Pointcut的特点
  • 1 用户性:由用户来指定
  • 2 变化的:用户可灵活指定
  • 3 多点性:用户可以选择在多个点上进行增强
这里主要涉及到的针对多点性,如何去应对这点。
其实就是一个完整的方法签名
com.dn.spring.aop.Girl.dbj(Boy,Time)
com.dn.spring.aop.Girl.dbj(Boy,Girl,Time)
如何做到多点性,灵活性?在一个描述中指定一类方法?
  • 某个包下的某个类的某个方法
  • 某个包下的所有类中所有方法
  • 某个包下的所有类中的do开头的方法
  • 某个包下的以service结尾的类的中的do开头的方法
  • 某个包下的及其子包下的以service结尾的类的中的do开头的方法

每部分的要求是怎样的

  • 包名:有父子特点,要能模糊匹配
  • 类名:要能模糊匹配
  • 方法:要能模糊匹配
  • 参数类型:参数可以多个
匹配类,匹配方法 
而实现这种的方式,可以使用到的表达式
正则表达式是可以的。AspectJ本就是切面编程组件,也是可以的。

AspectJ官网:

The AspectJ Project | The Eclipse Foundation

其实针对
这又是一个要支持可多变的问题,像通知一样,我们来定义一套标准接口,定义好基本行为,面向接口编程,屏蔽掉具体的实现。

实现

public interface Pointcut 
	/**
	 * 匹配类
	 * @param targetClass 将被匹配的目标类
	 * @return	true,表示匹配规则;否则返回false。
	 */
	boolean matchsClass(Class<?> targetClass);

	/**
	 * 匹配方法
	 * @param method 将要被匹配的方法
	 * @param targetClass 将要被匹配的目标类
	 * @return true,表示匹配规则;否则返回false。
	 */
	boolean matchsMethod(Method method, Class<?> targetClass);

在实现时,AspectJExpressionPointcut  上需要实现 pointcut 

  • 引入AspectJ的jar
<dependency>
    <groupId>org.aspectj</groupId>
    <artifactId>aspectjweaver</artifactId>
    <version>1.9.1</version>
</dependency>
  • 掌握Aspectj的api使用,我们只使用它的切点表达式解析匹配部分。

        入口:org.aspectj.weaver.tools.PointcutParser 获得切点解析器;

PointcutParser pp = PointcutParser
.getPointcutParserSupportingAllPrimitivesAndUsingContextClassloaderForResolution();

 解析表达式,得到 org.aspectj.weaver.tools.PointcutExpression

PointcutExpression pe = pp.parsePointcutExpression("execution(* com.dn.spring.samples.*.set*(..))");
用PointcutExpression 匹配类,不可靠。没关系,通过方法匹配来正确匹配。
pe.couldMatchJoinPointsInType(ABean.class)
用PointcutExpression 匹配方法,可靠。
Class<?> cl = ABean.class;
Method aMethod = cl.getMethod("doSomthing", null);
ShadowMatch sm = pe.matchesMethodExecution(aMethod);
System.out.println(sm.alwaysMatches());

最后得到的

public class AspectJExpressionPointcut implements Pointcut 
	// 先获得切点解析器
	private static PointcutParser pp = PointcutParser
			.getPointcutParserSupportingAllPrimitivesAndUsingContextClassloaderForResolution();
	
	// 切点表达式的字符串形式
	private String expression;
	
	// aspectj中的切点表达式实例pe
	private PointcutExpression pe;

	public AspectJExpressionPointcut(String expression) 
		super();
		this.expression = expression;
		pe = pp.parsePointcutExpression(expression);
	

	@Override
	public boolean matchsClass(Class<?> targetClass) 
		return pe.couldMatchJoinPointsInType(targetClass);
	

	@Override
	public boolean matchsMethod(Method method, Class<?> targetClass) 
		ShadowMatch sm = pe.matchesMethodExecution(method);
		return sm.alwaysMatches();
	

	public String getExpression() 
		return expression;
	

都需要配置成一个bean ,给

 Aspect

为用户提供更简单的外观,Advisor(通知者)组合Advice和Pointcut


public interface Advisor 

    String getAdviceBeanName();
    String getExpression();
 基于切入点的

当用户使用AspectJ表达式来指定他的切入点时,就用它。只需指定adviceBeanName、expression

Weaving

这部分 要做的是怎么将写的增强功能 织入到具体的某个bean上面。

织入分析

将用户提供的增强功能加到指定的方法上。 这是框架上需要做的,什么时候将功能织入到类上面去。
创建bean实例的时候,在bean初始化后,再对其进行增强。
如何确定这个,怎么增强。  
对bean类及方法挨个匹配用户指定的切面,如果有匹配的切面就是要增强。
包括如何增强的通知,利用代理进行织入功能增强。
bean增强,在beanfactory中

 aop框架如何代理增强。代理对象,bean是否增强。 返回的应该是代理实例,用户在那里去注册切面,也不能在beanfactory中去注册切面。 判断匹配、织入的逻辑 这里 这些肯定都不能在beanfactory中, 太累赘了。

让beanfactory去处理代理对象就行,为了灵活扩展框架 ,在beanfactory中去扩展,是非常不好的。

肯定大家的想法都是在beanfactory中相关扩展。

这里   也是利用 灵活扩展,设计能让我们一次写好BeanFactory后,不改代码,就可以灵活扩展

加入注册机制。 扩展点,对不同的阶段

 流转过程中, spring中 就利用这样进行扩展开。

不同扩展点,进行拆分开,

针对bean初始化前后,做增强处理, advisorautoproxycreator就是进行增强的处理。

 

/**
 * 后置处理器。
 * Bean实例化完毕后及依赖注入完成后触发。
 */
public interface BeanPostProcessor 
	/**
	 * bean初始化前的处理
	 * @param bean
	 * @param beanName
	 * @return
	 * @throws Exception
	 */
	default Object postProcessBeforeInitialization(Object bean, String beanName) throws Exception 
		return bean;
	

	/**
	 * bean初始化后的处理
	 * @param bean
	 * @param beanName
	 * @return
	 * @throws Exception
	 */
	default Object postProcessAfterInitialization(Object bean, String beanName) throws Exception 
		return bean;
	

将创建初始化的object 增强。
默认不做增强处理,

 

并且 需要 在beanfactory上面进行注入,这个需要 在 DefaultBeanFactory 中实现,


    @Override
    public void registerBeanPostProcessor(BeanPostProcessor bpp) 
        beanPostProcessors.add(bpp);
        if (bpp instanceof BeanFactoryAware) 
            ((BeanFactoryAware) bpp).setBeanFactory(this);
        
    
利用观察者模式,达到进行初始化的处理, 进行注册进去,

 

如何达到对对象进行增强处理, 判断bean是否需要进行功能增强处理。

 获取bean的类及所有方法

 利用反射去  就可以判断方法是否需要进行增强。

private List<Advisor> getMatchedAdvisors(Object bean, String beanName) 
		if (CollectionUtils.isEmpty(advisors)) 
			return null;
		

		// 得到类、所有的方法
		Class<?> beanClass = bean.getClass();
		List<Method> allMethods = this.getAllMethodForClass(beanClass);

		// 存放匹配的Advisor的list
		List<Advisor> matchAdvisors = new ArrayList<>();
		// 遍历Advisor来找匹配的
		for (Advisor ad : this.advisors) 
			if (ad instanceof PointcutAdvisor) 
				if (isPointcutMatchBean((PointcutAdvisor) ad, beanClass, allMethods)) 
					matchAdvisors.add(ad);
				
			
		

		return matchAdvisors;
	

 判断是否进行功能增强的。  并且如何找到继承的所有方法。 匹配  判断 是否是pointcutadvisor的

	private List<Method> getAllMethodForClass(Class<?> beanClass) 
		List<Method> allMethods = new LinkedList<>();
		Set<Class<?>> classes = new LinkedHashSet<>(ClassUtils.getAllInterfacesForClassAsSet(beanClass));
		classes.add(beanClass);
		for (Class<?> clazz : classes) 
			// 通过spring framework提供的工具类找出所有方法,包括从父类继承而来的方法
			Method[] methods = ReflectionUtils.getAllDeclaredMethods(clazz);
			for (Method m : methods) 
				allMethods.add(m);
			
		

		return allMethods;
	
判断类是否匹配的。
切面进行增强,创建 增强bean.

	/**
	 * 判断指定类中的方法是否有符合切点规则的。
	 * @param pa	方面信息,带有切点对象
	 * @param beanClass	指定的类
	 * @param methods	指定类中的所有方法
	 * @return
	 */
	private boolean isPointcutMatchBean(PointcutAdvisor pa, Class<?> beanClass, List<Method> methods) 
		Pointcut p = pa.getPointcut();

		// 首先判断类是否匹配
		if (!p.matchsClass(beanClass)) 
			return false;
		

		// 再判断是否有方法匹配
		for (Method method : methods) 
			if (p.matchsMethod(method, beanClass)) 
				return true;
			
		
		return false;
	

代理增强。

 判断如何代理生成对象,增强的方法。cglib代理类,把相同的地方抽象出来。

在运行时,对接口创建代理对象

将 代理统一封装成一个aop,把架子搭起来。 

匹配的切面 

/**
 * AOP代理接口,用来创建获得代理对象
 */
public interface AopProxy 
	/**
	 * Create a new proxy object.
	 * <p>
	 * Uses the AopProxy's default class loader (if necessary for proxy
	 * creation): usually, the thread context class loader.
	 * 
	 * @return the new proxy object (never @code null)
	 * @see Thread#getContextClassLoader()
	 */
	Object getProxy();

	/**
	 * Create a new proxy object.
	 * <p>
	 * Uses the given class loader (if necessary for proxy creation).
	 * @code null will simply be passed down and thus lead to the low-level
	 * proxy facility's default, which is usually different from the default
	 * chosen by the AopProxy implementation's @link #getProxy() method.
	 * 
	 * @param classLoader
	 *            the class loader to create the proxy with (or @code null for
	 *            the low-level proxy facility's default)
	 * @return the new proxy object (never @code null)
	 */
	Object getProxy(ClassLoader classLoader);

/**
 * AOP代理接口的工厂模式接口
 */
public interface AopProxyFactory 
	/**
	 * 根据参数获得AOP代理接口的实现
	 * @param bean
	 * @param beanName
	 * @param matchAdvisors
	 * @param beanFactory
	 * @return
	 * @throws Exception
	 */
	AopProxy createAopProxy(Object bean, String beanName, List<Advisor> matchAdvisors, BeanFactory beanFactory)
			throws Exception;

	/**
	 * 获得默认的AopProxyFactory实例
	 * 
	 * @return AopProxyFactory
	 */
	static AopProxyFactory getDefaultAopProxyFactory() 
		return new DefaultAopProxyFactory();
	
	/**
	 * 通过AopProxyFactory工厂去完成选择、和创建代理对象的工作。
	 * @param bean
	 * @param beanName
	 * @param matchAdvisors
	 * @return
	 * @throws Exception
	 */
	private Object createProxy(Object bean, String beanName, List<Advisor> matchAdvisors) throws Exception 
		// 默认的代理工厂实现中获得代理工厂实现
		return AopProxyFactory.getDefaultAopProxyFactory()
				// 根据参数信息,选择创建代理工厂具体实现
				.createAopProxy(bean, beanName, matchAdvisors, beanFactory)
				// 从选择的代理工厂中,获得代理对象
				.getProxy();
	

这都是尽量不动源码而使用的方法。

获取默认代理的方式。

public class DefaultAopProxyFactory implements AopProxyFactory 

	@Override
	public AopProxy createAopProxy(Object bean, String beanName, List<Advisor> matchAdvisors, BeanFactory beanFactory)
			throws Exception 
		// 是该用jdk动态代理还是cglib?
		if (shouldUseJDKDynamicProxy(bean, beanName)) 
			return new JdkDynamicAopProxy(beanName, bean, matchAdvisors, beanFactory);
		 else 
			return new CglibDynamicAopProxy(beanName, bean, matchAdvisors, beanFactory);
		
	

	private boolean shouldUseJDKDynamicProxy(Object bean, String beanName) 
		// 如何判断?
		// 这样可以吗:有实现接口就用JDK,没有就用cglib?
		// 请同学们在读spring的源码时看spring中如何来判断的
		return false;
	

在spring中使用到的代理模式,就采用这种方式。 

这里将需要功能进行数据增强处理工具。

/**
 * AOP代理工具类
 */
public class AopProxyUtils 

	/**
	 * 对方法应用advices增强,获得最终返回结果。
	 * 
	 * @param target bean对象,需要被增强的对象
	 * @param method	需要被增强的方法
	 * @param args	增强方法的参数
	 * @param matchAdvisors	匹配到的切面
	 * @param proxy	bean对象功能增强后的代理对象
	 * @param beanFactory ioc容器
	 * @return 方法增强后的返回结果
	 * @throws Throwable
	 */
	public static Object applyAdvices(Object target, Method method, Object[] args, List<Advisor> matchAdvisors,
                                      Object proxy, BeanFactory beanFactory) throws Throwable 
		// 这里要做什么?
		// 1、获取要对当前方法进行增强的advice Bean列表
		List<Object> advices = AopProxyUtils.getShouldApplyAdvices(target.getClass(), method, matchAdvisors,
				beanFactory);
		// 2、如有增强的advice,责任链式增强执行
		if (CollectionUtils.isEmpty(advices)) 
			return method.invoke(target, args);
		 else 
			// 责任链式执行增强
			AopAdviceChainInvocation chain = new AopAdviceChainInvocation(proxy, target, method, args, advices);
			return chain.invoke();
		
	

	/**
	 * 获取与方法匹配的切面的advices Bean对象列表
	 * 
	 * @param beanClass
	 * @param method
	 * @param matchAdvisors
	 * @param beanFactory
	 * @return
	 * @throws Exception
	 */
	public static List<Object> getShouldApplyAdvices(Class<?> beanClass, Method method, List<Advisor> matchAdvisors,
                                                     BeanFactory beanFactory) throws Throwable 
		if (CollectionUtils.isEmpty(matchAdvisors)) 
			return null;
		
		List<Object> advices = new ArrayList<>();
		for (Advisor ad : matchAdvisors) 
			if (ad instanceof PointcutAdvisor) 
				if (((PointcutAdvisor) ad).getPointcut().matchsMethod(method, beanClass)) 
					advices.add(beanFactory.getBean(ad.getAdviceBeanName()));
				
			
		

		return advices;
	


 这个作为一个钩子,你需要那个 例如 beanfactory 就实现beanfactoryaware就会将beanfactory给你。持有起来。 这是spring中会拿到的。

解决强一致性的问题, 在aop中 spring框架做这个是解决的很好的。

以上是关于AOP的功能分析及设计的主要内容,如果未能解决你的问题,请参考以下文章

spring配置功能分析以及设计

spring配置功能分析以及设计

JAVA之AOP

基于AOP和Redis实现对接口调用情况的接口及IP限流

Spring源码分析之AOP

转载Java JDK 动态代理(AOP)使用及实现原理分析