Spring框架从入门到入土(四):AOP面向切面编程详解

Spring框架从入门到入土(四):AOP面向切面编程详解

动态代理复习

动态代理是指,程序在整个运行的过程中根本不存在目标类的代理类,目标对象的代理对象只是由代理生成工具(不是真实定义的类)在程序运行时由JVM根据反射等机制动态生成的。代理对象与目标对象的代理关系在程序运行时才确立。

动态代理实现业务新增功能

原来的测试项目:

//接口
package com.liar.service;
/**
 * @author liar
 * @date 编写时间: 2022/3/22 15:12
 */
public interface SomeService {
    void doSome();
    void doOther();
}

//接口实现
package com.liar.service.impl;
import com.liar.Utils.ServiceTools;
import com.liar.service.SomeService;

/**
 * @author liar
 * @date 编写时间: 2022/3/22 15:14
 */
public class SomeServiceImpl implements SomeService {
    @Override
    public void doSome() {
        ServiceTools.doLog();
        System.out.println("执行了someserviceimpl的dosome方法");
        ServiceTools.doTrans();
    }
    @Override
    public void doOther() {
        ServiceTools.doLog();
        System.out.println("执行了someserviceimpl的doother方法");
        ServiceTools.doTrans();
    }
}

//工具类
package com.liar.Utils;
import java.util.Date;
/**
 * @author liar
 * @date 编写时间: 2022/3/27 16:49
 */
public class ServiceTools {

    public  static void doLog(){
        System.out.println("非业务方法的执行时间:"+ new Date());
    }

    public  static void doTrans(){
        System.out.println("非业务方法的执行完毕,提交事务!");
    }
}

//主测试
package com.liar;
import com.liar.service.SomeService;
import com.liar.service.impl.SomeServiceImpl;
/**
 * @author liar
 * @date 编写时间: 2022/3/27 16:47
 */
public class MyApp {
    public static void main(String[] args) {
        //调用doSome doOther

        SomeService service = new SomeServiceImpl();
        service.doSome();
        System.out.println("-------------------------");
        service.doOther();
    }
}

测试项目结构:

image-20220327165710863

改造实现步骤:

  1. 创建目标类,SomeServiceImpl目标类,给它的方法增加输出时间和事务
  2. 创建InvocationHandler接口的实现类,这个类给目标方法增加功能
  3. 使用jdk类中proxy,创建代理对象,实现创建对象的能力
//创建InvocationHandler接口的实现类,这个类给目标方法增加功能
package com.liar.handler;

import com.liar.Utils.ServiceTools;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;

/**
 * @author liar
 * @date 编写时间: 2022/3/27 19:37
 */
public class MyInvocationHandler implements InvocationHandler {

    private Object target;

    public MyInvocationHandler(Object target) {
        this.target = target;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("执行MyInvocationHandler中的invoke方法()");
        //通过代理对象执行方法,会调用这个invoke()
        Object res = null;
        ServiceTools.doLog();

        //执行目标类方法,通过method类实现
        res = method.invoke(target, args);
        ServiceTools.doTrans();
        return null;
    }
}
//使用jdk类中proxy,创建代理对象,实现创建对象的能力
package com.liar;
import com.liar.handler.MyInvocationHandler;
import com.liar.service.SomeService;
import com.liar.service.impl.SomeServiceImpl;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;

/**
 * @author liar
 * @date 编写时间: 2022/3/27 16:47
 */
public class MyApp {
    public static void main(String[] args) {
        //使用jdk的Proxy代理对象
        //创建目标对象
        SomeService target = new SomeServiceImpl();

        //创建InvocationHandler对象
        InvocationHandler handler = new MyInvocationHandler(target);

        //使用Proxy代理
        SomeService proxy = (SomeService) Proxy.newProxyInstance(
                target.getClass().getClassLoader(),
                target.getClass().getInterfaces(),handler);

        //通过代理执行方法
        proxy.doSome();
    }
}
  • 执行结果

image-20220327200514222

这种动态代理没有统一的标准,有时候会出现问题!

动态代理的作用

  1. 在目标类源代码不改变的情况下,增加功能
  2. 减少代码的重复
  3. 专注于业务逻辑代码
  4. 解耦合,让你的业务功能和日志,事务非业务功能分离。

AOP(Aspect Orient Programming)面向切面编程

简介:

Aop面向切面编程,基于动态代理,可以使用jdk,cglib两种代理方式

其实Aop就是一个动态代理的规范化,把动态代理的实现步骤,方式都定义好了,让开发人员选一种统一的方式,使用动态代理。

  • Aspect: 切面,给你的目标类增加的功能,就是切面。比如:日志,事务处理

    ​ 切面的特点:一般都是非业务的方法,独立使用

  • Orient :面向

  • Programming:编程

怎样理解面向切面编程?

  1. 需要在分析项目功能时候,找出切面
  2. 合理安排切面的执行时间,在目标方法之前还是目标方法之后
  3. 合理安排安全切面的位置,在哪个类,哪个方法增加增强功能

aop的技术实现框架:

  1. spring:spring内部实现了aop规范,能做aop的工作

    spring主要在事务处理使用aop

    一般不会使用spring的aop实现,因为比较笨重

  2. aspectj:一个开源的专门做aop的框架,spring框架继承了aspectj框架,通过spring框架就可以使用aspectj的功能。

    • 使用xml配置文件实现:配置全局事务
    • 使用注解实现

Aspectj的相关术语

  • Aspect:切面,表示增强功能,完成某一个功能,非业务功能。

    常见的切面功能有:日志,事务,统计信息,参数检查,权限验证

  • JoinPoint:连接点,连接任务业务和切面的位置,就某类中的业务方法

  • Pointcut:切入点,指多个连接点的方法集合,多个方法

  • 目标对象:给哪个类增加的功能,这个类就是目标对象

  • Advice:通知,通知表示切面功能的执行时间

一个切面有三个关键的要素:

  1. 切面的功能代码,就是切面要干什么?
  2. 切面执行位置,使用Pointcut表示
  3. 切面的执行时间,用Advice表示,在目标方法之前,还是在目标方法之后

Aspectj的切入点表达式

Aspectj定义了专门的表达式用于切入点。表达式的原型是

excution(modifiers-pattern? ret-type-pattern 
         declaring-type-pattern?name-pattern(param-pattern) 
         throws-pattern)
  • modifiers-pattern:访问权限类型

  • ret-type-pattern :返回值类型

  • declaring-type-pattern:包名类名

  • name-pattern(param-pattern):方法名(参数类型和参数个数)

  • throws-pattern 抛出异常类型

    ?表示可选部分

excution(访问权限类型 方法返回值类型 方法声明(参数) 异常类型)

切入点表达式要匹配的对象就是目标方法的方法名,所以excution表达式中明显就是方法的签名。注意,没有加粗部分可省略,各部分用;隔开。在其中可以使用以下符号:

符号意义
*0到多个任意字符
用在多个方法参数中,表示多个参数用在包名后,表示当前包及其子包路径
+用在类名后,表示当前类和子类,用在接口后,表示当前接口和实现类

举例子:

image-20220328005559473

image-20220328010217678

使用Aspectj切入点表达式
  1. 新建maven项目

  2. 加入依赖(spring依赖和aspectj依赖…)

  <dependencies>
    <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>4.11</version>
      <scope>test</scope>
    </dependency>
    <!--spring的依赖-->
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-context</artifactId>
      <version>5.3.16</version>
    </dependency>
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-aspects</artifactId>
      <version>5.2.5.RELEASE</version>
    </dependency>
  </dependencies>
  1. 创建目标类:接口和它的实现类 要做的是给类中的方法实现功能
//接口
package com.liar.aspectj01;

/**
 * @author liar
 * @date 编写时间: 2022/3/22 15:12
 */
public interface SomeService {
    
    /**
     * 测试方法1
     * @param name
     * @param age
     */
    void doSome(String name,Integer age);
}
//实现类
package com.liar.aspectj01;

/**
 * @author liar
 * @date 编写时间: 2022/3/22 15:14
 */
public class SomeServiceImpl implements SomeService {
    @Override
    public void doSome(String name,Integer age) {
        //给doSome方法增加一个功能
        //在方法前增加一个方法的执行时间
        System.out.println("执行了目标dosome方法");
        System.out.println("我是"+name+","+"今年"+age+"岁"+"。我是全世界最靓的仔!");
    }
                        
  1. 创建切面类:普通类
  • 在类的上面加上@Aspect

  • 在类中定义方法,方法就是切面需要执行的功能代码

    在方法上面加入aspectj中的通知注解,例如@Before,有需要指定切入点表达式excution()

package com.liar.aspectj01;

import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;

import java.util.Date;

/**
 * @author liar
 * @date 编写时间: 2022/3/28 16:10
 * @Aspect: 是aspectj框架中的类
 * 作用:表示当前类是切面类
 * 切面类:用来给方法增加功能的类,在这个类中有切面功能的代码
 * 使用位置:在类中定义的上面
 */
@Aspect
public class MyAspect {

    /*
     * 定义方法,方法是实现切面功能的
     * 方法的定义要求:
     * 1. 公共方法 public
     * 2. 方法没有返回值
     * 3. 方法名称自定义
     * 4. 方法可以有参数,也可以没有参数
     *    如果有参数,参数不是自定义的,有几个参数类型可以使用
     */

    /**
     * @Before: 前置通知注解
     * 属性:value,是切入点的表达式,表示切面的功能执行位置
     * 位置:在方法上面
     * 特点:
     *    1. 目标方法之前先执行
     *    2. 不会改变目标方法的执行结果
     *    3. 不会影响目标方法的执行
     */

    @Before(value = "execution(public void com.liar.aspectj01.SomeServiceImpl.doSome(String,Integer))")
    public void myBefore(){
        //就是切面要执行的功能代码
        System.out.println("前置通知,切面功能:在目标方法之前输出时间:"+new Date());
    }
}
  1. 创建spring的配置文件:声明对象,把对象交给容器统一管理,声明对象可以使用注解或者xml文件
  • 声明标定对象

  • 声明切面对象

  • 声明aspectj框架中的自动代理生成器标签。

    自动代理生成器:用来完成代理对象的自动创建功能。

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans.xsd
       http://www.springframework.org/schema/aop
       https://www.springframework.org/schema/aop/spring-aop.xsd">

<!--把对象交给spring容器创建由容器统一管理,创建对象-->
<!--声明目标对象-->
    <bean id="someService" class="com.liar.aspectj01.SomeServiceImpl"/>
<!--声明切面类对象-->
    <bean id="myAspect" class="com.liar.aspectj01.MyAspect"/>
<!--声明自动代理生成器:
    使用aspectj框架内部功能,创建目标对象的代理对象
    创建代理对象是在内存中实现的,修改目标对象的内存的结构,创建为代理对象
    所以目标对象就是被修改后的代理对象

    aspectj-autoproxy:会把容器中的所有的目标对象,一次性生成代理对象
-->
    <aop:aspectj-autoproxy/>
</beans>
  1. 创建测试类,从spring容器中获取目标对象,实际上就是代理对象。
package com.liar;

import com.liar.aspectj01.SomeService;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

/**
 * @author liar
 * @date 编写时间: 2022/3/28 19:44
 */
public class MyTest01 {

    @Test
    public void test01(){
        String config = "applicationContext.xml";
        ApplicationContext context = new ClassPathXmlApplicationContext(config);
        //从容器中取出对象
        SomeService proxy = (SomeService) context.getBean("someService");
        //通过代理对象执行方法,实现目标执行时增强了功能
        proxy.doSome("李四",18);
    }
}
切点表达式的多种写法
//原始
@Before(value = "execution(public void com.liar.aspectj01.SomeServiceImpl.doSome(String,Integer))")
//去掉public
@Before(value = "execution(void com.liar.aspectj01.SomeServiceImpl.doSome(String,Integer))")
//包路径用通配符表示
@Before(value = "execution(void *..SomeServiceImpl.doSome(String,Integer))")
//返回值用通配符代替
@Before(value = "execution(* *..SomeServiceImpl.doSome(String,Integer))")
//通配符代替
@Before(value = "execution(* *..SomeServiceImpl.do*(..))")
//通配符代替
@Before(value = "execution(* do*(..))")

Aspectj基于注解的Aop实现

切面的执行时间,这个执行时间在规范中叫Advice(通知,增强)使用注解表示有:

  1. @Before
  2. @AfterReturning
  3. @Around
  4. @AfterThrowing
  5. @After

注解1@Before在上面的例子已有展示!!

JoinPoint参数
  • 指定通知方法中的参数:JoinPoint
  • JoinPoint:业务方法,要加入切面功能的业务方法
  • 作用是:可以在通知方法中获取方法的执行时的信息,例如方法名称,方法的实参
  • 如果你的切面功能中需要用到方法的信息,就加入JoinPoint
  • 这个JoinPoint的值由框架赋予,必须是第一个位置的参数
    /**
     * 指定通知方法中的参数:JoinPoint
     * JoinPoint:业务方法,要加入切面功能的业务方法
     * 作用是:可以在通知方法中获取方法的执行时的信息,例如方法名称,方法事的实参
     * 如果你的切面功能中需要用到方法的信息,就加入JoinPoint
     * 这个JoinPoint的值由框架赋予,必须是第一个位置的参数
     */
    @Before(value = "execution(void *..SomeServiceImpl.doSome(String,Integer))")
    public void myBefore(JoinPoint join){
        //获取方法的完整定义
        System.out.println("方法的签名(定义):"+join.getSignature());
        System.out.println("方法的名称:"+join.getSignature().getName());
        //获取方法的实参
        Object[] args = join.getArgs();
        for (Object obj :
                args) {
            System.out.println("方法的参数是:"+obj);
        }
        //就是切面要执行的功能代码
        System.out.println("前置通知,切面功能:在目标方法之前输出时间:"+new Date());
    }
}
  • 测试结果

image-20220328235354854

后置通知@AfterReturning
  1. 接口和实现类
    /**
     * 测试方法2
     * @param name
     * @param age
     * @return
     */
    String doOther(String name,Integer age);

    /**
     * 测试方法3
     * @param name
     * @param age
     * @return
     */
    Student doStudent(String name,Integer age);
    @Override
    public String doOther(String name, Integer age) {
        System.out.println("执行了目标doOther方法");
        //在方法之后增加一个功能
        return "Hello aop!!";
    }

    @Override
    public Student doStudent(String name, Integer age) {
        System.out.println("执行了目标doStudent方法");
        Student stu = new Student();
        stu.setName(name);
        stu.setAge(age);
        return stu;
    }
  1. 切面类
package com.liar.aspectj02;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;

import java.util.Date;

/**
 * @author liar
 * @date 编写时间: 2022/3/28 16:10
 * @Aspect: 是aspectj框架中的类
 * 作用:表示当前类是切面类
 * 切面类:用来给方法增加功能的类,在这个类中有切面功能的代码
 * 使用位置:在类中定义的上面
 */
@Aspect
public class MyAspect {

    /*
     * 后置通知的定义方法,方法是实现切面功能的
     * 方法的定义要求:
     * 1. 公共方法 public
     * 2. 方法没有返回值
     * 3. 方法名称自定义
     * 4. 方法有参数,推荐是Object,参数名自定义
     */

    /**
     * @AfterReturning: 后置通知
     * 属性:1. value 切入点表达式
     *      2. returning 自定义的一个变量,表示目标方法的返回值
     *         自定义的变量名和通知方法的形参名一样
     * 位置:在方法定义的上面
     * 特点:
     * 1. 在目标方法执行之后
     * 2. 能够获取到目标方法的返回值,可以根据这个返回值做不同的处理功能
     *    Object res = doOther();
     * 3. 可以修改这个返回值
     *
     * 后置通知的执行
     * Object res = doOther();
     * myAfterReturning(res);
     */
    @AfterReturning(value = "execution(* *..SomeServiceImpl.doOther(..))",returning = "obj")
    public void myAfterReturning(Object obj){
        //Object res是目标方法执行后的返回值,根据返回值做你的切面的功能处理
        System.out.println("后置通知:在目标方法执行后,获得的返回值是:"+obj);

        //修改目标方法的返回值,看一下是否会影响最后方法的调用结果
        if(obj!=null){
            obj = "Hello Aspectj!!!";
        }
    }

    /**
     * 当返回参数是一个对象
     */

    @AfterReturning(value = "execution(* *..SomeServiceImpl.doStudent(..))",returning = "obj")
    public void myAfterReturning2(Object obj){
        //Object res是目标方法执行后的返回值,根据返回值做你的切面的功能处理
        System.out.println("后置通知:在目标方法执行后,获得的返回值是:"+obj);
        //修改目标方法的返回值,看一下是否会影响最后方法的调用结果
        //如果修改了res的内容,属性值等,是不是会影响后面的结果呢
        if(obj!=null){
            Student stu = (Student) obj;
            stu.setName("小张");
            stu.setAge(20);
        }
    }
}
  1. 配置文件和Student类省略,测试方法如下
    @Test
    public void test01(){
        String config = "applicationContext.xml";
        ApplicationContext context = new ClassPathXmlApplicationContext(config);
        //从容器中取出对象
        SomeService proxy = (SomeService) context.getBean("someService");
        //通过代理对象执行方法,实现目标执行时增强了功能
        String str = proxy.doOther("小梁",18);
        System.out.println("结果是:"+str);
        Student student = proxy.doStudent("小梁", 18);
        System.out.println(student);
    }
  1. 测试结果

    如果是不可变的字符串,那么之后修改不会变化,如果是可变化的对象,会改变对象的值。

环绕通知@Around
  1. 接口和实现类
package com.liar.aspectj03;

/**
 * @author liar
 * @date 编写时间: 2022/3/22 15:12
 */
public interface SomeService {

    /**测试环绕通知
     * @param name
     * @param age
     * @return
     */
    String doFirst(String name,Integer age);
}
package com.liar.aspectj03;

/**
 * @author liar
 * @date 编写时间: 2022/3/22 15:14
 */
public class SomeServiceImpl implements SomeService {

    @Override
    public String doFirst(String name, Integer age) {
        //在方法前增加一个方法的执行时间
        System.out.println("执行了目标doFirst方法");
        System.out.println("我是"+name+","+"今年"+age+"岁"+"。我是全世界最靓的仔!");
        return "Hello Around";
    }
}
  1. 切面类
package com.liar.aspectj03;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;

import java.util.Date;

/**
 * @author liar
 * @date 编写时间: 2022/3/28 16:10
 * @Aspect: 是aspectj框架中的类
 * 作用:表示当前类是切面类
 * 切面类:用来给方法增加功能的类,在这个类中有切面功能的代码
 * 使用位置:在类中定义的上面
 */
@Aspect
public class MyAspect {

    /**
     * 环绕通知的定义格式
     * 1. public
     * 2. 必须有一个返回值,推荐使用Object
     * 3. 方法名称自定义
     * 4. 方法有参数,固定的参数ProceedingJoinPoint
     */

    /**
     * @Around :环绕通知
     * 属性:value 切入点表达式
     * 位置:在方法的定义上面
     * 特点:
     * 1. 他是功能最强的通知
     * 2. 在目标方法的前和后都能增强功能
     * 3, 控制目标方法的折执行结果,影响最后的调用结果
     * @param point ProceedingJoinPoint等同于method
     *              作用:执行目标方法
     * @return 返回值:就是目标方法的执行结果,可以被修改
     * 环绕通知相当于jdk动态代理,InvocationHandler接口
     *
     * 环绕通知经常做事务,在目标方法之前开启事务,执行目标方法,在方法之后提交事务
     */
    @Around(value = "execution(* *..SomeServiceImpl.doFirst(..))")
    public Object myAround(ProceedingJoinPoint point) throws Throwable {

        String name = "";
        //获取第一个参数值
        Object[] args = point.getArgs();
        if(args != null && args.length > 1){
            Object arg = args[0];
            name = (String)arg;
        }
        //实现环绕通知
        Object result = null;
        //在目标方法之前加功能
        System.out.println("环绕通知,在目标方法之前时间"+new Date());
        //1. 目标方法的使用
        if ("小梁".equals(name)) {
            //符合条件调用目标方法
            result = point.proceed();
        }
        //在目标方法之后加功能
        System.out.println("环绕通知,在目标方法之后,提交事务");

        //修改目标方法的执行结果,影响后面的调用结果
        if(result != null){
            result = "Hello MyLove!";
        }
        return result;
    }
}
  1. 测试类
package com.liar;

import com.liar.aspectj03.SomeService;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

/**
 * @author liar
 * @date 编写时间: 2022/3/28 19:44
 */
public class MyTest03 {

    @Test
    public void test01(){
        String config = "applicationContext.xml";
        ApplicationContext context = new ClassPathXmlApplicationContext(config);
        //从容器中取出对象
        SomeService proxy = (SomeService) context.getBean("someService");
        //通过代理对象执行方法,实现目标执行时增强了功能
        String str = proxy.doFirst("小梁",18);
        System.out.println(str);
    }
}
@AfterThrowing异常通知(了解)
  1. 接口和实现类
    /**
     * 测试异常通知
     * @param name
     * @param age
     * @return
     */
    String doSecond(String name,Integer age);

//实现接口
    @Override
    public String doSecond(String name, Integer age) {
        System.out.println("执行了目标doSecond方法");
        System.out.println("我是"+name+","+"今年"+age+"岁"+"。我是全世界最靓的仔!");
        System.out.println(10/0);
        return "Hello Around";
    }
  1. 切面类
    /**
     * 异常方法的定义格式
     * 1. public
     * 2. 没有返回值
     * 3. 方法名称自定义
     * 4. 方法有一个参数Exception,如果还有就是JoinPoint
     */

    /**
     * @AfterThrowing :异常通知
     * 属性:1. value 切入点表达式
     *      2. throwing 自定义的变量,表示目标方法抛出的异常对象
     *         变量名和方法的参数名一样
     *
     * 特点:1. 在目标方法抛出异常的时候执行
     *      2. 可以做异常程序的监控,监控目标方法执行是不是有异常
     *         如果有异常,可以发送邮件或者短信通知
     */
    @AfterThrowing(value = "execution(* *..SomeServiceImpl.doSecond(..))",throwing = "e")
    public void myAfterThrowing(Exception e){
        System.out.println("异常通知:方法发生异常,执行:"+e.getMessage());
    }
  1. 测试方法
    @Test
    public void test02(){
        String config = "applicationContext.xml";
        ApplicationContext context = new ClassPathXmlApplicationContext(config);
        //从容器中取出对象
        SomeService proxy = (SomeService) context.getBean("someService");
        //通过代理对象执行方法,实现目标执行时增强了功能
        String str = proxy.doSecond("小梁",18);
        System.out.println(str);
    }
  1. 测试结果

image-20220329161815331

@After最终通知

无论方法是否抛出异常,该增强均会被执行。

其他测试代码省略,切面类如下:

    /**
     * 最终通知的定义格式
     * 1. public
     * 2. 没有返回值
     * 3. 方法名称自定义
     * 4. 方法没有参数,如果还有就是JoinPoint
     */

    /**
     * @After: 最终通知
     * 属性:value 切入点表达式
     * 位置:在方法的上面
     * 特点:1. 总是执行
     *      2. 在目标方法之后执行
     */
    @After(value = "execution(* *..SomeServiceImpl.doThird(..))")
    public void myAfter(){
        //一般做资源清楚的工作
        System.out.println("执行最终通知,总是会被执行的代码!");
    }

Pointcut定义切入点

当较多的通知增强方法使用相同的execution切入点表达式时,编写、维护均较为麻烦。
AspectJ提供了@Pointcut注解,用于定义execution切入点表达式。
其用法是,将@Pointcut注解在一个方法之上,以后所有的execution的value属性值均
可使用该方法名作为切入点。代表的就是@Pointcut定义的切入点。这个使用@Pointcut注解
的方法一般使用 private的标识方法,即没有实际作用的方法。

原来的切面类中的方法

    @Before(value = "execution(* *..SomeServiceImpl.doFirst(..))")
    public void myBefore(){
        System.out.println("前置通知,在目标方法之前执行!");
    }

    @After(value = "execution(* *..SomeServiceImpl.doFirst(..))")
    public void myAfter(){
        System.out.println("后置通知,在目标方法之后执行!");
    }

改造之后

package com.liar.aspectj04;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;

import java.util.Date;

/**
 * @author liar
 * @date 编写时间: 2022/3/28 16:10
 * @Aspect: 是aspectj框架中的类
 * 作用:表示当前类是切面类
 * 切面类:用来给方法增加功能的类,在这个类中有切面功能的代码
 * 使用位置:在类中定义的上面
 */
@Aspect
public class MyAspect {

    @Before(value = "myPointcut()")
    public void myBefore(){
        System.out.println("前置通知,在目标方法之前执行!");
    }

    @After(value = "myPointcut()")
    public void myAfter(){
        System.out.println("后置通知,在目标方法之后执行!");
    }

    /**
     * @Pointcut
     * 定义和管理切入点,如果你的项目中有多个切入点表达式是重复的,可以进行复用的
     * 可以使用Pointcut
     * 属性:value 切入点表达式
     * 位置:自定义的方法的上面
     * 特点:
     * 当使用@Pointcut定义在一个方法的上面,此时这个方法的名称就是切入点表达式的别名。
     * 其他的通知中,value属性就可以使用这个方法名称,代替切入点表达式
     */
    @Pointcut(value = "execution(* *..SomeServiceImpl.doFirst(..))")
    private void myPointcut(){
        //无需代码
    }
}

最后补充:

  1. 没有接口的话使用的是cglib代理
  2. 如果期望有接口也使用cglib代理,需要在配置文件中设置
    <!--    
     如果期望目标类有接口,使用clib代理
     proxy-target-class="true"告诉框架,要使用cglib代理
    -->
    <aop:aspectj-autoproxy proxy-target-class="true"/>
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值