spring aop聊点不一样的东西


扩展右上角“ 设为星标
”能第一时间看到好文章

你有几年没回老家了?
我有三年。
今年怕是又回不去了,有些想家了。。。
你呢?
前几篇文章本打算写spring aop的,但是强忍着没有写(旁白:也有可能是没想好该怎么写:stuck_out_tongue_closed_eyes:),就是为了今天整个专题,因为它是spring中最核心的技术之一,实在太重要了。
关于spring aop的文章网上一搜一大堆,但我想写点不一样的东西,尝试一种全新的写作风格,希望您会喜欢。

从实战出发

很多文章讲spring aop的时候,一开始就整一堆概念,等我们看得差不多要晕的时候,才真正进入主题。。。
我却相反,没错,先从实战出发。
在spring aop还没出现之前,想要在目标方法之前先后加上日志打印的功能,我们一般是这样做的:

@Service
public  class TestService {

    public void doSomething1() {
        beforeLog();
        System.out.println("==doSomething1==");
        afterLog();
    }

    public void doSomething2() {
        beforeLog();
        System.out.println("==doSomething1==");
        afterLog();
    }

    public void doSomething3() {
        beforeLog();
        System.out.println("==doSomething1==");
        afterLog();
    }

    public void beforeLog() {
        System.out.println("打印请求日志");
    }

    public void afterLog() {
        System.out.println("打印响应日志");
    }
}

如果加了新doSomethingXXX方法,就需要在新方法前后手动加beforeLog和afterLog方法。
原本相安无事的,但长此以往,总有会出现几个刺头青。
刺头青A说:每加一个新方法,都需要加两行重复的代码,是不是很麻烦?
刺头青B说:业务代码和公共代码是不是耦合在一起了?
刺头青C说:如果有几千个类中加了公共代码,而有一天我需要删除,是不是要疯了?
spring大师们说:我们提供一套spring的aop机制,你们可以闭嘴了。

下面看看用spring aop(偷偷说一句,还用了 aspectj
)是如何打印日志的:

@Service
public class TestService {

public void doSomething1() {
System.out.println("==doSomething1==");
}

public void doSomething2() {
System.out.println("==doSomething1==");
}

public void doSomething3() {
System.out.println("==doSomething1==");
}
}
@Component
@Aspect
public class LogAspect {

@Pointcut("execution(public * com.sue.cache.service.*.*(..))")
public void pointcut() {
}

@Before("pointcut()")
public void beforeLog() {
System.out.println("打印请求日志");
}

@After("pointcut()")
public void afterLog() {
System.out.println("打印响应日志");
}
}

增加了 LogAspect
类,在类上加了 @Aspect
注解。先在类中使用 @Pointcut
注解定义了pointcut方法,然后将beforeLog和afterLog方法移到这个类中,分别加上 @Before
@After
注解。
改造后,业务方法在TestService类中,而公共方法在LogAspect类中,是分离的。如果要新加一个业务方法,直接加就好,LogAspect类不用改任何代码,新加的业务方法就自动拥有打印日志的功能,是不是很神奇?

spring aop其实是一种横切的思想,通过动态代理技术将公共代码织入到业务方法中。
这里出于5毛钱的友情,有必要温馨提醒一下。aop是一种思想,不是spring独有的,目前市面上比较出名的有:

  • aspectj
  • spring aop
  • jboss aop

我们现在主流的做法是将spring aop和 aspectj结合使用,
spring借鉴了AspectJ的切面,以提供注解驱动的AOP。
此时,一个黑影一闪而过。
刺头青D问:你说的“横切”,“动态代理”,“织入” 是什么东东?

几个重要的概念

根据上面spring aop的代码,用一张图聊聊几个重要的概念:

  • 连接点(Joinpoint) 程序执行的某个特定位置,如某个方法调用前,调用后,方法抛出异常后,这些代码中的特定点称为连接点。
  • 切点(Pointcut) 每个程序的连接点有多个,如何定位到某个感兴趣的连接点,就需要通过切点来定位。
  • 通知(Advice) 增强是织入到目标类连接点上的一段程序代码。
  • 切面(Aspect) 切面由切点和通知组成,它既包括了横切逻辑的定义,也包括了连接点的定义,SpringAOP就是将切面所定义的横切逻辑织入到切面所制定的连接点中。
  • 目标对象(Target) 需要被增强的业务对象
  • 代理类(Proxy) 一个类被AOP织入增强后,就产生了一个代理类。
  • 织入(Weaving) 织入就是将增强添加到对目标类具体连接点上的过程。

还是那个刺头青D说(旁边:这位仁兄比较好学):spring aop概念弄明白了,挺简单的。 @Pointcut
注解的 execution
表达式刚刚看得我一脸懵逼,可以再说说吗,我请你吃饭?

切入点表达式

@Pointcut
注解的 execution
切入点表达,看似简单,里面还是有些内容的。为了更直观一些,还是用张图来总结一下:


该表达式的含义是:匹配访问权限是public,任意返回值,包名为:com.sue.cache.service,下面的所有类所有方法和所有参数类型。图中所有用*表示,比如图中类名用.*表示的是所有类。如果具体匹配某个类,比如:TestService,则表达式可以换成:

@Pointcut("execution(public * com.sue.cache.service.TestService.*(..))")

其实spring支持9种表达式, execution
只是其中一种。

有哪些入口?

先说说我为什么会问这样一个问题?
spring aop有哪些入口?说人话就是在问:spring中有哪些场景需要调用aop生成代理对象,难道你不好奇吗?

AbstractAutowireCapableBeanFactory类的createBean方法中,有这样一段代码:
它通过BeanPostProcessor提供了一个生成代理对象的机会。具体逻辑在AbstractAutoProxyCreator类的postProcessBeforeInstantiation方法中:


说白了,需要实现 TargetSource
才有可能会生成代理对象。该接口是对 Target
目标对象的封装,通过该接口可以获取到目标对象的实例。
不出意外,这时,又会冒出一个黑影。
刺头青F说:这里生成代理对象有什么用呢?

有时我们想自己控制bean的创建和初始化,而不需要通过spring容器,这时就可以通过实现 TargetSource
满足要求。只是创建单纯的实例还好,如果我们想使用代理该怎么办呢?这时候,入口1的作用就体现出来了。
AbstractAutowireCapableBeanFactory类的doCreateBean方法中,有这样一段代码:


它主要作用是为了解决对象的循环依赖问题,核心思路是提前暴露singletonFactory到缓存中。
通过getEarlyBeanReference方法生成代理对象: