AOP面向切面编程

目录

AOP面向切面编程

AOP的核心概念

AOP的简单入门案例:测试一个方法使用的时间

第一步:导坐标

第二步:编写SpringConfig配置类

第三步:编写dao层

第四步:编写通知类,并导入AOP所需要的坐标

第五步:让spring开启AOP功能

测试:

错误分析:

AOP的工作流程

AOP切入点表达式

使用通配符描述切入点

AOP通知类型

@Around注意事项

使用@Around环绕通知计算某个方法执行一万次的时间

AOP通知获取原方法的数据

获取切入点方法的参数

获取切入点方法返回值

获取切入点方法运行异常信息

小案例:去除密码空格


AOP面向切面编程

作用:在不改动源码的情况下进行功能增强

应用:

  • 在工程运行慢的过程中,对目标方法进行耗时分析
  • 对目标方法添加事务管理
  • 对目标方法添加权限访问控制
  • ...

AOP的核心概念

  • 连接点:程序执行过程中的任意位置,大多数为一个方法的执行
  • 切入点:匹配连接点的式子
  • 在springAOP中,一个切入点可以只描述一个方法,也可以匹配多个方法
  • 通知:在切入点处执行的操作,也就是增强的内容
  • 切面:描述通知与切入点的对应关系

AOP的简单入门案例:测试一个方法使用的时间

第一步:导坐标

html 复制代码
 <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>5.2.10.RELEASE</version>
        </dependency>
 <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.12</version>
        </dependency>

第二步:编写SpringConfig配置类

java 复制代码
@Configuration
@ComponentScan("com.hhh")
public class SpringConfig {

}

第三步:编写dao层

java 复制代码
//让这个类交给spring管理,变成bean
@Repository
public class UserMapperImpl implements UserMapper {
    @Override
    public void save() {
        System.out.println("UserMapper dao");
    }
}

第四步:编写通知类,并导入AOP所需要的坐标

html 复制代码
<dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjweaver</artifactId>
            <version>1.9.7</version>
        </dependency>
java 复制代码
//对谁增强
//什么时候增强
//增强什么东西
@Component
@Aspect
public class MyAdvice {//通知类
    private long begin;
    private long end;
    //对谁做增强
    @Pointcut("execution(void com.hhh.dao.UserMapper.save())")
    public void point(){
    }
    //什么时候增强,增强什么内容
    @Before("point()")
    public void before(){
        begin=System.currentTimeMillis();
        System.out.println(begin);
    }
    @After("point()")
    public void after(){
        end=System.currentTimeMillis();
        long result=end-begin;
        System.out.println(end);
        System.out.println("花费的时间为"+result);

    }

}

我们对这个类使用了两个注解

@Component把这个类交给spring管理

@Aspect告诉spring这是一个通知类

增强我们要知道对谁增强,什么时候增强,增强什么
对谁增强:

定义一个point方法,并加上注解@Pointcut

"execution(void com.hhh.dao.UserMapper.save())"这个就是一个切点表达式,也就是切入点切入点要写的有方法返回的类型,以及方法所在的位置。通过切入点我们也可以发现UserMapper类的save()方法就是连接点。这样一来point()方法就代表了save()方法
什么时候增强:

使用注解@Before和@After

增强什么东西:

在before(),after()方法进行增强内容的书写,书写的代码内容就是通知

注解加上方法就是切面-->描述了切入点和通知的关系

第五步:让spring开启AOP功能

写完通知类之后,还要再SpringConfig配置类中加上注解@EnableAspectJAutoProxy

java 复制代码
@Configuration
@ComponentScan("com.hhh")
//开启AOP功能
@EnableAspectJAutoProxy
public class SpringConfig {

}

测试:

java 复制代码
public class UserMapperTest {
    @Test
    public void test(){
        //加载配置类,创建IoC容器
        ApplicationContext ctx=new AnnotationConfigApplicationContext(SpringConfig.class);
        //UserMapper userMapperImpl = (UserMapper) ctx.getBean("userMapperImpl");
        UserMapper userMapper = ctx.getBean(UserMapper.class);
        //这里不能使用UserMapperImpl.class,因为在容器里的bean是实现类的代理类型
        userMapper.save();

    }

}

结果:

错误分析:

如果没有出现增强的内容

去看一下通知类有没有加@Component交给spring管理,有没有加@Aspect,切入点表达式有没有问题,SpringConfig配置类有没有加上注解 @EnableAspectJAutoProxy**,开启AOP功能**

AOP的工作流程

1.Spring容器启动

2.读取 所有切面配置中的切入点

3.初始化bean,判定bean对应的类中方法是否匹配到任意切入点

  • 匹配失败,创建对象
  • 匹配成功,创建原始对象的代理对象,成为目标对象

4.获取bean执行方法

  • 获取bean,调用方法并执行,完成操作
  • 获取的bean是代理对象时,根据代理对象的运行模式运行原始方法和增强的内容,完成操作

AOP切入点表达式

execution(void com.hhh.dao.UserMapper.save(int))

  1. 动作关键字:描述切入点的行为动作,例如execution
  2. 访问修饰符:public,private-->可以省略
  3. 返回值
  4. 报名
  5. 类/接口名
  6. 方法名
  7. 参数
  8. 异常名:方法定义中抛出指定异常,可以省略

使用通配符描述切入点

* : 单个独立的任意符号,可以独立出现,也可以作为前缀或者后缀的匹配符出现

.. : 多个连续的任意符号,可以独立出现 ,常用于简化包名与参数名的书写

如:

复制代码
@Pointcut("execution(* com.hhh.dao.*Mapper.*(..))")

返回类型使用*替代,*Mapper(UserMapper等)文件下的所有方法都作为连接点,而方法参数使用..

  • : 专用于匹配子类类型
复制代码
@Pointcut("execution(* com.hhh.dao.*Mapper+.*(..))")

包括了所有*Mapper文件的所有子类(即实现类等)

AOP通知类型

  • 前置通知-->@Before
  • 后置通知-->@After
  • 环绕通知 -->@Around
  • 返回后通知-->@AfterReturning(原方法执行完后执行)
  • 返回异常后通知-->@AfterThrowing 和返回后通知只有一个能执行
java 复制代码
@Component
@Aspect
public class MyAdvice {//通知类
    //对谁做增强
    @Pointcut("execution(* com.hhh.dao.*Mapper+.*(..))")
    public void point(){
    }
    //什么时候增强,增强什么内容
    @Before("point()")
    public void before(){
        System.out.println("before");
    }
    @After("point()")
    public void after(){
        System.out.println("after");

    }
    //环绕通知
    @Around("point()")
    public void around(ProceedingJoinPoint jointPoint) throws Throwable {
        System.out.println("around before");
        //执行被代理的方法(原方法)
        jointPoint.proceed();
        System.out.println("around after");
    }
    //返回后通知即原方法执行完后执行
    @AfterReturning("point()")
    public void afterReturning(){
        System.out.println("afterReturning");
    }
//返回异常后通知
    @AfterThrowing("point()")
    public void afterThrowing(){
        System.out.println("afterThrowing");
    }
}

结果:

@Around注意事项

1.环绕通知必须依赖形参ProceedingJoinPoint才能实现对原始方法的调用,进而实现原始方法调用前后增强

2.通知中如果未使用ProceedingJoinPoint对原始方法的调用,将跳过原始方法的执行

3.对原始方法的调用不接收返回值 ,通知方法设置成void即可,如果接收返回值,必须设置成Object类型,并返回

4.原始方法的返回值如果是void类型,通知方法返回值的类型可以设置为void,也可以为Object

5.由于无法预知原始方法运行后是否会抛出异常,因此环绕方法必须抛出Throwable对象

如:

java 复制代码
 @Override
    public Integer update() {
        System.out.println("UserMapper update");
        return 20;
    }

让原始方法返回20整型值

java 复制代码
public class UserMapperTest {
    @Test
    public void test(){
        //加载配置类,创建IoC容器
        ApplicationContext ctx=new AnnotationConfigApplicationContext(SpringConfig.class);
        //UserMapper userMapperImpl = (UserMapper) ctx.getBean("userMapperImpl");
        UserMapper userMapper = ctx.getBean(UserMapper.class);
        Integer i = userMapper.update();//接收返回值并打印
        System.out.println(i);
    }

}

结果:

可以发现无法拿到返回值,因为在@Around环绕通知里没有返回原方法的值

所以进行修改

java 复制代码
    //环绕通知
    @Around("point()")
    public Object around(ProceedingJoinPoint jointPoint) throws Throwable {
        System.out.println("around before");
        //执行被代理的方法(原方法)
        Object proceed = jointPoint.proceed();//接收原方法的返回值
        System.out.println("around after");
        return proceed;//返回
    }

结果:

我们也可以在通知中对原方法的返回值进行修改

java 复制代码
    //环绕通知
    @Around("point()")
    public Object around(ProceedingJoinPoint jointPoint) throws Throwable {
        System.out.println("around before");
        //执行被代理的方法(原方法)
        Integer proceed = (Integer) jointPoint.proceed();
        System.out.println("around after");
        proceed=100;//修改
        return proceed;
    }

使用@Around环绕通知计算某个方法执行一万次的时间

java 复制代码
 //计算某个方法执行一万次的时间
     @Around("point()")
    public Object around(ProceedingJoinPoint jointPoint) throws Throwable {
         //获取签名
         Signature signature = jointPoint.getSignature();
         //通过签名获取接口名
         String typeName = signature.getDeclaringTypeName();
         //通过签名获取执行方法的方法名字
         String name = signature.getName();

         long begin=System.currentTimeMillis();
         Integer proceed=null;
       for(int i=0;i<10000;i++){
          proceed = (Integer) jointPoint.proceed();//执行原方法
       }
       long end=System.currentTimeMillis();

         System.out.println(typeName+"."+name+"执行一万次的时间为"+(end-begin)+"ms");

        return proceed;
    }
java 复制代码
public class UserMapperTest {
    @Test
    public void test(){
        //加载配置类,创建IoC容器
        ApplicationContext ctx=new AnnotationConfigApplicationContext(SpringConfig.class);
        //UserMapper userMapperImpl = (UserMapper) ctx.getBean("userMapperImpl");
        UserMapper userMapper = ctx.getBean(UserMapper.class);
        Integer i = userMapper.update();
        System.out.println(i);
    }

}

结果:

AOP通知获取原方法的数据

获取切入点方法的参数

  • JointPoint:使用于前置,后置,返回后通知,抛出异常后通知
  • ProceedJointPonit:适用于环绕通知
java 复制代码
 @Override
    public Integer update(String name,Integer age) {
        System.out.println("name="+name+" "+"age="+age);
        System.out.println("UserMapper update");
        return 20;
    }

通知类

java 复制代码
@Component//交给IoC容器管理
@Aspect//这是一个通知类
public class MyAdvice2 {
    //写切入点表达式
    @Pointcut("execution(* com.hhh.dao.UserMapper.update(..))")//参数里面要写..
    public void point(){
    }
    @Before("point()")
    public void before(JoinPoint joinPoint){
        //获取原方法的参数
        Object[] args = joinPoint.getArgs();
        System.out.println("before 参数为"+Arrays.toString(args));
    }
    @Around("point()")
    public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
        //获取签名
        Signature signature = joinPoint.getSignature();
        //获取执行方法的接口
        String typeName = signature.getDeclaringTypeName();
        //获取方法名
        String name = signature.getName();
        //获取方法的参数
        Object[] args = joinPoint.getArgs();
        System.out.println(typeName+"."+name+"的参数为"+ Arrays.toString(args));
        //让原方法运行
        Object proceed = joinPoint.proceed();
        //返回原方法的返回值
        return proceed;
    }
}

测试:

java 复制代码
public class UserMapperTest {
    @Test
    public void test(){
        //加载配置类,创建IoC容器
        ApplicationContext ctx=new AnnotationConfigApplicationContext(SpringConfig.class);
        //UserMapper userMapperImpl = (UserMapper) ctx.getBean("userMapperImpl");
        UserMapper userMapper = ctx.getBean(UserMapper.class);
        Integer i = userMapper.update("hhh",19);
        System.out.println(i);
    }

}

结果:

获取切入点方法返回值

  • 返回后通知
  • 环绕通知
java 复制代码
@Component
@Aspect
public class MyAdvice3 {
    @Pointcut("execution(* com.hhh.dao.UserMapper.update(..))")
    public void point(){}
    @AfterReturning(value = "point()",returning="num")
    public void afterReturning(Integer num){//通过参数获取原方法的返回值
        System.out.println("原方法的返回值为"+num);
    }
    @Around("point()")
    public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
        //获取原方法的返回值
        Object proceed = joinPoint.proceed();
        return proceed;
    }
}

结果:

获取切入点方法运行异常信息

  • 抛出异常后通知
  • 环绕通知
java 复制代码
@Component
@Aspect
public class MyAdvice4 {
    @Pointcut("execution(* com.hhh.dao.UserMapper.update(..))")
    public void point(){}
    @AfterThrowing(value = "point()",throwing = "t")
    public void throwing(Throwable t){
        System.out.println("AfterThrowing的"+t);
    }
    @Around("point()")
    public Object around(ProceedingJoinPoint joinPoint){
        Object proceed=null;
        try {
           proceed = joinPoint.proceed();
        } catch (Throwable e) {
            System.out.println("around的"+e);//捕获原方法的异常
        }
        return proceed;
    }
}

在原方法写一个异常

java 复制代码
 @Override
    public Integer update(String name,Integer age) {
        Integer i=1/0;
        System.out.println("name="+name+" "+"age="+age);
        System.out.println("UserMapper update");
        return 20;
    }

结果:

小案例:去除密码空格

java 复制代码
@Service//交给Spring管理,变成bean
public class ResourceServiceImpl implements ResourceService {
    @Override
    public void save(String url, String password) {
        System.out.println("url="+url);
        System.out.println("password="+password);
        //验证有没有去除空格
        System.out.println(password.equals("1234")?"现在密码没有空格":"现在密码有空格");
    }
}

通知类:

java 复制代码
@Component
@Aspect
public class MyAdviceForResource {
    @Pointcut("execution(* com.hhh.service.ResourceService.save(..))")
    public void point(){
    }
    @Around("point()")
    public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
        System.out.println("around");
        //先获取原方法的参数
        Object[] args = joinPoint.getArgs();
        //这里不能使用增强for循环,因为增强for循环只能用于遍历,不能改变原参数的值
        for(int i=0;i<args.length;i++){
            if(args[i].getClass().equals(String.class)){//判断参数是不是String类型
                args[i]=args[i].toString().trim();//如果是String类型就去空格
            }
        }
        //执行原方法
        Object proceed = joinPoint.proceed(args);//重新传参
        return proceed;
    }
}

测试

java 复制代码
public class ResourceServiceTest {
    @Test
    public void test(){
        //加载配置类,初始化IoC容器
        ApplicationContext ctx= new AnnotationConfigApplicationContext(SpringConfig.class);
        ResourceService bean = ctx.getBean(ResourceService.class);
        bean.save("http://localhost:8080/database","  1234  ");

    }

}

结果:

相关推荐
c4fx3 分钟前
Delphi5利用DLL实现窗体的重用
开发语言·delphi·dll
阿华的代码王国8 分钟前
MySQL ------- 索引(B树B+树)
数据库·mysql
Redstone Monstrosity10 分钟前
字节二面
前端·面试
kinlon.liu11 分钟前
零信任安全架构--持续验证
java·安全·安全架构·mfa·持续验证
东方翱翔17 分钟前
CSS的三种基本选择器
前端·css
鸽芷咕26 分钟前
【Python报错已解决】ModuleNotFoundError: No module named ‘paddle‘
开发语言·python·机器学习·bug·paddle
王哲晓32 分钟前
Linux通过yum安装Docker
java·linux·docker
Hello.Reader36 分钟前
StarRocks实时分析数据库的基础与应用
大数据·数据库
Jhxbdks36 分钟前
C语言中的一些小知识(二)
c语言·开发语言·笔记
java66666888836 分钟前
如何在Java中实现高效的对象映射:Dozer与MapStruct的比较与优化
java·开发语言