模板方法模式Spring抽象类@Autowired注入与AOP碰到的问题 – 绝知此事要躬行

模板方法模式Spring抽象类@Autowired注入与AOP碰到的问题 – 绝知此事要躬行

类型
spring boot
URL
https://blog.dongxiang.io/2020/12/18/%E6%A8%A1%E6%9D%BF%E6%96%B9%E6%B3%95%E6%A8%A1%E5%BC%8Fspring%E6%8A%BD%E8%B1%A1%E7%B1%BBautowired%E6%B3%A8%E5%85%A5%E4%B8%8Eaop%E7%A2%B0%E5%88%B0%E7%9A%84%E9%97%AE%E9%A2%98/
是否整理吸收
Date
一个新业务的创单流程不同端的参数和供应商有不同的需求,因此打算使用模板方法来封装创单流程,将差异化的逻辑抽象出来交给各自的子类去实现。
创单流程会依赖一些其他服务,比如会员、商品等,因此必须要用某种方式将这些服务注入到抽象父类模板之中。常规的操作是在子类中通过父类的构造方法的方式来进行服务注入,由于我们项目是使用Spring框架开发的,同时我知道Spring是可以在抽象父类里的属性加上注解标签@Autowired,子类在容器实例化的时候会自动给父类属性进行属性注入的,所以就通过Autowired方式进行服务的注入。
如下:
@Component
public class A {

    public String m() {
        return "A.m()";
    }

}

@Component
public class B {

    public String m() {
        return "B.m()";
    }

}

public abstract class AbstractService {

    @Resource
    protected A a;

    @Autowired
    private B b;

    // 模板方法
    publicfinal void m() {
        System.out.println(a.m());
        System.out.println(b.m());
        abs();
    }

    public abstract void abs();

}

@Service
public class ServiceB extends AbstractService {
    @Override
    public void abs() {
        System.out.println("ServiceB.abs");
    }
}

@Component
public class TestService {

    @Resource
    private ServiceB serviceB;

    public void test() {
        serviceB.m();
    }
}

调用TestService.test()输出:
A.m()
B.m()
ServiceB.abs
notion image
,来源:https://www.runoob.com/wp-content/uploads/2014/08/template_pattern_uml_diagram.jpg
如上测试服务一切正常,父类AbstractService能够被正常注入,模板方法也能正常执行。
但是当我对AbstractService加了一个切面之后,意外发生了,我调用TestService.abs()之后得到了一个空指针异常。
@Aspect
@Component
public class TestAspect {

    @Pointcut("execution(public * abstractservice.AbstractService.*(..))")
    public void pointcutAbstract() {
    }

    @Before("TestAspect.pointcutAbstract()")
    public void before(JoinPoint joinPoint) {
        System.out.println("before joinPoint = " + joinPoint.getTarget().getClass().getName());
    }

    @After("io.dongxiang.japi.spring.abstractservice.TestAspect.pointcutAbstract()")
    public void after(JoinPoint joinPoint) {
        System.out.println("after joinPoint = " + joinPoint.getTarget().getClass().getName());
    }

}

调用TestService.test()空指针异常:
2020-12-18 17:31:53.103 ERROR 7256 --- [nio-8080-exec-1] o.a.c.c.C.[.[.[/].[dispatcherServlet]    : Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed; nested exception is java.lang.NullPointerException] with root cause

java.lang.NullPointerException: null
	at AbstractService.m(AbstractService.java:16) ~[classes/:na]
	at TestService.test(TestService.java:18) ~[classes/:na]
此时我就蒙了,根据异常信息推断应该是AbstractService的属性a,b没有注入成功为null导致的。为了找出原因我就debug了下代码。
notion image
根据debug信息可以看到AbstractService的实例的属性a,b都为null。而且可以看到这是一个CGLIB生成的代理类,所以里面的属性为null并不意外,但是调用提示了空指针异常。查看代理CALLBACK里的代理目标对象属性a,b确实是存在已注入的(下图)
notion image
因此可以推断出,对代理对象的方法AbstractService.m()的调用并没有代理到实际的目标对象之上
为什么没有代理到实际对象呢???
答案就是我在模板方法上加了一个final标识,目的是为了防止子类覆盖父类定义好的创单流程。而CGLIB生成AbstractService代理类的时候是通过继承的方式进行的,而final关键字定义在方法上导致了生成的代理类无法覆盖父类的方法,所以就不能代理AbstractService.m()方法,执行的是代理对象自己的m()方法,而代理类并未被注入属性a,b所以才导致了空指针异常。
去掉AbstractService.m()前面的final修饰符aop就能正常执行了.
// 模板方法 public void m() { System.out.println(a.m()); System.out.println(b.m()); abs(); }
调用TestService.test()输出:
before joinPoint = io.dongxiang.japi.spring.abstractservice.ServiceB
A.m()
B.m()
ServiceB.abs
after joinPoint = abstractservice.ServiceB
before joinPoint = abstractservice.ServiceA

Loading Comments...