聊一聊 AOP :表现形式与基础概念

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

系列目录

本系列分为 上、中、下三篇。上篇主要是介绍若是使用 AOP ,提供了demo和配置方式说明;中篇来对实现 AOP 的技术原理进行分析;下篇主要针对Spring中对于AOP的实现进行源码分析。git

项目地址

项目地址:glmapper-ssm-parentgithub

这个项目里面包含了下面几种 AOP 实现方式的全部代码,有兴趣的同窗能够fork跑一下。这个demo中列举了4中方式的实现:web

  • 基于代码的方式
  • 基于纯POJO类的方式
  • 基于Aspect注解的方式
  • 基于注入式Aspect的方式

目前咱们常常用到的是基于Aspect注解的方式的方式。下面来一个个了解下不一样方式的表现形式。正则表达式

基于代理的方式

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

一、首先定义一个接口: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工具类来获取代理对象的。express

@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的过程。bash

纯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 注解标注咱们的切面类便可。

一、定义切面类,并使用 @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="loggerAspectInject" 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

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

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中涉及到的一些基本概念及它们之间的关系作了简单的梳理。

一些坑

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

一、使用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();
}
复制代码

二、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 异常信息。

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

相关文章
相关标签/搜索