【Spring】核心之AOP--面向切面编程

1. Spring核心之AOP

1.1 什么是AOP

AOP为Aspect Oriented Programming的缩写,意思为面向切面编程,是通过预编译方式和运行期动态 代理实现程序功能的统一维护的一种技术。
AOP的作用:不修改源码的情况下,程序运行期间对方法进行功能增强
好处:
    1、减少代码的重复,提高开发效率,便于维护。
    2、专注核心业务的开发。
核心业务和服务性代码(切面)混合在一起
开发中:各自做自己擅长的事情,运行的时候将服务性代码织入到核心业务中。
通过spring工厂自动实现将服务性代码以切面的方式加入到核心业务代码中。

1.2 AOP的实现机制-动态代理

什么是代理模式:
代理:自己不做,找人帮你做。
代理模式:在一个原有功能的基础上添加新的功能。
分类:静态代理和动态代理。

2. SpringAOP

2.1 Spring AOP相关概念

Spring的AOP实现底层就是对动态代理的代码进行了封装,封装后我们只需要对需要关注的部 分进行代码编写,并通过配置的方式完成指定目标的方法增强。
AOP的相关术语:
    Target(目标对象)
         要被增强的对象,一般是业务逻辑类的对象。
    Proxy(代理)
        一个类被 AOP 织入增强后,就产生一个结果代理类。
     Aspect(切面)
        表示增强的功能,就是一些代码完成的某个功能,非业务功能。是切入点和通知的结合。
    Joinpoint(连接点)
        所谓连接点是指那些被拦截到的点。在Spring中,这些点指的是方法(一般是类中的业务方
        法),因为 Spring只支持方法类型的连接点。
    Pointcut(切入点)
        切入点指声明的一个或多个连接点的集合。通过切入点指定一组方法。 被标记为 final 的方
        法是不能作为连接点与切入点的。因为最终的是不能被修改的,不能被增强的。
    Advice(通知/增强)
        所谓通知是指拦截到 Joinpoint 之后所要做的事情就是通知。通知定义了增强代码切入到目标代码的时间点,是目标方法执行之前执行,还是之后执行等。通知类型不同,切入时间不同。
        通知的类型:前置通知,后置通知,异常通知,最终通知,环绕通知。
        切入点定义切入的位置,通知定义切入的时间。
    Weaving(织入).
        是指把增强应用到目标对象来创建新的代理对象的过程。 spring 采用动态代理织入,而 AspectJ 采用编译期织入和类装载期织入。
切面的三个关键因素:
    1、切面的功能–切面能干啥
    2、切面的执行位置–使用Pointcut表示切面执行的位置
    3、切面的执行时间–使用Advice表示时间,在目标方法之前还是之后执行。

2.2 AspectJ 对 AOP 的实现

对于 AOP 这种编程思想,很多框架都进行了实现。Spring 就是其中之一,可以完成面向切面编程。 AspectJ 也实现了 AOP 的功能,且其实现方式更为简捷而且还支持注解式开发。所以,Spring 又将 AspectJ 的对于 AOP 的实现也引入到了自己的框架中。
在 Spring 中使用 AOP 开发时,一般使用 AspectJ 的实现方式
AspectJ 是一个优秀面向切面的框架,它扩展了 Java 语言,提供了强大的切面实现。

2.2.1 AspectJ的通知类型

AspectJ 中常用的通知有5种类型:

  1. 前置通知
  2. 后置通知
  3. 环绕通知
  4. 异常通知
  5. 最终通知

2.2.2 AspectJ的切入点表达式

AspectJ 定义了专门的表达式用于指定切入点。
表达式的原型如下:
execution(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 抛出异常类型
    ?表示可选的部分
以上表达式共 4 个部分。
execution(访问权限 方法返回值 方法声明(参数) 异常类型)
切入点表达式要匹配的对象就是目标方法的方法名。
所以,execution 表达式中就是方法的签名。
表达式中黑色文字表示可省略部分,各部分间用空格分开。在其中可以使用以下符号:
欢迎关注
例如:
execution(* com.wjx.service..(…))
指定切入点为:定义在 service 包里的任意类的任意方法。
execution(* com.wjx.service….(…))
指定切入点为:定义在 service 包或者子包里的任意类的任意方法。“…”出现在类名中时,后
面必须跟 “”,表示包、子包下的所有类。
execution(
com.wjx.service.IUserService+.*(…))
指定切入点为:IUserService 若为接口,则为接口中的任意方法及其所有实现类中的任意方
法;若为类, 则为该类及其子类中的任意方法。

2.3 注解方式实现AOP

开发阶段:关注核心业务和AOP代码
运行阶段:spring框架会在运行的时候将核心业务和AOP代码通过动态代理的方式编织在一起
代理方式的选择:是否实现了接口:有接口就选择JDK动态代理;没有就选择CGLIB动态代理。

1、 引入依赖
在这里插入图片描述
2 创建spring配置文件引入约束
在这里插入图片描述
核心业务类:

public interface IService { 
    void add(int id,String name); 
    boolean update(int num); 
}

TeamService核心业务类

@Service 
public class TeamService implements IService{ 
    @Override 
    public void add(int id, String name) { 
        System.out.println("TeamService---- add----"); 
    } 
    @Override 
    public boolean update(int num) { 
        System.out.println("TeamService---- update----"); 
        //int res=10/0; 
        if(num>666){ 
            return true; 
        } 
        return false; 
    } 
}

NBAService核心业务类:

@Service("nbaService") 
public class NBAService implements IService{ 
    @Override 
    public void add(int id, String name) { 
        System.out.println("NBAService---- add----"); 
    } 
    @Override 
    public boolean update(int num) { 
        System.out.println("NBAService---- update----"); 
        //int res=10/0; 
        if(num>666){ 
            return true; 
        } 
        return false; 
    } 
}

切面类

@Component   //切面对象的创建权限依然交给spring容器
@Aspect   //aspectj 框架的注解 标识当前类是一个切面类 
public class MyAspect{ 
/** 
 * 当较多的通知增强方法使用相同的 execution 切入点表达式时,编写、维护均较为麻烦。 
 * AspectJ 提供了@Pointcut 注解,用于定义 execution 切入点表达式。 
 * 其用法是,将@Pointcut 注解在一个方法之上,以后所有的 execution 的 value属性值 
 * 可使用该方法名作为切入点。代表的就是@Pointcut 定义的切入点。 
 * 这个使用@Pointcut 注解方法一般使用 private 的标识方法,即没有实际作用的方法。 
*/ 
@Pointcut("execution(* com.kkb.service..*.*(..))") 
private void pointCut(){ 
} 
@Pointcut("execution(* com.kkb.service..*.add*(..))") 
private void pointCut2(){ } 
/** 
 * 声明前置通知 
 * @param jp 
 */ 
 @Before("pointCut()") 
 public void before(JoinPoint jp){ 
     System.out.println("前置通知:在目标方法执行之前被调用的通知"); 
     String name = jp.getSignature().getName(); 
     System.out.println("拦截的方法名称:"+name); 
     Object[] args = jp.getArgs(); 
     System.out.println("方法的参数格式:"+args.length); 
     System.out.println("方法参数列表:"); 
     for (Object arg : args) { 
         System.out.println("\t"+arg); 
     } 
 } 
 /** 
  * AfterReturning 注解声明后置通知 
  * value: 表示切入点表达式 
  * returning 属性表示 返回的结果,如果需要的话可以在后置通知的方法中修改结果 
  */ 
@AfterReturning(value = "pointCut2()",returning = "result") 
public Object afterReturn(Object result){ 
    if(result!=null){ 
        boolean res=(boolean)result; 
        if(res){ 
            result=false; 
        } 
    } 
    System.out.println("后置通知:目标方法执行之后调用的通知,result="+result); 
    return result; 
} 
/** 
 * Around 注解声明环绕通知 
 * ProceedingJoinPoint 中的proceed方法表示目标方法被执行 
 */
@Around(value = "pointCut()") 
public Object around(ProceedingJoinPoint pjp) throws Throwable { 
    System.out.println("环绕方法---目标方法的执行之前"); 
    Object proceed = pjp.proceed(); 
    System.out.println("环绕方法---目标方法的执行之后"); 
    return proceed; 
} 
/** 
 * AfterThrowing 注解声明异常通知方法 
 * value: 表示切入点表达式 
 * returning 属性表示 返回的结果,如果需要的话可以在后置通知的方法中修改结果 
 */ 
@AfterThrowing(value = "pointCut()",throwing = "ex") 
public void exception(JoinPoint jp,Throwable ex){ 
    //一般会把异常发生的时间、位置、原有都记录下来 
    System.out.println("异常通知:目标方法出现异常的时候才会别调用的通知"); 
    System.out.println(jp.getSignature()+"方法出现异常,异常信息是:"
        =+ex.getMessage()); 
} 
/** 
 * After 注解声明为最终通知 
 */ 
@After( "pointCut()") 
public void myFinally(){ 
    System.out.println("最终通知:无论是否出现异常都是最后被调用的通知"); 
} 
}

测试类:

public class Test01 { 
@Test
public void test01(){ 
    ApplicationContext ac=new ClassPathXmlApplicationContext("spring.xml"); 
    TeamService teamService = (TeamService) ac.getBean("teamService"); 
    teamService.add(1001,"湖人队"); 
    System.out.println("--------------------------"); 
    boolean update = teamService.update(888); 
    System.out.println("update 结果="+update); 
    System.out.println("~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"); 
    NBAService nbaService = (NBAService) ac.getBean("nbaService"); 
    nbaService.add(1002,"热火"); 
    System.out.println("--------------------------"); 
    boolean update2 = teamService.update(888); 
    System.out.println("update 结果="+update2); 
} 
}

在定义好切面 Aspect 后,需要通知 Spring 容器,让容器生成“目标类+ 切面”的代理对象。这个代理是由容器自动生成的。只需要在 Spring 配置文件中注册一个基于 aspectj 的自动代理生成器,其就会自动扫描到@Aspect 注解,并按通知类型与切入点,将其织入,并生成代理。

spring.xml配置文件中开启包扫描和 注册aspectj的自动代理

在这里插入图片描述

2.4 XML方式实现AOP

切面类:

@Component //切面对象的创建权限依然交给spring容器 
@Aspect //aspectj 框架的注解 标识当前类是一个切面类 
public class MyAOP { 
    public void before(JoinPoint jp){ 
        System.out.println("AOP前置通知:在目标方法执行之前被调用的通知"); 
    } 
    public void afterReturn(Object result){ 
        System.out.println("AOP后置通知:在目标方法执行之后被调用的通知
            ,result="+result); 
    } 
    public Object around(ProceedingJoinPoint pjp) throws Throwable { 
        System.out.println("AOP环绕方法---目标方法的执行之前"); 
        Object proceed = pjp.proceed(); 
        System.out.println("AOP环绕方法---目标方法的执行之后"); 
        return proceed; 
    } 
    public void exception(JoinPoint jp,Throwable ex){ 
        //一般会把异常发生的时间、位置、原有都记录下来 
        System.out.println("AOP异常通知:在目标方法执行出现异常的时候才
            会别调用的通知,否则不执行"); 
        System.out.println(jp.getSignature()+"方法出现异常,异常信息 是:"
            +ex.getMessage()); 
    } 
    public void myFinally(){ 
        System.out.println("AOP最终通知:
            无论是否出现异常都是最后被调用的通知"); 
    } 
}

配置文件:
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

王某人@

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值