文章目录
1.SpringAop(Aspect Oriented Programming)是什么?
我们听说过很多次关于springAop的问题,那么什么是AOP呢?怎样结合官方文档来学习这个呢?那么我们将从他的概念,底层原理进行深入分析,做到应对面试和深入理解的层级
1.1springaop的概念
- AOP意思为
面向切面编程
和AOP比较像的一个词是我们的OOP,oop是面向对象编程,AOP是建立在OOP编程上的一种设计思想,而SpringAOP是实现AOP思想的主流框架. - AOP目的:对业务逻辑的各个部分进行隔离,在不改动代码的前提下进行功能的增强,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,通过是提高了开发效率;
- 应用场景:各个模块的横向切入点,比如日志、权限控制等具体如下:
1.在调用service具体一些业务方法的时候,想在前面打一些日志。
2.通过前后两次取时间戳来减一下,来统计所有业务方法执行的时间。
3.在调用某一类业务方法时,判断用户有没有权限。
4.在一系列业务方法前后加上事务的控制。比如startTransaction、commitTransaction(模拟事务控制)。
1.2 SpringAOP的底层原理
- 实际上AOP底层是通过Spring提供的
动态代理
技术实现的,在运行期间Spring通过动态代理技术动态的生成代理对象
,代理对象方法执行
时进行增强功能的介入,在调用目标对象的方法,从而完成业务功能的增强
1.3SpringAop实现的技术内容
- Spring框架监控切入点(Pointcut)方法的执行,一旦监控到切入点方法被运行,使用代理机制(proxy),动态创建目标对象的代理对象,根据通知类别(advice),在代理对象的对应位置将通知对应的功能织入,完成完整代码逻辑运行;
AOP代理模式
- 代理有两个,分别是动态代理和静态代理,SpringAOP是动态代理,而AspectJ是静态代理实现的AOP
2.1 AOP动态代理技术
- 上述提到我们的AOP是基于我们动态代理实现的,接下来我们看看AOP中的两种代理技术
- JDK动态代理(基于接口实现) cglib动态代理技术(非接口,基于父类)
在Spring里可以把一个类型注册成Spring里的一个Bean,这时候Spring就会帮我们把这个Bean初始化,变成一个可用的对象。加入我们需要在上面做一些增强,就是我们所谓的AOP。这时候我们就需要在中间加一层代理类或者增强类。
2.1.1 Jdk动态代理的实现原理
jdk动态代理,基于Java的反射机制实现,必须有接口才能使用该方法生成代理对象。JDK代理主要涉及到了两个类;Java.lang.reflect.InvocationHandler和java.lang.reflect.Proxy。这两个类的主要方法如下
- 大致流程是实现InvactionHandle接口创建方法调用器,通过Proxy类指定classLoader对象和一组interface创建动态代理,通过
反射获取动态代理类的构造函数
,参数类型就是调用器接口类型,通过构造函数创建动态代理类实例,构造时调用处理器接口类型。 JdkProxy
:假如说这个对象所在的类上面有接口(基于接口来做的),Spring会默认使用JdkProxy(JDK的动态代理),来生成一个代理,在代理里进一步的把所有对这个类做的增强操作,放到代理执行的代码里面。然后先做了增强的操作,再去调用原本的类的他的方法。
2.1.2 GGLib动态代理的原理
- CGLIB 代理的工作原理:利用ASM开源包,将对象类的class文件加载进来,通过修改字节码生成的子类来进行处理;在这个子类里,当我们调用原先这个类的某个方法时,先做增强操作,再去调原本类的方法,最后再把结果返回回来。
jdk动态代理的代码实现
- 基于反射
-
proxyTargetClass
:如果要代理的类有接口但想强制不用默认JDK的动态代理,也是用字节码
增强的技术,就可以开启proxyTargetClass选项。同CGlib。 -
CGlib
:假如说要增强或代理的这个类没有接口,只有一个类的定义,Spring会默认使用CGlib,对他做字节码增强。相当于硬生生的给他生成一个子类。在这个子类里,当我们调用原先这个类的某个方法时,先做增强操作,再去调原本类的方法,最后再把结果返回回来。
总之,Spring AOP面向切面的增强功能,都是作用在方法上的!
3.AOP的相关概念
在正式讲解AOP的操作之前,我们需要理解AOP的相关术语,常用的如下
- Target(目标对象): 代理的目标对象
- JoinPoint(连接点):所谓连接点是指那些要被拦截到的点,在spring中这些点指的方法,因为spring只支持方法类型的连接点;典型的包括方法调用,对类成员的访问以及异常处理程序块的执行等等,它自身还可以嵌套其它 joint point
- Pointcut(切入点):所谓切入点是指我们要对那些joinpoint进行操作,是Joinpoint的集合体可以理解成
- Advice(通知、增强):所谓通知是拦截到joinpoint之后要做的事情就是通知
- Aspect(切面): 是指pointCut与Advice的结合体
- Weaving(织入) :是指把增强(advice)应用到目标对象(target)来创建新的代理对象的过程;spring采用动态代理织入;但AspectJ采用的是编译期织入和类加载时期织入(weaving)
- Proxy(代理): 一个类AOP织入(weaving)增强后,就产生一个结果代理类
3.1连接点的小知识补充
AOP中的Joinpoint可以有多种类型:**构造方法调用,字段的设置和获取,方法的调用,方法的执行,异常的处理执行,类的初始化。**也就是说在AOP的概念中我们可以在上面的这些Joinpoint上织入我们自定义的Advice
,但是在Spring中却没有实现上面所有的joinpoint,确切的说,Spring只支持方法执行类型的Joinpoint。
AOP在开发中的使用
4.1XML配置AOP的详解
- 关于pointcut的example:其中(.)
代表任意,流程是:
返回类型.哪个包下的.哪个类.哪个方法.哪个参数`
<aop:pointCut id = "sakura" expression = "execution com.itheima.cast.*.*(..)"></aop:pointcut>
- 通知单配置语法
<aop:通知类型 method = "切面类中方法名" pointcut = "切点表达式" ></aop:通知类型>
4.2重点讲解我们用的注解AOP
- 1.创建目标接口和目标类:(基于JDK接口代理)
- 2.创建切面类
- 3.将目标类和切面类交由spring进行管理(@component是将前面xml的注进来)
- 4.在切面类使用注解@Aspect
4.2.1AOP详解注解配置
这里我建议要搭配xml进行理解
- 比如,这里我们定义的一个MyAspect类,里面有切点(pointcut)方法,用到的是@PointCut(…)
- 其次,我们的**@Before也就是我们的通知类(Advice)也就是增强方法,里面是包含我们的pointcut(包含我们要对那些类进行增强)的**
- 在往外看,这整个就是一个Aspect切面
- IDEA中的注解开发
5.需求的实现:需要验证有权限的用户才能调用该接口代码实现
场景:我们B站用户根据等级不同,进行的权限不同,比如LV6可以进行购买大会员激活码,或者彩色弹幕等等…那么我们就可以采用AOP简化开发
-1.创建接口类的注解
@Component
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
@Documented
public @interface ApiLimitedRole {
String[]limitedRoleCodeList()default{};
}
@Component
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
@Documented
public @interface DataLimitedRole {
}
- 2.创建切面类Aspect
@Aspect
@Component
public class ApiLimitedRoleAspect {
@Autowired
private UserSupport userSupport;
@Autowired
private UserRoleService userRoleService;
@Pointcut("@annotation(com.imooc.bilibili.domain.annotation.ApiLimitedRole)")
public void check(){
}
@Before("check() && @annotation(apiLimitedRole)")
public void doBefore(JoinPoint joinPoint, ApiLimitedRole apiLimitedRole){
Long userId = userSupport.getCurrentUserId();
//获取用户权限集合
List<UserRole> userRoleList = userRoleService.getUserRoleByUserId(userId);
//获取到权限限制集合
String[] limitedRoleCodeList = apiLimitedRole.limitedRoleCodeList();
Set<String> limitedRoleCodeSet = Arrays.stream(limitedRoleCodeList).collect(Collectors.toSet());
Set<String> roleCodeSet = userRoleList.stream().map(UserRole::getRoleCode).collect(Collectors.toSet());
roleCodeSet.retainAll(limitedRoleCodeSet);
if(roleCodeSet.size()>0){
throw new ConditionalException("权限不足!");
}
}
}
@Aspect
@Component
public class DataLimitedRoleAspect {
@Autowired
private UserSupport userSupport;
@Autowired
private UserRoleService userRoleService;
@Pointcut("@annotation(com.imooc.bilibili.domain.annotation.DataLimitedRole)")
public void check(){
}
@Before("check()")
public void doBefore(JoinPoint joinPoint){
Long userId = userSupport.getCurrentUserId();
//获取用户权限集合
List<UserRole> userRoleList = userRoleService.getUserRoleByUserId(userId);
//获取到权限限制集合
Set<String> roleCodeSet = userRoleList.stream().map(UserRole::getRoleCode).collect(Collectors.toSet());
Object[] args = joinPoint.getArgs();
for (Object arg : args) {
if(arg instanceof UserMoment){
UserMoment userMoment = (UserMoment)arg;
String type = userMoment.getType();
if(roleCodeSet.contains(AuthRoleConstant.ROLE_LV1 )&& !"0".equals(type)){
throw new ConditionalException("参数异常");
}
}
}
}
}
- 3.对需要实现AOP加载的方法上加上对应注解
/**
* 新增用户动态请求
* @param userMoment
* @return
* @throws Exception
*/
@ApiLimitedRole(limitedRoleCodeList = {AuthRoleConstant.ROLE_LV0})
@DataLimitedRole
@PostMapping("/user-moments")
public JsonResponse<String> addUserMoments(@RequestBody UserMoment userMoment) throws Exception{
Long userId = userSupport.getCurrentUserId();
userMoment.setUserId(userId);
userMomentsService.addUserMoments(userMoment);
return JsonResponse.success();
}