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通知的执行顺序
我们来看下这些通知的一个指向流程:
正常的执行逻辑:
- 执行环绕通知的前置逻辑
- 执行前置通知
- 执行目标方法
- 执行后置通知
- 执行最终通知
- 执行环绕通知的后置逻辑
出现异常的执行逻辑:
- 执行环绕通知的前置逻辑
- 执行前置通知
- 执行目标方法
- 出现异常,执行异常通知,出现异常返回通知和环绕通知的后置逻辑就不会被执行了
- 执行最终通知
多个切面时的执行流程:
springAOP在真实开发中可以做什么?
我这里给大家举几个例子: 反正你就可以理解为,你可以在你的业务逻辑的任何方法执行的时候进行干扰,也就是增强,你可以获取到该方法运行时的参数,可以做一些具体的操作,具体怎么操作是由你的决定的,springAOP在执行方法的同时把控制器交给你,这也是很多框架的特点,在执行原有逻辑的基础上,给你留有接口,或者是能让你对执行过程可以进行干扰。
- 记录用户的操作记录
- 事务管理
- 防重复提交
- 做数据缓存
- 权限验证
- 数据校验
进阶-代理模式之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 的作用及原理分析