转载

聊一聊 AOP <上>

aop 终于提上日程来写一写了。

  • 从一个例子说起
    • 基于代理的方式
    • 纯POJO切面 配置方式
    • AspectJ 注解方式
    • AspectJ XML 配置方式
    • 表达式说明
  • 基础概念
    • AOP概念
    • Target Object
    • 织入(Weave
    • Proxy
    • Introduction
    • Aspect
    • Joinpoint
      • Joinpoint & ProceedingJoinPoint
    • Pointcut
      • 关于 Pointcut 和 Joinpoint 的区别
    • Advice
      • 概念
      • 分类
    • 关系
  • AOP的实现机制
    • 动态代理
    • 动态字节码生成
    • 自定义类加载器
    • 字节码转
  • Spring AOP一些知识点
    • 实现原理
    • 事务管理
  • 一些坑

从一个例子说起

项目地址:XXXXX;

基于代理的方式

这种方式看起来很好理解,但是配置起来相当麻烦;小伙伴们可以参考项目来看,这里只贴出比较关键的流程代码。

  • 首先定义一个接口:GoodsService
public interface GoodsService {
	/**
	 * 查询所有商品信息
	 * 
	 * @param offset 查询起始位置
	 * @param limit 查询条数
	 * @return
	 */
	List<Goods> queryAll(int offset,int limit);
}
  • GoodsService 实现类
@Service
@Qualifier("goodsService")
public class GoodsServiceImpl implements GoodsService {
	@Autowired 
	private GoodsDao goodsDao;
	
	public List<Goods> queryAll(int offset, int limit) {
		System.out.println("执行了queryAll方法");
		List<Goods> list = new ArrayList<Goods>();
		return list;
	}
}
  • 定义一个通知类 LoggerHelper,该类继承 MethodBeforeAdvice和 AfterReturningAdvice。
//通知类 LoggerHelper
public class LoggerHelper implements MethodBeforeAdvice,
AfterReturningAdvice {

    private static final Logger LOGGER = LoggerFactory.getLogger(LoggerHelper.class);
    //MethodBeforeAdvice的before方法实现
    public void before(Method method, Object[] objects, Object o) throws Throwable {
        LOGGER.info("before current time:"+System.currentTimeMillis());
    }
    //AfterReturningAdvice的afterReturning方法实现
    public void afterReturning(Object o, Method method,
    Object[] objects, Object o1) throws Throwable {
        LOGGER.info("afterReturning current time:"+System.currentTimeMillis());
    }
}
  • 重点,这个配置需要关注下。这个项目里面我是配置在applicationContext.xml文件中的。
<!-- 定义被代理者 -->
<bean id="goodsServiceImpl" class="com.glmapper.framerwork.service.impl.GoodsServiceImpl"></bean>

<!-- 定义通知内容,也就是切入点执行前后需要做的事情 -->
<bean id="loggerHelper" class="com.glmapper.framerwork.aspect.LoggerHelper"></bean>

<!-- 定义切入点位置 -->
<bean id="loggerPointcut" class="org.springframework.aop.support.JdkRegexpMethodPointcut">
	<property name="pattern" value=".*query.*"></property>
</bean>

<!-- 使切入点与通知相关联,完成切面配置 -->
<!-- 从这里可以帮助我们理解Advisor,advice和pointcut之间的关系-->
<!--adivce和pointcut是Advisor的两个属性-->
<bean id="loggerHelperAdvisor" class="org.springframework.aop.support.DefaultPointcutAdvisor">
	<property name="advice" ref="loggerHelper"></property>
	<property name="pointcut" ref="loggerPointcut"></property>
</bean>

<!-- 设置代理 -->
<bean id="proxy" class="org.springframework.aop.framework.ProxyFactoryBean">
	<!-- 代理的对象 ,也就是目标类-->
	<property name="target" ref="goodsServiceImpl"></property>
	<!-- 使用切面 -->
	<property name="interceptorNames" value="loggerHelperAdvisor"></property>
	<!-- 代理接口,商品接口 -->
	<property name="proxyInterfaces" value="com.glmapper.framerwork.service.GoodsService"></property>
</bean>
  • 使用:注解注入方式
@Controller
@RequestMapping("/buy")
public class BuyController {
    @Autowired
    private OrderService orderService;
    //因为我们已经在配置文件中配置了proxy,
    //所以这里可以直接注入拿到我们的代理类
    @Autowired
    private GoodsService proxy;
    
    @RequestMapping("/initPage")
    public ModelAndView initPage(HttpServletRequest request,
    	HttpServletResponse response, ModelAndView view) {
    //这里使用proxy执行了*query*,
    List<Goods> goods = proxy.queryAll(10,10);
    view.addObject("goodsList", goods);
    view.setViewName("goodslist");
    return view;
    }
}
  • 使用:工具类方式手动获取bean

这个方式是通过一个SpringContextUtil工具类来获取代理对象的。

@RequestMapping("/initPage")
public ModelAndView initPage(HttpServletRequest request,
	HttpServletResponse response, ModelAndView view) {
    //这里通过工具类来拿,效果一样的。
    GoodsService proxy= (GoodsService) SpringContextUtil.getBean("proxy");
    List<Goods> goods = proxy.queryAll(10,10);
    view.addObject("goodsList", goods);
    view.setViewName("goodslist");
    return view;
}
  • SpringContextUtil 类的定义

这个还是有点坑的,首先SpringContextUtil是继承ApplicationContextAware这个接口,我们希望能够SpringContextUtil可以被Spring容器直接管理,所以,需要使用 @Component 标注。标注了之后最关键的是它得能够被我们配置的注入扫描扫到(亲自踩的坑,我把它放在一个扫不到的包下面,一直debug都是null;差点砸电脑...)

@Component
public class SpringContextUtil implements ApplicationContextAware {

    // Spring应用上下文环境
    private static ApplicationContext applicationContext;

    /**
     * 实现ApplicationContextAware接口的回调方法,设置上下文环境
     *
     * @param applicationContext
     */
    public void setApplicationContext(ApplicationContext applicationContext) {
        SpringContextUtil.applicationContext = applicationContext;
    }

    /**
     * @return ApplicationContext
     */
    public static ApplicationContext getApplicationContext() {
        return applicationContext;
    }

    /**
     * 获取对象
     * 这里重写了bean方法,起主要作用
     * @param name
     * @return Object 一个以所给名字注册的bean的实例
     * @throws BeansException
     */
    public static Object getBean(String name) throws BeansException {
        return applicationContext.getBean(name);
    }
}
  • 运行结果
21:04:47.940 [http-nio-8080-exec-7] INFO 
c.g.framerwork.aspect.LoggerHelper - before current
time:1529413487940

执行了queryAll方法

21:04:47.940 [http-nio-8080-exec-7] INFO 
c.g.framerwork.aspect.LoggerHelper - afterReturning current
time:1529413487940

上面就是最最经典的方式,就是通过代理的方式来实现AOP的过程。

纯POJO切面 aop:config

注意这里和LoggerHelper的区别,这里的LoggerAspect并没有继承任何接口或者抽象类。

  • POJO 类定义
/**
 * @description: [描述文本]
 * @email: <a href="guolei.sgl@antfin.com"></a>
 * @author: guolei.sgl
 * @date: 18/6/20
 */
public class LoggerAspect {
    private static final Logger LOGGER =
    LoggerFactory.getLogger(LoggerHelper.class);

    public void before(){
        LOGGER.info("before current time:"+System.currentTimeMillis());
    }

    public void afterReturning() {
        LOGGER.info("afterReturning current time:"+System.currentTimeMillis());
    }
}
  • 配置文件
<!-- 定义通知内容,也就是切入点执行前后需要做的事情 -->
<bean id="loggerAspect"  
    class="com.glmapper.framerwork.aspect.LoggerAspect">
</bean>

<aop:config>
    <!--定义切面-->
    <aop:aspect ref="loggerAspect">
    	<aop:pointcut id="loggerPointCut"  expression=
    	"execution(* com.glmapper.framerwork.service.impl.*.*(..)) " />
    	<!-- 定义 Advice -->
    	<!-- 前置通知 -->
    	<aop:before pointcut-ref="loggerPointCut" method="before" />
    	<!-- 后置通知 -->
    	<aop:after-returning pointcut-ref="loggerPointCut"
    	method="afterReturning"/>
    </aop:aspect>
</aop:config>

注意这里LoggerAspect中的before和afterReturning如果有参数,这里需要处理下,否则会报 0 formal unbound in pointcut 异常。

@AspectJ 注解驱动方式

  • 定义切面类,并使用 @Aspect 进行标注
/**
 * @description: 使用Aspect注解驱动的方式
 * @email: <a href="guolei.sgl@antfin.com"></a>
 * @author: guolei.sgl
 * @date: 18/6/20
 */
@Aspect
public class LoggerAspectInject {

    private static final Logger LOGGER = LoggerFactory.getLogger(LoggerAspectInject.class);

    @Pointcut("execution(* com.glmapper.framerwork.service.impl.*.*(..))")
    public void cutIn(){}

    @Before("cutIn()")
    public void before(){
        LOGGER.info("before current time:"+System.currentTimeMillis());
    }

    @AfterReturning("cutIn()")
    public void AfterReturning(){
        LOGGER.info("afterReturning current time:"+System.currentTimeMillis());
    }
}
  • 使用方式1:配置文件方式声明 bean
<aop:aspectj-autoproxy />
<!-- 定义通知内容,也就是切入点执行前后需要做的事情 -->
<bean id="sleepHelper"
    class="com.glmapper.framerwork.aspect.LoggerAspectInject">
</bean>
<!-- 定义被代理者 -->
<bean id="goodsServiceImpl"
    class="com.glmapper.framerwork.service.impl.GoodsServiceImpl">
</bean>

客户端使用:

@Controller
@RequestMapping("/buy")
public class BuyController {

    @Autowired
    private OrderService orderService;
    
    @RequestMapping("/initPage")
    public ModelAndView initPage(HttpServletRequest request,
    		HttpServletResponse response, ModelAndView view) {
        //通过SpringContextUtil手动获取 代理bean
    	GoodsService goodsService=(GoodsService)
    	SpringContextUtil.getBean("goodsServiceImpl");
    
    	List<Goods> goods = goodsService.queryAll(10,10);
    	view.addObject("goodsList", goods);
    	view.setViewName("goodslist");
    	return view;
    }
}
  • 使用方式2:使用@component注解托管给IOC
@Aspect
@Component //这里加上了Component注解,就不需要在xml中配置了
public class LoggerAspectInject {

    private static final Logger LOGGER =
    LoggerFactory.getLogger(LoggerAspectInject.class);

    @Pointcut("execution(* com.glmapper.framerwork.service.impl.*.*(..))")
    public void cutIn(){}

    @Before("cutIn()")
    public void before(){
        LOGGER.info("before current time:"+System.currentTimeMillis());
    }

    @AfterReturning("cutIn()")
    public void AfterReturning(){
        LOGGER.info("afterReturning current time:"+System.currentTimeMillis());
    }
}

客户端代码:

@Controller
@RequestMapping("/buy")
public class BuyController {

    @Autowired
    private OrderService orderService;
    //直接注入
    @Autowired
    private GoodsService goodsService;
    
    @RequestMapping("/initPage")
    public ModelAndView initPage(HttpServletRequest request,
    		HttpServletResponse response, ModelAndView view) {
    	
    	List<Goods> goods = goodsService.queryAll(10,10);
    	view.addObject("goodsList", goods);
    	view.setViewName("goodslist");
    	return view;
    }
}

比较完整的一个LoggerAspectInject,在实际工程中可以直接参考

/**
 * @description: aop
 * @email: <a href="henugl@1992.163.com"></a>
 * @author: glmapper@磊叔
 * @date: 18/6/4
 */
@Aspect
@Component
public class LoggerAspectInject {
    private static final Logger LOGGER= LoggerFactory.getLogger(LoggerAspectInject.class);
    
    @Pointcut("execution(* com.glmapper.book.web.controller.*.*(..))")
    public void cutIn(){

    }

    @Around("cutIn()")   // 定义Pointcut,名称即下面的标识"aroundAdvice
    public Object aroundAdvice(ProceedingJoinPoint poin){
        System.out.println("环绕通知");
        Object object = null;
        try{
            object = poin.proceed();
        }catch (Throwable e){
            e.printStackTrace();
        }
        return object;
    }

    // 定义 advise
    //这个方法只是一个标识,相当于在配置文件中定义了pointcut的id,此方法没有返回值和参数
    @Before("cutIn()")
    public void beforeAdvice(){
        System.out.println("前置通知");
    }

    @After("cutIn()")
    public void afterAdvice(){
        System.out.println("后置通知");
    }

    @AfterReturning("cutIn()")
    public void afterReturning(){
        System.out.println("后置返回 ");
    }

    @AfterThrowing("cutIn()")
    public void afterThrowing(){
        System.out.println("后置异常");
    }
}

关于命名切入点:上面的例子中cutIn方法可以被称之为命名切入点,命名切入点可以被其他切入点引用,而匿名切入点是不可以的。只有@AspectJ支持命名切入点,而Schema风格不支持命名切入点。 如下所示,@AspectJ使用如下方式引用命名切入点:

@Pointcut("execution(* com.glmapper.book.web.controller.*.*(..))")
public void cutIn(){
}
//引入命名切入点
@Before("cutIn()")
public void beforeAdvice(){
    System.out.println("前置通知");
}

注入式 AspectJ 切面

这种方式我感觉是第二种和第三种的结合的一种方式。

  • 定义切面类
/**
* @description: 注入式 也是一种通过XML方式配置的方式
* @email: <a href="guolei.sgl@antfin.com"></a>
* @author: guolei.sgl
* @date: 18/6/20
*/
public class LoggerAspectHelper {

    private static final Logger LOGGER = LoggerFactory.getLogger(LoggerAspectHelper.class);
    
    /**
     * 调动方法前执行
     * @param point
     * @throws Throwable
     */
    public void doBefore(JoinPoint point) throws Throwable {
        LOGGER.info("before current time:"+System.currentTimeMillis());
    }
    
    /**
     * 在调用方法前后执行
     * @param point
     * @return
     * @throws Throwable
     */
    public Object doAround(ProceedingJoinPoint point) throws Throwable
    {
        LOGGER.info("around current time:"+System.currentTimeMillis());
        if(point.getArgs().length>0) {
            return point.proceed(point.getArgs());
        }else{
            return point.proceed();
        }
    }
    
    /**
     * 在调用方法之后执行
     * @param point
     * @throws Throwable
     */
    public void doAfter(JoinPoint point) throws Throwable
    {
        LOGGER.info("after current time:"+System.currentTimeMillis());
    }
    
    /**
     * 异常通知
     * @param point
     * @param ex
     */
    public void doThrowing(JoinPoint point, Throwable ex)
    {
        LOGGER.info("throwing current time:"+System.currentTimeMillis());
    }

}
  • XML 配置
<bean id="loggerAspectHelper"    
    class="com.glmapper.framerwork.aspect.LoggerAspectHelper">
</bean>

<aop:config>
    <aop:aspect id="configAspect" ref="loggerAspectHelper">
    	<!--配置com.glmapper.framerwork.service.imp
    	包下所有类或接口的所有方法 -->
    	<aop:pointcut id="cutIn" expression=
    	"execution(* com.glmapper.framerwork.service.impl.*.*(..))" />
    	
    	<aop:before   pointcut-ref="cutIn" method="doBefore" />
    	<aop:after    pointcut-ref="cutIn" method="doAfter" />
    	<aop:around   pointcut-ref="cutIn" method="doAround" />
    	<aop:after-throwing pointcut-ref="cutIn" 
    	    method="doThrowing" throwing="ex" />
    	
    </aop:aspect>
</aop:config>
  • 结果
23:39:48.756 [http-nio-8080-exec-4] INFO  c.g.f.aspect.LoggerAspectHelper
- before current time:1529509188756
23:39:48.757 [http-nio-8080-exec-4] INFO  c.g.f.aspect.LoggerAspectHelper
- around current time:1529509188757
excute queryAll method...
23:39:48.757 [http-nio-8080-exec-4] INFO  c.g.f.aspect.LoggerAspectHelper
- after current time:1529509188757

表达式

execution

用于匹配方法执行的连接点;

execution(* com.glmapper.book.web.controller.*.*(..))
  • execution()表达式的主体;
  • 第一个 "*" 符号表示返回值的类型任意;
  • com.glmapper.book.web.controller AOP所切的服务的包名,即,我们的业务部分
  • 包名后面的"." 表示当前包及子包
  • 第二个"*" 表示类名,即所有类
  • .*(..) 表示任何方法名,括号表示参数,两个点表示任何参数类型

within

用于匹配指定类型内的方法执行;

//如果在com.glmapper.book.web.controller包或其下的任何子包中
//定义了该类型,则在Web层中有一个连接点。
within(com.glmapper.book.web.controller..*)

@Pointcut("within(com.glmapper.book.web.controller..*)")
public void cutIn(){}

@within:用于匹配所以持有指定注解类型内的方法;

/**
 * @description: 注解定义
 * @email: <a href="henugl@1992.163.com"></a>
 * @author: glmapper@磊叔
 * @date: 18/6/4
 */
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD,ElementType.FIELD})
public @interface AuthAnnotation {
}

任何目标对象对应的类型持有AuthAnnotation注解的类方法;必须是在目标对象上声明这个注解,在接口上声明的对它不起作用。

@within(com.glmapper.book.common.annotaion.AuthAnnotation)

//所有被@AdviceAnnotation标注的类都将匹配
@Pointcut("@within(com.glmapper.book.common.annotaion.AuthAnnotation)") 
public void cutIn(){}

this

用于匹配当前AOP代理对象类型的执行方法;注意是AOP代理对象的类型匹配,这样就可能包括引入接口也类型匹配;this中使用的表达式必须是类型全限定名,不支持通配符;

//当前目标对象(非AOP对象)实现了 UserService 接口的任何方法
this(com.glmapper.book.web.service.UserService)

//用于向通知方法中传入代理对象的引用。
@Before("cutIn() && this(proxy)")
public void beforeAdvice(ProceedingJoinPoint poin,Object proxy){
    System.out.println("前置通知");
}

target

用于匹配当前目标对象类型的执行方法;注意是目标对象的类型匹配,这样就不包括引入接口也类型匹配;target中使用的表达式必须是类型全限定名,不支持通配符;

//当前目标对象(非AOP对象)实现了 UserService 接口的任何方法
target(com.glmapper.book.web.service.UserService)

//用于向通知方法中传入代理对象的引用。
@Before("cutIn() && target(proxy)")
public void beforeAdvice(ProceedingJoinPoint poin,Object proxy){
    System.out.println("前置通知");
}

@target:用于匹配当前目标对象类型的执行方法,其中目标对象持有指定的注解;任何目标对象持有Secure注解的类方法;这个和@within一样必须是在目标对象上声明这个注解,在接口上声明的对它同样不起作用。

@target(com.glmapper.book.common.annotaion.AuthAnnotation)

@Pointcut("@target(com.glmapper.book.common.annotaion.AuthAnnotation)")
public void cutIn(){}

args

用于匹配当前执行的方法传入的参数为指定类型的执行方法;参数类型列表中的参数必须是类型全限定名,通配符不支持;args属于动态切入点,这种切入点开销非常大,非特殊情况最好不要使用;

//任何一个以接受“传入参数类型为java.io.Serializable”开头,
//且其后可跟任意个任意类型的参数的方法执行,
//args指定的参数类型是在运行时动态匹配的
args (java.io.Serializable,..)

//用于将参数传入到通知方法中。
@Before("cutIn() && args(age,username)")
public void beforeAdvide(JoinPoint point, int age, String username){
  //...
}

@args:用于匹配当前执行的方法传入的参数持有指定注解的执行;任何一个只接受一个参数的方法,且方法运行时传入的参数持有注解AuthAnnotation;动态切入点,类似于arg指示符;

@args (com.glmapper.book.common.annotaion.AuthAnnotation)

@Before("@args(com.glmapper.book.common.annotaion.AuthAnnotation)")
public void beforeAdvide(JoinPoint point){
  //...
}

@annotation

使用“@annotation(注解类型)”匹配当前执行方法持有指定注解的方法;注解类型也必须是全限定类型名;

//当前执行方法上持有注解 AuthAnnotation将被匹配
@annotation(com.glmapper.book.common.annotaion.AuthAnnotation)

//匹配连接点被它参数指定的AuthAnnotation注解的方法。
//也就是说,所有被指定注解标注的方法都将匹配。
@Pointcut("@annotation(com.glmapper.book.common.annotaion.AuthAnnotation)")
public void cutIn(){}

还有一种是bean的方式,没用过。有兴趣可以看看。

例子在下面说到的基础概念部分对应给出。

基础概念

基础概念部分主要将 AOP 中的一些概念点捋一捋,这部分主要参考了官网上的一些解释。

AOP

AOP(Aspect-Oriented Programming) , 即 面向切面编程 , 它与 OOP ( Object-Oriented Programming , 面向对象编程) 相辅相成, 提供了与 OOP 不同的抽象软件结构的视角。在 OOP 中,我们以类(class)作为我们的基本单元, 而 AOP 中的基本单元是 Aspect(切面)

横切关注点( Cross Cutting Concern ):独立服务,如系统日志。如果不是独立服务(就是与业务耦合比较强的服务)就不能横切了。通常这种独立服务需要遍布系统各个角落,遍布在业务流程之中。

Target Object

目标对象。织入 advice 的目标对象。 目标对象也被称为 advised object 。 因为 Spring AOP 使用运行时代理的方式来实现 aspect, 因此 adviced object 总是一个代理对象(proxied object);注意, adviced object 指的不是原来的类, 而是织入 advice 后所产生的代理类。

织入(Weave)

Advice 应用在 JoinPoint 的过程,这个过程叫织入。从另外一个角度老说就是将 aspect 和其他对象连接起来, 并创建 adviced object 的过程。

根据不同的实现技术, AOP 织入有三种方式:

Java
Advice

Spring 采用动态代理织入, 而AspectJ采用编译器织入和类装载期

代理

Spring AOP默认使用代理的是标准的JDK动态代理。这使得任何接口(或一组接口)都可以代理。

Spring AOP也可以使用CGLIB代理。如果业务对象不实现接口,则默认使用CGLIB。对接口编程而不是对类编程是一种很好的做法;业务类通常会实现一个或多个业务接口。在一些特殊的情况下,即需要通知的接口上没有声明的方法,或者需要将代理对象传递给具体类型的方法,有可能强制使用CGLIB。

Introductions

我们知道Java语言本身并非是动态的,就是我们的类一旦编译完成,就很难再为他添加新的功能。但是在一开始给出的例子中,虽然我们没有向对象中添加新的方法,但是已经向其中添加了新的功能。这种属于向现有的方法添加新的功能,那能不能为一个对象添加新的方法呢?答案肯定是可以的,使用introduction就能够实现。

introduction:动态为某个类增加或减少方法。为一个类型添加额外的方法或字段。Spring AOP 允许我们为 目标对象 引入新的接口(和对应的实现)。

Aspect

切面:通知和切入点的结合。

切面实现了cross-cutting(横切)功能。最常见的是logging模块、方法执行耗时模块,这样,程序按功能被分为好几层,如果按传统的继承的话,商业模型继承日志模块的话需要插入修改的地方太多,而通过创建一个切面就可以使用AOP来实现相同的功能了,我们可以针对不同的需求做出不同的切面。

而将散落于各个业务对象之中的Cross-cutting concerns 收集起来,设计各个独立可重用的对象,这些对象称之为Aspect;在上面的例子中我们根据不同的配置方式,定义了四种不同形式的切面。

Joinpoint

Aspect 在应用程序执行时加入业务流程的点或时机称之为 Joinpoint ,具体来说,就是 Advice 在应用程序中被呼叫执行的时机,这个时机可能是某个方法被呼叫之前或之后(或两者都有),或是某个异常发生的时候。

Joinpoint & ProceedingJoinPoint

环绕通知 = 前置+目标方法执行+后置通知,proceed方法就是用于启动目标方法执行的。

环绕通知 ProceedingJoinPoint 执行 proceed 方法 的作用是让目标方法执行 ,这 也是环绕通知和前置、后置通知方法的一个最大区别。

Proceedingjoinpoint 继承了 JoinPoint 。是在JoinPoint的基础上暴露出 proceed 这个方法。proceed很重要,这个是aop代理链执行的方法;暴露出这个方法,就能支持 aop:around 这种切面(其他的几种切面只需要用到JoinPoint,这跟切面类型有关), 能决定是否走代理链还是走自己拦截的其他逻辑。

在环绕通知的方法中是需要返回一个Object类型对象的,如果把环绕通知的方法返回类型是void,将会导致一些无法预估的情况,比如:404。

Pointcut

匹配 join points 的谓词。 Advice 与切入点表达式相关联, 并在切入点匹配的任何连接点上运行。(例如,具有特定名称的方法的执行)。由切入点表达式匹配的连接点的概念是 AOP 的核心, Spring 默认使用 AspectJ 切入点表达式语言。

Spring 中, 所有的方法都可以认为是 Joinpoint , 但是我们并不希望在所有的方法上都添加 Advice , 而 Pointcut 的作用就是提供一组规则(使用 AspectJ pointcut expression language 来描述) 来匹配 Joinpoint , 给满足规则的 Joinpoint 添加 Advice

Pointcut 和 Joinpoint

Spring AOP 中, 所有的方法执行都是 join point 。 而 point cut 是一个描述信息,它修饰的是 join point , 通过 point cut ,我们就可以确定哪些 join point 可以被织入 Advice 。 因此 join pointpoint cut 本质上就是两个不同维度上的东西。

advice 是在 join point 上执行的, 而 point cut 规定了哪些 join point 可以执行哪些 advice

Advice

概念

Advice 是我们切面功能的实现,它是切点的真正执行的地方。比如像前面例子中打印时间的几个方法(被@Before等注解标注的方法都是一个通知);Advice 在 Jointpoint 处插入代码到应用程序中。

分类

BeforeAdvice,AfterAdvice,区别在于Advice在目标方法之前调用还是之后调用,Throw Advice 表示当目标发生异常时调用Advice。

  • before advice: 在 join point 前被执行的 advice. 虽然 before advice 是在 join point 前被执行, 但是它并不能够阻止 join point 的执行, 除非发生了异常(即我们在 before advice 代码中, 不能人为地决定是否继续执行 join point 中的代码)
  • after return advice: 在一个 join point 正常返回后执行的 advice
  • after throwing advice: 当一个 join point 抛出异常后执行的 advice
  • after(final) advice: 无论一个 join point 是正常退出还是发生了异常, 都会被执行的 advice.
  • around advice:在 join point 前和 joint point 退出后都执行的 advice. 这个是最常用的 advice.

Advice、JoinPoint、PointCut 关系

聊一聊 AOP &lt;上&gt;

下面这张图是在网上一位大佬的博客里发现的,可以帮助我们更好的理解这些概念之间的关系。

聊一聊 AOP &lt;上&gt;

一些坑

在调试程序过程中出现的一些问题记录

1、使用AOP拦截controller层的服务成功,但是页面报错404

@Around("cutIn()")
public void aroundAdvice(ProceedingJoinPoint poin) {
    System.out.println("环绕通知");
}

这里需要注意的是再使用环绕通知时,需要给方法一个返回值。

@Around("cutIn()")
public Object aroundAdvice(ProceedingJoinPoint poin) throws Throwable {
    System.out.println("环绕通知");
    return poin.proceed();
}

2、0 formal unbound in pointcut

在spring 4.x中 提供了aop注解方式 带参数的方式。看下面例子:

@Pointcut(value = "execution(* com.glmapper.framerwork.service.impl.*(int,int)) && args(i,j)")  
public void cutIn(int i, int j) {}  
  
@Before(value="cutIn(i, j)",argNames = "i,j")  
public void beforeMethod( int i, int j) {  
    System.out.println("---------begins with " + i + "-" +j);  
}

比如说这里,Before中有两个int类型的参数,如果此时我们在使用时没有给其指定参数,那么就会抛出: Caused by: java.lang.IllegalArgumentException: error at ::0 formal unbound in pointcut 异常信息。

本来是想放在一篇里面的,但是实在太长了,就分开吧;周末更新下

原文  https://juejin.im/post/5b1ca657f265da6e5a205c45
正文到此结束
Loading...