Spring之AOP

一、AOP思想

在这里插入图片描述

在开发中,为了给业务方法中增加日志记录,权限检查,事务控制等功能,此时我们需要去修改业务方法代码,考虑到代码的重用性,我们可以考虑使用OOP的继承或组合关系来消除重复, 但是无论怎么样, 我们都会在业务方法中纵向地增加这些功能方法的调用代码。

此时,既不遵循开闭原则,也会为后期系统的维护带来很大的麻烦。这些零散存在于业务方法中的功能代码,我们称之为横切面关注点,横切面关注点不属于业务范围,应该从业务代码中剥离出来。为了解决该问题, OOP思想是不行了,得使用AOP思想。

AOP(Aspect Oritention Programming):

把一个个的横切关注点放到某个模块中去,称之为切面。那么**每一个的切面都能影响业务的某一种功能,**切面的目的就是功能增强,如日志切面就是一个横切关注点,应用中许多方法需要做日志记录的只需要插入日志的切面即可。

在这里插入图片描述

在这里插入图片描述

这种面向切面编程的思想就是AOP思想

二、AOP底层原理(了解)

AOP,面向切面编程,底层使用动态代理方式实现。针对实现了接口的类的横向扩展,采用的是JDK动态代理进行实现。在JDK动态代理中,为对原有类(实现了某一接口的类)进行横向扩展,内部创建了实现了同一接口的类代理对象,具有与原有类对象相同的功能,且进行了增强。对于未实现接口的类,想实现横向的扩展,则需要使用cglib动态代理实现,内部创建原有类的子类的代理对象,即在子类中调用父类的方法完成增强。

三、核心概念

  1. 连接点(Joinpoint):被拦截到的点。在Spring中只支持方法类型的连接点,因此这些点在Spring中指的是方法。简单来说,我们可以认为是可以被增强功能的方法。
  2. 切入点(Pointcut):对连接点进行拦截的定义。在Spring中通过表达式(后续将进行说明)形式来定义所匹配的类下的方法,以此定义切入点。
  3. 通知/增强(Advice):所谓通知指的就是指拦截到连接点之后要执行的代码,通知分为:
    • 前置通知(before 在方法之前执行)
    • 后置(after-returning 在方法之后执行)
    • 环绕(around 在方法之前和之后执行)
    • 通知异常(after-throwing 在方法出现异常时执行)(了解)
    • 最终(after 在后置之后执行,无论方法是否执行成功)(了解)。
  4. 目标对象(Target):代理的类或是要被增强的类。
  5. 织入(Weaving):是指把增强advice应用到目标对象target来创建新的代理对象proxy的过程
  6. 代理类(Proxy):一个类进行织入后将产生一个结果类,称为代理类。
  7. 切面(Aspect):是切入点pointcut和通知advice的结合。

在这里插入图片描述

四、Pointcut语法

  • AOP的规范本应该由SUN公司提出,但是被AOP联盟捷足先登。AOP联盟在制定AOP规范时,首先就要解决一个问题: 怎么表示切入点。也就是说怎么表达需要在哪些方法上做增强。这是一个如何表示WHERE的问题。
  • AOP规范后,面向切面编程的框架AspectJ也就应运而生了, 同时也确定如何去表达这个WHERE。

AspectJ切入点语法如下(表示在哪些包下的哪些类中的哪些方法上做切入增强):

execution(modifiers-pattern? ret-type-pattern declaring-type-pattern? name-pattern(param-pattern)throws-pattern?)

execution(<修饰符>? <返回类型> <声明类型>? <方法名><参数>) <异常>?)

注: ? 表示可写可不写

表达式参数详细:

  • 修饰符[一般省略]
    • public 公共方法
    • * 任意
  • 返回值类型[不能省略,一般使用*]
    • void 返回没有值
    • String 返回值字符串
    • * 任意
  • 包[可以省略,但一般不省]
    • com.hxzy.service.impl 固定
    • com.hxzy.*.impl com.hxzy下面的所有impl子包
    • com.hxzy… com.hxzy下面的所有子包(包含自己)
  • 类[一般省略用*来指代]
    • UserServiceImpl 指定类
    • *Impl 以Impl结尾
    • User* 以User开头
    • * 任意
  • 方法名[不能省略]
    • addUser 固定方法
    • add* 以add开头
    • *Do 以Do结尾
    • * 任意
  • (参数)
    • () 无参
    • (int) 一个整型
    • (int ,int) 两个整型
    • (…) 参数任意
  • throws [可省略,一般不写]

在这里插入图片描述

切入点表达式中的通配符:

  • * : 匹配任何部分,但是只能表示一个单词
  • .. : 可用于全限定名中和方法参数中,分别表示 包以及子包 和 0到N个参数。

常见的写法:(对应表达式从后往前看)
execution(* com.myw.service.*.*(..)): 表示对service包下的所有类所有方法做增强
execution(* com.myw.service.*Serice.*(..)): 表示对service包下所有以Service为后缀的类的所有方法做增强
execution(* com.myw..service.*Service.*(..) : 表示对myw包及其子包下的service包中的以Service为后缀的类的所有方法做增强

五、AOP小案例

这里的案例不是使用Maven构建,而是采用较为原始的方法

使用AOP的准备操作

1、导入jar包

所需jar包如下:

Spring基础jar包:
1. spring-beans
2. spring-context
3. spring-core
4. spring-expression
5. commons-logging-1.2.jar

Spring AOP相关的jar包:
1. aopalliance-1.0.jar
2. aspectjweaver-1.8.7.jar
3. spring-aop
4. spring-aspects
2、准备目标对象(被代理对象,被通知的对象,被增强的类对象)
package com.myw.service.Impl;

import com.myw.service.IUserService;

public class UserServiceImpl implements IUserService {
    public void add() {
        System.out.println("增加用户");
    }

    public void delete() {
        System.out.println("删除用户");
    }

    public void update() {
        System.out.println("修改用户");
    }

    public void query() {
        System.out.println("查询用户");
    }
}
3、准备通知(被增强方法的代码,想要实现功能的方法代码)
package com.myw.Aspect;

import org.aspectj.lang.ProceedingJoinPonit;

public class MyAspect {
    /*
        一般前置、后置、环绕用得比较多
     */

    //前置通知
    public void before(){
        System.out.println("前置通知");
    }

    //后置通知
    public void after(){
        System.out.println("后置通知");
    }

    public Object around(ProceedingJoinPoint pjp) throws Throwable{
        System.out.println("环绕通知的前置部分");

        //用来获取被环绕方法的返回值
        Object proceed = pjp.proceed();

        System.out.println("环绕通知的后置部分");
        return proceed;
    }
}
4、配置 applicationContext.xml
  1. 导入aop(约束)命名空间
  2. 配置目标对象
  3. 配置通知对象
  4. 配置将通知织入目标对象
<!--application.xml-->
<?xml version="1.0" encoding="UTF-8">
<beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://www.springframework.org">
	<!--1.配置目标对象-->
 	<bean name="userService" class="com.myw.service.Impl.UserServiceImpl"></bean>
	
	<!--2.配置通知对象-->
	<bean name="myAspect" class="com.myw.Aspect.MyAspect"></bean>
	
	<!--3.配置将通知织入目标对象中-->
	<aop:config>

		<!--配置切入点-->
		<!--
			public void com.myw.service.Impl.UserServiceImpl.*(..)
			* com.myw.service.Impl.UserServiceImpl.*(..)
		-->
		<aop:pointcut expression="execution(* com.myw.service.Impl.UserServiceImpl.*(..))" id="pc">
		<aop:aspect ref="myAspect">
			<!--指定名为before的方法作为前置通知-->
			<aop:before method="before" pointcut-ref="pc"/>
			
			<!--后置通知-->
			<aop:after method="after" pointcut-ref="pc"/>

			<!--环绕通知-->
			<aop:around method="around" pointcut-ref="pc">
		</aop:aspect>
</beans>
5、使用

当用目标类(UserServiceImpl)去调用其本身被增强的方法时,通知会附加上去

IUserService u = new UserServiceImpl();
u.add()

输入结果:

前置通知
环绕通知的前置部分
增加用户
环绕通知的后置部分
后置通知

总结:通知的几种类型

  1. 前置通知———目标方法运行之前调用
  2. 后置通知———目标方法运行之后调用(如果出现异常不调用)
  3. 环绕通知———目标方法之前和之后都调用

六、spring中的aop使用注解配置(推荐)

1、applicationContext.xml中配置目标对象,通知对象,开启使用注解完成织入
<?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:context="http://www.springframework.org/schema/context"
       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/context http://www.springframework.org/schema/context/spring-context-4.0.xsd
		http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.0.xsd">
    <!--1.确定扫描注解的范围,这里扫描com.myw的所有子包-->
    <context:component-scan base-package="com.myw"></context:component-scan>
    
    <!--2.配置目标对象-->
 	<bean name="userService" class="com.myw.service.Impl.UserServiceImpl"></bean>
	
	<!--3.配置通知对象-->
	<bean name="myAspect" class="com.myw.Aspect.MyAspect"></bean>

    <!--4.使Spring的AOP注解生效,后面的true表示使用cglib动态代理模式-->
    <aop:aspectj-autoproxy proxy-target-class="true"></aop:aspectj-autoproxy>
</beans>
2、@Aspect注解代表该类是个通知类,书写切点表达式@Pointcut(“execution(返回值 全类名.方法名(参数))”)
package com.myw.Aspect;

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

//表示这是一个通知类
@Aspect
public class MyAspect{

    //创建一个方法,使用Pointcut注解,为后面的方法做准备
    @Pointcut("execution(* com.myw.service.Impl.UserServiceImpl(..))")
    public void pc(){}

    @Before("MyAspect.pc()")
    public void before(){
        System.out.println("前置通知");
    }

    @After("MyAspect.pc()")
    public void after(){
        System.out.println("后置通知");
    }

    @Around("MyAspect.pc()")
    public Object around(ProceedingJoinPoint joinPoint) throws Throwable{
        //环绕方法执行前

        //目标方法(会获取目标方法的返回值)
        Object obj = joinPoint.proceed();

        //环绕方法执行后

        return obj;
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值