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的继承结构
这个类实现了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切入点位置,接下来就是创建对象了,我们下一期讲。