解决Spring AOP拦截父类中方法失效问题(intercept call to superclass method using Spring-AOP)

解决Spring AOP拦截父类中方法失效问题

  • 解决方案
    • 1、子类中显式覆盖(@Override)父类中的方法
    • 2、使用 execution & target 方式定义pointcut
    • 3、第二种方法的变种
  • 问题引出
  • 入坑步骤
  • 解决过程
  • DEMO 完整代码

解决方案

如题,如果想在AOP中拦截某个具体子类中继承自父类的方法,需要按照如下两种方式完成

1、子类中显式覆盖(@Override)父类中的方法

  • 父类方法
public abstract class GenericDao<T> {

    protected  int insert(T entity)throws Exception{
       System.out.println("插入记录==" + entity.getClass().getSimpleName());
       return 0;
    }

    protected int deleteById()throws Exception{
        System.out.println("删除了数据=");
        return 0;
    }

}
  • 子类方法
@Repository
public class DeptDao extends GenericDao<DeptInfo> {

   @Override
   public int deleteById() throws Exception {
       return super.deleteById();
   }
}
  • AOP 类中的配置
@Aspect
@Component
public class SubClassInterCeptor {
   @After(value = "execution(* com.example.aopdemo.dao.DeptDao.deleteById(..))")
   public void afterDeptDelete(){
       System.out.println("进入dept后置拦截");
   }
}

注意:com.example.aopdemo.dao.DeptDao.deleteById ,表示拦截具体子类DeptDao对应的方法

2、使用 execution & target 方式定义pointcut

  • 子类无需覆盖父类方法
@Repository
public class UserDao extends GenericDao<UserInfo> {

}
  • AOP类中定义拦截
@Aspect
@Component
public class SubClassInterceptor {
    @After(value = "execution(* com.example.aopdemo.dao.GenericDao+.insert(..)) && target(com.example.aopdemo.dao.UserDao)")
    public void afterUserInsert(JoinPoint joinPoint){
        System.out.println("进入user后置拦截");
    }
}

关键点:
com.example.aopdemo.dao.GenericDao+.insert(…)) GenericDao+,表示拦截GenericDao及其子类
target(com.example.aopdemo.dao.UserDao) target表示具体执行目标类,必须为类的全限定名

3、第二种方法的变种

  • AOP 拦截配置
@Aspect
@Component
public class SubClassInterceptor {

    @After(value = "execution(* com.example.aopdemo.dao.GenericDao+.insert(..)) && target(bean)")
    public void afterUserInsert(JoinPoint joinPoint, UserDao bean){
        System.out.println("进入user后置拦截");
    }
}

问题引出

在Spring项目开发过程中,经常会遇到各种基类(super class),比如各种GenericDao ,BaseDao,模板模式类
中定义的方法,这些基类有若干个子类实现,若想通过AOP方式关注某个具体子类执行某个基类方法的执行,
则需要采取正确的姿势来定义pointcut,否则会无法正确拦截,因为Spring AOP 默认忽略继承自父类的方法

近日接到产品一个需求,需要在用户注册完成后向其推荐人发送小程序推送消息,由于用户注册服务早已开发测试完毕,且用户注册来自多个渠道,但是最终都会调用BaseDao中的insert方法进行入库,直接覆盖insert方法添加发送逻辑,在此处耦合了非insert本身的逻辑,于是便想到了采用AOP方式,关注user insert执行,基于执行结果判定是否需要发送消息,而且对业务无侵入,快速插拔。于是乎coding走起

入坑步骤

-编写AOP代码

@Aspect
@Component
public class SubClassInterceptor {

    @After(value = "execution(* com.example.aopdemo.dao.UserDao.insert(..))")
    public void afterUserInsert(JoinPoint joinPoint){
        System.out.println("进入user后置拦截");
    }
  }

由于只关注user的 insert方法执行,想当然配置了
@After(value = “execution(* com.example.aopdemo.dao.UserDao.insert(…))”)
兴奋的跑起测试用例

@SpringBootTest
class UserDaoTest {
    @Autowired
    private UserDao userDao;
    @Test
    void  testInsert(){
        try {
            userDao.insert(new UserInfo());
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

然而并没有如期看到 “进入user后置拦截”的控制台输出,蒙圈中…明明是执行的userDao.insert方法啊

解决过程

于是乎找出Spring Framework Reference Documentation ,查看 11.2 @AspectJ support ,定位到
Declaring a pointcut

• execution - for matching method execution join points, this is the primary pointcut designator you will
use when working with Spring AOP
• within - limits matching to join points within certain types (simply the execution of a method declared
within a matching type when using Spring AOP)
• this - limits matching to join points (the execution of methods when using Spring AOP) where the bean
reference (Spring AOP proxy) is an instance of the given type
target - limits matching to join points (the execution of methods when using Spring AOP) where the
target object (application object being proxied) is an instance of the given type

• args - limits matching to join points (the execution of methods when using Spring AOP) where the
arguments are instances of the given types

平时开发过程中用到的最多的可能是execution ,这里

target 用于限定执行目标方法的对象的类型

官方示例寻找端倪

@Before(value="com.xyz.lib.Pointcuts.anyPublicMethod() && target(bean) && @annotation(auditable)",
argNames="bean,auditable")
public void audit(Object bean, Auditable auditable) {
AuditCode code = auditable.value();
// ... use code and bean
}

英文不太好,于是乎参考
链接: Spring AOP 所有切入点指示符详解(execution,within,this,target,args,@within,@target,@args,@annotation).

总结出以上解决方案,若您有更好的解决方案欢迎留言哦~

DEMO 完整代码

  • 实体
public class UserInfo {
    private Long id;
    private String name;

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }
}
public class DeptInfo {
    private Long id;
    private String deptName;

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }
}

  • DAO
public abstract class GenericDao<T> {

    protected int insert(T entity)throws Exception{
       System.out.println("插入记录==" + entity.getClass().getSimpleName());
       return 0;
    }

    protected int deleteById()throws Exception{
        System.out.println("删除了数据=");
        return 0;
    }

}
@Repository
public class UserDao extends GenericDao<UserInfo> {

}
@Repository
public class DeptDao extends GenericDao<DeptInfo> {

    @Override
    public int deleteById() throws Exception {
        return super.deleteById();
    }
}
  • AOP 实现
@Aspect
@Component
public class SubClassInterceptor {
    /**
     * 用户信息插入后执行的业务逻辑
     * @param joinPoint
     * @param bean
     */
    //@After(value = "execution(* com.example.aopdemo.dao.GenericDao+.insert(..)) && target(com.example.aopdemo.dao.UserDao)")
    @After(value = "execution(* com.example.aopdemo.dao.GenericDao+.insert(..)) && target(bean)")
    public void afterUserInsert(JoinPoint joinPoint, UserDao bean){
        System.out.println("进入user后置拦截");
    }

    /**
     * 只关注 dept删除方法执行
     */
    @After(value = "execution(* com.example.aopdemo.dao.DeptDao.deleteById(..))")
    public void afterDeptDelete(JoinPoint joinPoint){
        System.out.println("进入dept后置拦截");
    }

}
  • 测试用例
@SpringBootTest
class UserDaoTest {
    @Autowired
    private UserDao userDao;
    @Test
    void  testInsert(){
        try {
            userDao.insert(new UserInfo());
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
@SpringBootTest
class DeptDaoTest {

    @Autowired
    private DeptDao deptDao;
    @Test
    void testDeptInsert(){
        try {
            deptDao.insert(new DeptInfo());
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    
    @Test
    void testDeptDelete(){
        try {
            deptDao.deleteById();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

}

你可能感兴趣的:(java,spring,aop,java)