Spring(三)spring核心技术——aop

一、AOP简介

  AOP是Aspect Orient Programming的缩写,即面向切面编程。基于动态代理的,可以使用jdk和cglib两种代理方式。

  • Aspect: 切面,给你的目标类增加的功能,就是切面。像上面用的日志,事务都是切面。切面的特点:一般都是非业务方法,独立使用的。

  • Orient:面向,对着。

  • Programming:编程

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

作用:

  • 在目标类不修改的情况下增加功能

  • 减少代码的重复

  • 使开发人员专注业务功能的实现

  • 解耦合:业务功能和日志、事务等非业务功能的耦合

二、动态代理的实现方式

  • jdk动态代理

  使用jdk中的Proxy,Method,InvocaitonHanderl创建代理对象。jdk动态代理要求目标类必须实现接口

  • cglib动态代理

  第三方的工具库,创建代理对象,原理是继承。 通过继承目标类,创建子类。子类就是代理对象。要求目标类不能是final的,方法也不能是final的。

三、AOP中名词概念

  • aspect(切面):表示给业务方法增加的功能,一般为体制输出、事务、权限检查等

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

  • pointcut(切入点):是一个或多个JoinPoint的集合,表示切面功能执行的位置

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

  • advice(通知):也叫增强。表示切面执行的时间,在方法前或后

四、何时使用AOP

  • 某项目功能类不完善,需要增加功能,但是没有源代码

  • 给项目的多个类需要增加相同功能

  • 为业务功能增加事务、日志输出

五、AOP的实现

1、Spring

  Spring在内部实现了aop规范,能做aop的工作。spring主要在事务处理时使用aop,在项目开发中很少使用spring的aop实现,因为spring的aop比较笨重。

2、使用Aspectj框架实现AOP

  一个开源的专门做aop的框架。spring框架中集成了aspectj框架,通过spring就能使用aspectj的功能。

aspectJ框架实现aop有两种方式:

1)使用xml的配置文件 : 配置全局事务

2)使用注解,项目中要做aop功能,一般都使用注解

  • 使用相应注解确定切面执行的时间

  • 使用切面表达式确定切面执行的位置

  切面位置的切入点表达式:execution(修饰符 返回值 包名.类名.方法名(方法参数) 异常)。用来指定切面执行的位置。

aspectj中常用注解如下:

  1. @Aspect:声明该类是切面类
/* * @Aspect:是Aspectj框架中的注解 * 作用:声明该类是切面类 * 切面类:为目标类增加功能的类,包含切面的功能代码 * 位置:在类的上面 * */
@Aspect
public class MyAspect {
   }
  1. @Before:前置通知,在目标方法之前执行切面的功能
@Aspect
public class MyAspect {
   
    /* * 定义方法,是实现切面功能的方法 * 方法定义要求: * 1、方法时公共的 * 2、方法名自定义 * 3、方法没有返回值 * 4、可以有参数,也可以无参数 * 如果有参数,参数不是自定义的 * @Before:前置通知注解 * 属性:value,是切入点表达式,表明切面的功能执行的位置 * 位置:在方法的上面 * 特点: * 1、在目标方法前执行 * 2、不会改变和影响目标方法的执行 * */
    @Before(value = "execution(public void cn.krain.ba01.SomeServiceImpl.doSome(String,Integer))")
    public void myBefore(){
   
        System.out.println("前置通知,切面功能,在目标方法执行前输出执行的时间:"+new Date());
    }
  1. @AfterReturning:后置通知,在目标方法之后执行切面的功能,能获取返回值
@Aspect
public class MyAspect {
   
    /* * @AfterReturning定义方法,是实现切面功能的方法 * 方法定义要求: * 1、方法时公共的 * 2、方法名自定义 * 3、方法没有返回值 * 4、方法有参数 * @AfterReturning:后置通知 * 属性:1、value:切入点表达式 * 2、returning:自定义变量,目标方法的返回值 * 自定义变量名称和通知方法的形参名一致 * 位置:在方法定义的上面 * 特点: * 1、在目标方法之后执行 * 2、能获取目标方法的返回值 * 3、可修改这个返回值 * */
    @AfterReturning(value = "execution(* cn.krain.ba02.SomeServiceImpl.doOther(..))",
                    returning="res")
    public void myAfterReturning(Object res){
   
        System.out.println("后置通知,切面功能,在目标方法执行前输出执行的时间:"+res);
        if (res!=null){
   
            res = "Hello";
        }
    }
}
  1. @Around:环绕通知,能在目标方法前后增强功能,能够控制目标方法的执行,修改返回值
@Aspect
public class MyAspect {
   
    /* * 环绕通知的定义格式 * 方法定义要求: * 1、方法时公共的 * 2、方法名自定义 * 3、必须有返回值 * 4、方法有固定的参数:proceedingJoinPoint * @Around:环绕通知 * 属性:value 切入点表达式 * 位置:在方法的定义上面 * 特点: * 1、是功能最强的通知 * 2、能够在目标方法前后增强功能 * 3、控制目标方法是否被执行 * 4、修改原来目标方法的执行结果,影响最后的调用结果 * */
    @Around(value = "execution(* *..SomeServiceImpl.doFirst(..))" )
    public Object myAround(ProceedingJoinPoint joinPoint) throws Throwable {
   

        //获取第一个参数的值
        String name = null;
        Object args [] = joinPoint.getArgs();
        if (args!=null && args.length>=1){
   
            Object arg = args[0];
            name = (String) arg;
        }

        Object result = null;

        //实现环绕通知
        System.out.println("环绕通知,在目标方法之前,输出时间:"+new Date());

        //1、目标方法调用
        //控制目标方法的执行
        if (name.equals("张三")){
   
            result = joinPoint.proceed();       //等同于执行目标方法
        }

        System.out.println("环绕通知,在目标方法之后,提交事务");

        //修改目标函数返回结果
        if (result!=null){
   
            result = "Hello Aop";
        }
        return result;
    }
}
  1. @AfterThrowing:异常通知,在目标方法抛出异常后执行
@Aspect
public class MyAspect {
   
    /* 异常通知的定义格式 * 方法定义要求: * 1、方法时公共的 * 2、方法名自定义 * 3、方法没有返回值 * 4、固定参数Exception,如果还有参数则是:JoinPoint * 5、throwing的值要与Exception的参数名相同 * @AfterThrowing:异常通知 * 属性:1、value 切入点表达式 * 2、throwing 自定义变量,表示目标方法抛出的异常对象 * 如果有异常,通过邮件、短信通知 * 特点: * 1、在目标方法出现异常时执行 * 2、监控目标方法是否存在异常 * * 执行过程: * try{ * SomeServiceImpl.doSecond(); * }catch(Exception e){ * myAfterThrowing(e); * } * */
    @AfterThrowing(value = "execution(* *..SomeServiceImpl.doSecond())",
                    throwing = "ex")
    public void myAfterThrowing(Exception ex){
   
        System.out.println("异常通知,方法执行异常,执行:"+ex.getMessage());
        //发送短信
    }
}
  1. @After:最终通知,总是会执行
@Aspect
public class MyAspect {
   
    /* 最终通知的定义格式 * 方法定义要求: * 1、方法时公共的 * 2、方法名自定义 * 3、方法没有返回值 * 4、没有参数,如果有参数则是:JoinPoint * */

    /* * @After 最终通知 * 属性:value 切入表达式 * 位置:在方法的上面 * * 特点: * 1、总是会执行 * 2、在目标方法之后执行 * * 执行过程: * try{ * //目标方法 * }catch(){ * * }finally{ * myAfter(); * } * */
    @After(value = "execution(* *..SomeServiceImpl.doThird(..))")
    public void myAfter(){
   
        System.out.println("执行最终通知,总是会被执行的代码");
    }
}
  1. @Pointcut:定义和管理切入点的辅助注解
@Aspect
public class MyAspect {
   
    @After(value = "myPT()")
    public void myAfter(){
   
        System.out.println("执行最终通知,总是会被执行的代码");
    }

    @Before(value = "myPT()")
    public void myBefore(){
   
        System.out.println("执行前置通知");
    }
    /* * @Pointcut:定义和管理切入点表达式,如果项目中的切入点表达式是重复的,可使用Pointcut * 属性:value 切入点表达式 * 位置:在自定义方法上面 * 为切入点表达式定义别名,使用"方法名称()"代替切入点表达式 * */
    @Pointcut(value = "execution(* *..SomeServiceImpl.doThird(..))")
    private void myPT(){
   
        //无功能代码
    }
}
  • 编写spring主配置文件
<?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,有spring统一创建和管理-->
    <!--创建SomeService目标对象-->
    <bean name="someService" class="cn.krain.ba.SomeServiceImpl" />

    <!--创建aspect切面对象-->
    <bean name="myAspect" class="cn.krain.ba.MyAspect" />

    <!-- 声明自动代理生成器:使用aspectj的内部功能,创建目标对象的代理对象。 创建代理对象是在内存中实现的,修改目标对象在内存中的结构,创建成代理对象 因此,目标对象就是被修改后的代理对象 -->
    <aop:aspectj-autoproxy />

    <!-- 如果项目中有接口时,仍希望使用CGHLIB动态代理 proxy-target-class="true":告诉框架使用cglib动态代理 -->
    <aop:aspectj-autoproxy proxy-target-class="true" />
</beans>
  • 测试类MyTest.java
public class MyTest {
   
    public void Test(){
   
        String config = "applicationContext.xml";
        ApplicationContext ac = new ClassPathXmlApplicationContext(config);
        //根据对象名获取代理对象
        SomeService proxy = (SomeService) ac.getBean("someService");
        //proxy."目标方法名"();
    }
}
全部评论

相关推荐

无敌虾孝子:喜欢爸爸还是喜欢妈妈
点赞 评论 收藏
分享
程序员猪皮:看不到八股什么意思
点赞 评论 收藏
分享
点赞 收藏 评论
分享
牛客网
牛客企业服务