spring AOP

先看一个例子,增强 spring bean 的方法,使用 JDK 动态代理的方式

@Component
public class Test implements BeanPostProcessor, ApplicationContextAware {

    private ApplicationContext applicationContext;

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.applicationContext = applicationContext;
    }

    /**
     * 放到单例池的是代理对象
     * @param bean 原来的 bean
     * @param beanName 原来的 bean 名称
     */
    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        // 如果 bean 是 User 才增强
        if (bean instanceof User) {
            User user = (User) applicationContext.getBean("user");
            // 返回代理对象( JDK 动态代理)
            return Proxy.newProxyInstance(
                    user.getClass().getClassLoader(),
                    user.getClass().getInterfaces(),
                    (Object proxy, Method method, Object[] args) -> {
                        System.out.println("增强前.....");
                        // 执行 bean 本身的方法(没有过滤方法,就是增强所有方法)
                        Object invoke = method.invoke(user, args);
                        System.out.println("增强后.....");
                        return invoke;
                    }
            );
        }
        // 如果 bean 不是 User 直接返回原来的
        return bean;
    }
}

spring 的 AOP 具体实现功能更强大,但也是利用 BeanPostProcessor 为 bean 生成代理对象从而达到增强 bean 的目的

AOP 是一种思想,实现方式不唯一,有 AspectJ 和 Advisor,spring 的声明式事物使用的是 Advisor,但是 AspectJ 使用更广泛

AspectJ 更强大更灵活,Advisor 相对功能单一但实现起来更方便,开发人员基本不会使用 Advisor ,所以这里只介绍 AspectJ

AspectJ 概念

概念 单词 解释
目标对象 Target 被增强方法所在的对象
代理对象 Proxy 代理想想,后面实际调用的对象
连接点 Joinpoint 目标对象中可以被增强的方法(就是bean的所有方法)
切入点/切点 Pointcut 目标对象实际被增强的方法(bean实际增强的方法)
通知 Advice 增强的代码,分为多种类型
切面 Aspect 通知+切入点(bean原来的方法+增强的东西)
织入 Weaving 将通知和切入点组合的过程

通知

类型 xml 配置方式 执行时机 备注
前置 <aop:before> 目标方法执行前
后置 <aop:after-returning> 目标方法执行后 目标方法异常不执行
环绕 <aop:around> 目标方法执行前后都执行 目标方法异常,后置不执行
异常 <aop:after-throwing> 目标方法发生异常时执行 不发生异常不执行
最终 <aop:after> 目标方法执行后 不管目标方法是否异常都执行

切点表达式

execution([访问修饰符] 返回值类型 包名.类名.方法名(方法参数))

  1. 返回值类型、某一级包名、类名、方法名 可以用 * 表示任意,不限定
  2. 包名中使用单点表示单个子级,双点表示所有子级(com.style.spring.xml,com..xml)
  3. 参数可以使用..表示任意参数

示例

@Aspect
@Component
public class TestAspect {

    // 切点表达式
    @Pointcut("execution (* com.dtyunxi.icommerce.svr.rest..*.*(..))")
    public void pointCut(){}
    
    // 这又不是静态方法为什么可以这样调方法?
    // 这还在字符串里呢,咋可能调方法,这时规定的语法格式,可以理解为不是 java 代码
    @Before("TestAspect.pointCut()")
    public void before(JoinPoint joinPoint) {
        System.out.println("前置通知");
        System.out.println("目标对象:" + joinPoint.getTarget());
        System.out.println("切点表达式:" + joinPoint.getStaticPart());
    }

    // 还可以省略类名,直接写方法名
    // throwing 属性值要和方法参数明相等
    @AfterThrowing(value = "pointCut()", throwing = "e")
    public void afterThrowing(Throwable e) {
        System.out.println("发生异常" + e.getMessage());
    }

    @Before("pointCut()")
    public void after(JoinPoint joinPoint) {
        System.out.println("后置通知");
    }

    // ProceedingJoinPoint 是 JoinPoint 的子类,可以执行目标方法
    @Around("pointCut()")
    public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
        System.out.println("环绕前置通知");
        Object proceed = joinPoint.proceed(); // 执行目标方法
        System.out.println("环绕后置通知");
        return proceed;
    }


}
posted @ 2024-06-29 18:35  CyrusHuang  阅读(4)  评论(0编辑  收藏  举报