springAOP使用及原理分析

Posted 野生java研究僧

tags:

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

springAOP简介

AOP(Aspect Oriented Program):即面向切面编程

在面向切面编程的思想里面,把功能分为核心业务功能和周边功能

  • 所谓的核心业务,比如登陆,增加数据,删除数据都叫核心业务
  • 所谓的周边功能,比如性能统计,日志,事务管理等等

在SpringAOP中将这些周边功能称为切面,将核心业务逻辑与这些周边功能分别独立开始,然后将它们交织在一起就是AOP需要做的事情

AOP将业务模块锁公共调用的、非业务逻辑的功能封装起来;这样的好处?

一:所以这些非核心业务功能都集中在一起,而不是分散到多处代码中,便于减少系统的重复代码,降低模块间的耦合度,有利于维护

二:服务模块更简洁,因为它们只包含主要关注点或核心功能的代码,次要的非业务功能(周边功能)被转移到切面中

AOP当中的概念

连接点(JoinPoint):在程序执行过程中某个特定的点,比如类初始化前、类初始化后,方法调用前,方法调用后;

切点(Pointcut):所谓切点就是你所切取的类中的方法,比如你横切的这个类中有两个方法,那么这两个方法都是连接点,对这两个方法的定位就称之为切点;

增强(Advice):增强是织入到连接点上的一段程序,另外它还拥有连接点的相关信息;

目标对象(Target):增强逻辑的织入目标类,就是我的增强逻辑植入到什么位置;

引介(Introduction):一种特殊的增强,它可以为类添加一些属性喝方法;

织入(Weaving):织入就是讲增强逻辑添加到目标对象的过程;

代理(Proxy):一个类被AOP织入增强后,就会产生一个结果类,他是融合了原类和增强逻辑的代理类;

切面(Aspect):切面由切点和增强组成,他是横切逻辑定义和连接点定义的组成;

相关注解说明

  • @Apsect: 将当前类标识为一个切面;
  • @Pointcut: 公共切入点:使用方式 eg: @Before(value = “pointCut()”)
  • @Before:前置通知,就是在目标方法执行之前执行;
  • @AfterReturning:后置通知,方法退出时执行;
  • @AfterThrowing: 异常通知,有异常时该方法执行;
  • @After: 最终通知,无论什么情况都会执行;
  • @Afround: 环绕通知;在目标方法执行的前后进行执行

execution表达式说明:

eg: @Pointcut(value = “execution(* com.compass.aop.test…* .*(…))”)

  • 标识符 含义
  • execution() 表达式的主体
  • 第一个“*”符号 表示返回值的类型任意
  • com.smartplg.interview.test AOP所切的服务的包名,即,需要进行横切的业务类
  • 包名后面的“…” 表示当前包及子包
  • 第二个“*” 表示类名,*即所有类
  • .*(…) 表示任何方法名,括号表示参数,两个点表示任何参数类型

annotation: 匹配指定注解

@annotation(anno.RequiredAnnotationClass) 匹配有此注解[@RequiredAnnotationClass]描述的方法

简单易懂的图就是:

假设我要在用户登录后做日志记录,大致的流程图就是:

AOP基本使用

有了以上的前置知识,我们就可以使用spring的AOP功能进行编程了,相关的依赖自己进行百度导入,使用springBoot的话,引入依赖比较简单。我们使用注解的方式来完成AOP切面,xml太繁琐了,有兴趣的可以自己看下xml版本的。

1.定义一个配置类,定义扫描规则,开启AOP功能

@EnableAspectJAutoProxy
@Configuration // 配置类
@ComponentScan("com.compass.aop.test") // 标识扫描的路径
public class AppConfig 


2.定义一个登录接口

public interface UserService 
    /**
     * 登录接口 [模拟,真实的时候是需要连接数据库查询的]
     * @param username 
     * @param password
     * @return
     */
    String login(String username,String password);

3.对登录接口实现

@Service
public class UserServiceImpl implements UserService 

    @Log(title = "loginOperation") //此注解是自定义注解,表示是开启记录日志功能
    @Override
    public String login(String username, String password) 
        System.out.println("login 被执行...");
        // 打开此注释即可看到异常通知
	    // int age = 10/0;
        if ("admin".equals(username) && "123".equals(password))
            return "login success";
        
        return "login failed";
    


4.自定注解

@Target( ElementType.PARAMETER, ElementType.METHOD )
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface EnableLog
    public String value() default "yes";

4.定义切面 对login(String username, String password) 方法进行增强

@Aspect // 表示这是一个切面
@Component  // 将这个切面添加到spring容器中
public class LogAspect 

    // 公共切入点 使用方式 @Before(value = "pointCut()")
    @Pointcut(value = "execution(* com.compass.aop.test..*  .*(..))")
    private void pointCut() 
    

    // 前置通知
    @Before(value = "pointCut()")
    public void doBefore(JoinPoint joinPoint) 
        System.out.println("前置通知[execution]");
    
    
    
    // 后置通知 在指定注解下执行
    @AfterReturning(value = "@annotation(serviceLog)", returning = "result")
    public void doAfterReturning(JoinPoint joinPoint, EnableLog serviceLog, String result) 
        System.out.println("后置通知:"+result+" Log-value:"+serviceLog.value());
        // 如果我们注解标注的值为value 我们可以在此处记录用户的日志信息
        if (serviceLog.value())
            // 获取到方法参数的value 如果是真实环境我们就可以将日志记录到数据库或者文件中
            Object[] args = joinPoint.getArgs();
            System.out.println(args);
        
    

    // 最终通知
    @After(value = "pointCut()")
    public void doAfter(JoinPoint joinPoint) throws NoSuchMethodException 
        System.out.println("最终通知: ");

        //当前调用的方法签名
        Signature signature = joinPoint.getSignature();
        MethodSignature msg=(MethodSignature) signature;
        Object target = joinPoint.getTarget();
        //获取注解标注的方法
        Method method = target.getClass().getMethod(msg.getName(), msg.getParameterTypes());
        //通过方法获取注解
        EnableLog annotation = method.getAnnotation(EnableLog.class);
        //获取参数
        Object[] args = joinPoint.getArgs();

    

    // 异常通知
    @AfterThrowing(value = "pointCut()",throwing = "e")
    public void doAfterThrowing(JoinPoint joinPoint ,Throwable e) 
        System.out.println("异常通知 :"+e.getMessage());
    

    //环绕通知 注意要有ProceedingJoinPoint参数传入
    @Around(value = "pointCut()")
    public Object doAround(ProceedingJoinPoint point) throws Throwable 
        System.out.println("环绕通知..环绕前");
        // 执行目标方法 不写这行代码 目标方法不会被执行
        // 如果有返回值,一定要return出去,否则自己的业务方法得不到返回值
        Object result = point.proceed();
        System.out.println("环绕通知..环绕后");
        return result;
    



测试类

public class MyTest 
    public static void main(String[] args) 
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
        UserService userService = context.getBean( "userServiceImpl",UserService.class);
        String result = userService.login("admin", "123");
        System.out.println("登录结果:"+result);
    

注意点:

  • 环绕通知一定要调用 point.proceed() 方法进行执行目标方法,不然我们的目标方法不会被执行
  • 环绕通知如果有返回值,我们也需要获取到返回值,return出去,否则我们调用目标方法的时候得不到结果

AOP通知的执行顺序

我们来看下这些通知的一个指向流程:

正常的执行逻辑:

  1. 执行环绕通知的前置逻辑
  2. 执行前置通知
  3. 执行目标方法
  4. 执行后置通知
  5. 执行最终通知
  6. 执行环绕通知的后置逻辑

出现异常的执行逻辑:

  1. 执行环绕通知的前置逻辑
  2. 执行前置通知
  3. 执行目标方法
  4. 出现异常,执行异常通知,出现异常返回通知和环绕通知的后置逻辑就不会被执行了
  5. 执行最终通知

多个切面时的执行流程:

springAOP在真实开发中可以做什么?

我这里给大家举几个例子: 反正你就可以理解为,你可以在你的业务逻辑的任何方法执行的时候进行干扰,也就是增强,你可以获取到该方法运行时的参数,可以做一些具体的操作,具体怎么操作是由你的决定的,springAOP在执行方法的同时把控制器交给你,这也是很多框架的特点,在执行原有逻辑的基础上,给你留有接口,或者是能让你对执行过程可以进行干扰。

  1. 记录用户的操作记录
  2. 事务管理
  3. 防重复提交
  4. 做数据缓存
  5. 权限验证
  6. 数据校验

进阶-代理模式之JDK动态代理

代理模式有两种实现:

  • jdk动态代理:jdk原生API,jdk动态代理是由java内部的反射机制来实现的,[如果你实现了接口那么就是动态代理]
  • cglib代理:使用第三方jar包,cglib动态代理底层则是借助asm来实现,[ 如果没有实现接口是用的cglib代理 ]

空口无凭,看源码说话,这也就是我们可以实现接口,也可以不实现接口都能完成AOP切入功能的硬核所在

// 创建一个AOP代理
protected final synchronized AopProxy createAopProxy() 
	if (!this.active) 
			activate();
		
  		  // 先获取到代理工厂,让后再去创建代理对象
		return getAopProxyFactory().createAopProxy(this);


@Override
	public AopProxy createAopProxy(AdvisedSupport config) throws AopConfigException 
		if (!NativeDetector.inNativeImage() &&
				(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.");
			
            // 如果你的目标类是一个接口,那么使用动态代理
			if (targetClass.isInterface() || Proxy.isProxyClass(targetClass)) 
                   // 创建一个动态代理对象返回
				return new JdkDynamicAopProxy(config);
			
           	//如果不是的话,那就使用cglb进行创建代理对象
			return new ObjenesisCglibAopProxy(config);
		
		else 
			return new JdkDynamicAopProxy(config);
		
	

JDK动态代理的执行方式:

public class JDKDynamicProxy 

    /**
     * 使用jdk的反射机制创建出一个代理对象
     * @param target 需要创建代理的对象
     * @return 代理对象
     */
    public  static <T> T  getTransactionProxyInstance(Object target,Class<T> clazz)

        Object proxyInstance = Proxy.newProxyInstance(target.getClass().getClassLoader(),target.getClass().getInterfaces(), new InvocationHandler() 
            /**
             * 三个参数:1、代理对象,2、目标对象的方法,3、目标对象的参数值列表
             */
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable 
                System.out.println("执行目标业务逻辑之前");  //执行核心业务之前执行的内容
                Object result = method.invoke(target, args); //执行目标对象方法,即核心业务
                System.out.println("执行目标业务逻辑之后"); //执行核心业务之后执行的内容
                return result;
            
        );
        return (T)proxyInstance;
    



调用测试

    public static void main(String[] args) 

        UserServiceImpl userService = new UserServiceImpl();
        UserService userServiceProxy = JDKDynamicProxy.getTransactionProxyInstance(userService, UserService.class);
        System.out.println("结果:"+userServiceProxy.login("admin", "123"));
        // 一个是真实的对象,一个jdk动态创建的代理对象,根本都不是同一个对象
        System.out.println(userService == userServiceProxy );

    

输出结果:

执行目标业务逻辑之前
login 被执行... [目标方法被执行]
执行目标业务逻辑之后
结果:login success
false

进阶-SpringAOP原理

1.我们先来看下这个@EnableAspectJAutoProxy 这个注解做了什么事情

他往容器中导入了一个 AspectJAutoProxyRegistrar.class 组件

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(AspectJAutoProxyRegistrar.class)
public @interface EnableAspectJAutoProxy 
    boolean proxyTargetClass() default false;

    boolean exposeProxy() default false;


AspectJAutoProxyRegistrar.class做了什么事情?

class AspectJAutoProxyRegistrar implements ImportBeanDefinitionRegistrar 

	/**
	 * Register, escalate, and configure the AspectJ auto proxy creator based on the value
	 * of the @@link EnableAspectJAutoProxy#proxyTargetClass() attribute on the importing
	 * @code @Configuration class.
	 */
	@Override
	public void registerBeanDefinitions(
			AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) 

        // 在此处导入了 AnnotationAwareAspectJAutoProxyCreator.class 
		AopConfigUtils.registerAspectJAnnotationAutoProxyCreatorIfNecessary(registry);

		AnnotationAttributes enableAspectJAutoProxy =
				AnnotationConfigUtils.attributesFor(importingClassMetadata, EnableAspectJAutoProxy.class);
		if (enableAspectJAutoProxy != null) 
			if (enableAspectJAutoProxy.getBoolean("proxyTargetClass")) 
				AopConfigUtils.forceAutoProxyCreatorToUseClassProxying(registry);
			
			if (enableAspectJAutoProxy.getBoolean("exposeProxy")) 
				AopConfigUtils.forceAutoProxyCreatorToExposeProxy(registry);
			
		
	


然后我们的 AppConfig 早在 this() [ 也就是AnnotationConfigApplicationContext构造器的时候 ],的时候就添加到beanDefinitionMap中去了,然后在 invokeBeanFactoryPostProcessors(beanFactory);后置处理器的时候,就会去容器中找到所有配置类,并且解析。

invokeBeanFactoryPostProcessors(beanFactory)做的事情:

1.拿到工厂中的所有Bean定义信息(后置处理器(第一步的那几个)+配置类)

2.找到真正的配置类

3.使用parse进行配置类解析包扫描所有组件进来

4.1 scanner扫描包路径下的所有类,判断是否扫描范围内的,如果是就封装成4.ScannedGenericBeanDefinition添加到需要返回的组件集合中(@Lazy等注解会在这一步解析,保存到当前Bean的定义信息【BeanDefinition】中)

5.除了会处理@ComponentScan注解还能处理【@PropertySource、@lmport 、@lmportResource、】

执行完 invokeBeanFactoryPostProcessors(beanFactory) 我们的beanDefinitionMap中就有了Bean的定义信息了

等处理完,我们就可以看到 beanDefinitionMap 中了 AnnotationAwareAspectJAutoProxyCreator 的定义

AnnotationAwareAspectJAutoProxyCreator 他的职责就是看看那些类需要添加到 advisedBeans

在容器刷新的12大步骤中,registerBeanPostProcessors(beanFactory),会注册所有实现了BeanPostProcessors接口的类,然后我们的AnnotationAwareAspectJAutoProxyCreator就会被创建并且添加到容器中。

	public static void registerBeanPostProcessors(
			ConfigurableListableBeanFactory beanFactory, AbstractApplicationContext applicationContext) 

		
		// 先从容器中获取到 所有实现了 BeanPostProcessor 接口的beanName
		String[] postProcessorNames = beanFactory.getBeanNamesForType(BeanPostProcessor.class, true, false);

		
		int beanProcessorTargetCount = beanFactory.getBeanPostProcessorCount() + 1 + postProcessorNames.length;
		beanFactory.addBeanPostProcessor(new BeanPostProcessorChecker(beanFactory, beanProcessorTargetCount));

		
		List<BeanPostProcessor> priorityOrderedPostProcessors = new ArrayList<>();
		List<BeanPostProcessor> internalPostProcessors = new ArrayList<>();
		List<String> orderedPostProcessorNames = new ArrayList<>();
		List<String> nonOrderedPostProcessorNames = new ArrayList<>();
         //  处理实现了 PriorityOrdered接口的BeanPostProcessor
		for (String ppName : postProcessorNames) 
			if (beanFactory.isTypeMatch(ppName, PriorityOrdered.class)) 
				BeanPostProcessor pp = beanFactory.getBean(ppName, BeanPostProcessor.class);
				priorityOrderedPostProcessors.add(pp);
				if (pp instanceof MergedBeanDefinitionPostProcessor) 
					internalPostProcessors.add(pp);
				
			
			else if (beanFactory.isTypeMatch(ppName, Ordered.class)) 
				orderedPostProcessorNames.add(ppName);
			
			else 
				nonOrderedPostProcessorNames.add(ppName);
			
		

		// 进行排序
		sortPostProcessors(priorityOrderedPostProcessors, beanFactory);
        // 注册到beanPostProcessors中
		registerBeanPostProcessors(beanFactory, priorityOrderedPostProcessors);

		 // 处理实现了 Ordered 的BeanPostProcessor
         // 我们的AnnotationAwareAspectJAutoProxyCreator是实现了Ordered接口的会在此处进行处理
		List<BeanPostProcessor> orderedPostProcessors = new ArrayList<>(orderedPostProcessorNames.size());
		for (String ppName : orderedPostProcessorNames) 
             // 调用 getBean() 创建我们的AnnotationAwareAspectJAutoProxyCreator对象 经过生命周期
			BeanPostProcessor pp = beanFactory.getBean(ppName, BeanPostProcessor.class);
			orderedPostProcessors.add(pp);
			if (pp instanceof MergedBeanDefinitionPostProcessor) 
				internalPostProcessors.add(pp);
			
		
		sortPostProcessors(orderedPostProcessors, beanFactory);
		registerBeanPostProcessors(beanFactory, orderedPostProcessors);

		// 没有实现任何排序接口的BeanPostProcessor
		List<BeanPostProcessor> nonOrderedPostProcessors = new ArrayList<>(nonOrderedPostProcessorNames.size());
		for (String ppName : nonOrderedPostProcessorNames) 
			BeanPostProcessor pp = beanFactory.getBean(ppName, BeanPostProcessor.class);
			nonOrderedPostProcessors.add(pp);
			if (pp instanceof MergedBeanDefinitionPostProcessor) 
				internalPostProcessors.add(pp);
			
		
		registerBeanPostProcessors(beanFactory, nonOrderedPostProcessors);

		// Finally, re-register all internal BeanPostProcessors.
		sortPostProcessors(internalPostProcessors, beanFactory);
		registerBeanPostProcessors(beanFactory, internalPostProcessors);

		#yyds干货盘点#Spring源码三千问Spring AOP 中 TargetSource 的作用及原理分析

深度分析SpringAOP,一文带你彻底搞懂SpringAOP底层原理!

SpringAOP使用及源码分析(SpringBoot下)

SpringAOP使用及源码分析(SpringBoot下)

Spring异步调用原理及SpringAop拦截器链原理

Spring异步调用原理及SpringAop拦截器链原理