吉法师的博客

不知道能否追到喜欢的人呀,今年努力下吧~ 2022.1.4

Aop面向切面编程

AOP的产生

OOP:Object Oriented Programming,OOP作为面向对象编程的模式,获得了巨大的成功,OOP的主要功能是数据封装、继承和多态。

而AOP是一种新的编程方式,它和OOP不同,OOP把系统看作多个对象的交互,AOP把系统分解为不同的关注点,或者称之为切面(Aspect)。

也就是AOP能把需求做拆分,分为安全控制、日志、业务等模块,减少重复的实现和代码编写。

使用

一、装配AOP

@Aspect
@Component
public class LoggingAspect {
    // 在执行UserService的每个方法前执行:
    @Before("execution(public * com.itranswarp.learnjava.service.UserService.*(..))")
    public void doAccessCheck() {
        System.err.println("[Before] do access check...");
    }

    // 在执行MailService的每个方法前后执行:
    @Around("execution(public * com.itranswarp.learnjava.service.MailService.*(..))")
    public Object doLogging(ProceedingJoinPoint pjp) throws Throwable {
        System.err.println("[Around] start " + pjp.getSignature());
        Object retVal = pjp.proceed();
        System.err.println("[Around] done " + pjp.getSignature());
        return retVal;
    }
}
  • 观察doAccessCheck()方法,我们定义了一个@Before注解,后面的字符串是告诉AspectJ应该在何处执行该方法,这里写的意思是:执行UserService的每个public方法前执行doAccessCheck()代码。

  • 再观察doLogging()方法,我们定义了一个@Around注解,它和@Before不同,@Around可以决定是否执行目标方法,因此,我们在doLogging()内部先打印日志,再调用方法,最后打印日志后返回结果。

  • 在LoggingAspect类的声明处,除了用@Component表示它本身也是一个Bean外,我们再加上@Aspect注解,表示它的@Before标注的方法需要注入到UserService的每个public方法执行前,@Around标注的方法需要注入到MailService的每个public方法执行前后。

二、方便的使用

虽然Spring容器内部实现AOP的逻辑比较复杂(需要使用AspectJ解析注解,并通过CGLIB实现代理类),但我们使用AOP非常简单,一共需要三步:

  1. 定义执行方法,并在方法上通过AspectJ的注解告诉Spring应该在何处调用此方法;

  2. 标记@Component和@Aspect;

  3. 在@Configuration类上标注@EnableAspectJAutoProxy。

三、拦截器

顾名思义,拦截器有以下类型:

  • @Before:这种拦截器先执行拦截代码,再执行目标代码。如果拦截器抛异常,那么目标代码就不执行了;

  • @After:这种拦截器先执行目标代码,再执行拦截器代码。无论目标代码是否抛异常,拦截器代码都会执行;

  • @AfterReturning:和@After不同的是,只有当目标代码正常返回时,才执行拦截器代码;

  • @AfterThrowing:和@After不同的是,只有当目标代码抛出了异常时,才执行拦截器代码;

  • @Around:能完全控制目标代码是否执行,并可以在执行前后、抛异常后执行任意拦截代码,可以说是包含了上面所有功能。

四、注解的方式使用

@Aspect
@Component
public class LoggingAspect {
    @Before("execution(public * com..*.UserService.*(..))")
    public void doAccessCheck() {
        System.err.println("[Before] do access check...");
    }
}

@Component
public class MailService {
    @Autowired
    UserService userService;

    public String sendMail() {
        ZoneId zoneId = userService.zoneId;
        System.out.println(zoneId); // null
        ...
    }
}

正确使用AOP,我们需要一个避坑指南:

  • 访问被注入的Bean时,总是调用方法而非直接访问字段;
  • 编写Bean时,如果可能会被代理,就不要编写public final方法。

Share