spring学习笔记-从‘动态代理’理解spring的面向切面编程

1.面向切面编程中的一些专业术语

在这里插入图片描述
横切关注点: 首先一个方法可能有很多关注点,那么多个方法就有多个关注点,但是这些方法的关注点中可能有一两个是所有方法都需要关注的,也就是横跨了多个模块(比如各个模块的增删改查,对数据库进行了操作,我们需要记录日志信息),那么我们就可以将这些点叫做横切关注点。

通知方法: 通常在方法中我们比较关注的地方会做一些操作,不管是记录一些日志还是发送一些通知,这些都是通过方法进行的,并且因为这些方法高度通用,所以我们会将它抽象后定义到一个独立的class中,这个class就是“切面类”,切面类中的方法就是“通知方法”(我觉得叫“横切方法”更贴切)。

连接点: 通知方法可以进行切入的点。连接点一般是横切关注点的一部分,它是在方法的执行过程中才会真正定义的,因为只有定义了拦截器,方法在执行过程中被拦截器拦截到,我们才可能对这个“横切关注点”进行操作。不然你就只能在心里默默地关注它

切入点: 真正调用了“通知方法”的连接点才能被称为“切入点“,哪个连接点会变成“切入点”是由“切入点表达式”决定的,切入点表达式就是用来定义“通知方法”在哪个“目标方法”的什么位置进行切入的

2.spring依赖的一些外部拓展包

我们知道spring的aop底层其实就是动态代理,但是动态代理有一个缺陷就是,被代理类一定要实现接口,而如果我们需要支持不实现接口就能实现面向切面编程,就需要导入三个springAop的外部拓展包,https://repo.spring.io/webapp/#/search/quick/,大部分spring所需要的jar包都可以在这里搜索到。
需要的三个包分别是:

com.springsource.net.sf.cglib-2.2.0.jar
aspectjweaver-1.8.4.jar
aopalliance-1.0.jar

3.基于注解的AOP简单配置

1.导入spring的基础jar包及aop的拓展jar包
在这里插入图片描述
2. 创建切面类
在这里插入图片描述
创建切面类,并将切面类注入IOC容器,我们知道单纯将对象注入IOC容器中,spring是不能区分这个对象到底是model、service还是一个切面类的,所以我们要使用@Aspect注释来标识切面类,这样spring才能知道到底哪个类是切面类,基本语法:

@Aspect
@Component
public class AspectServiceImpl{

}

3.定义通知方法
切面类创建好了之后,我们需要在切面类中定义 “通知方法”,通知方法最主要的是其切入对象方法的时机确认(也就是什么时候对方法进行拦截,并执行通知操作)

  1. spring中提供了5种注解来标识执行时机,分别是:
    @Before:方法开始之前执行
    @After:方法最终结束时调用,
    @AfterReturning:方法正常返回后调用
    @AfterThrowing:方法出现异常时执行
    @Around:环绕通知,通知方法会将目标方法封装起来,around比较特殊,如果需要在执行around通知方式后能正常执行目标方法,需要在通知方法中进行调用

  2. 基本定义语法是:@注释(execution(切入点表达式))

        //@Before(execution(访问修饰符 返回值类型 方法签名(形参类型列表)))
        //语法示例
        @Before("execution(public String 
com.hao.spring.aop.service.impl.UserServiceImpl.*())")
        public void beforeFunction() {
            System.out.println("前置方法@Before");
        }
        //@around比较特殊
        //环绕通知
      @Around("execution(public String  com.hao.spring.aop.service.impl.UserServiceImpl.*())")
      public void aroundFunction(ProceedingJoinPoint pj) {
            try {
                  System.out.println("方法前环绕");
                  pj.proceed();
                  System.out.println("方法后环绕");
            } catch (Throwable e) {
                  e.printStackTrace();
            }
      }
  1. 各个通知方法的执行顺序是“@around前–before前置通知–对目标方法–around后–after后置方法–afterReturning正常返回/AfterThrowing异常返回”
    在这里插入图片描述
    4.开启基于注解的aop功能
    切面类和通知方法创建完成后,只需要在applicationContext.xml中开启基于注解的aop面向切面编程的功能就可以了(需要先在头文件中引入aop的名称空间)。
    在这里插入图片描述
xmlns:aop="http://www.springframework.org/schema/aop"
http://www.springframework.org/schema/aop 
http://www.springframework.org/schema/aop/spring-aop.xsd

<aop:aspectj-autoproxy></aop:aspectj-autoproxy>

5.测试
到这里如果上面的定义没有问题就可以测试一下aop面向切面的执行效果了
在这里插入图片描述

4.关于aop的一些详细说明

1.切入点指示符
切入点指示符除了execution还有within、this、target、args
execution:通过方法签名匹配连接点(public是可以不写或者用*号代替的),因为只能切入public的方法

execution(public String 
com.hao.spring.aop.service.impl.UserServiceImpl.*(..))

within:通过类型全签名匹配该类下的所有连接点

within(com.hao.spring.aop.service.impl.UserServiceImpl)

this:通过接口类型全签名指定aop代理对象的代理类型,匹配该对象的所有连接点,这个是匹配代理对象的类型

this(com.hao.spring.aop.service.UserService)

target:通过接口类型全签名匹配实现了该接口的对象的所有连接点,这个是匹配实现类的类型

target(com.hao.spring.aop.service.UserService)

this和target都是通过指定接口(因为代理对象和目标对象的联系是通过接口进行的)的类型全签名来匹配连接点的,所以this和target是一样的,但是这两个的切入点表达式是不可以使用通配符的

args:通过指定参数类型来匹配形参列表相同的目标方法

args(java.lang.String)

2.切入点表达式中通配符“*”和“”的使用

*:可以进行一个或多个字符的代替,例:

通配符功能示例描述
*指代任意返回值execution(public * com.hao.spring.aop.service.impl.UserServiceImpl.save())
*指代任意单层路径execution(public * com.hao.spring.*.service.impl.UserServiceImpl.save())匹配任意包下的实现包中的UserServiceImpl类
*指代任意单个参数execution(public * com.hao.spring.aop.service.impl.ServiceImpl.save())指代以ServiceImpl结尾的方法
*开始的包路径可以指代多层路径execution(public * *.ServiceImpl.save())所有包下的以ServiceImpl结尾的方法

…:匹配任意多个参数或者任意多层路径

通配符功能示例描述
指代任意多个参数execution(public * com.hao.spring.aop.service.impl.UserServiceImpl.save(…))这样就可以匹配任意形参类型的方法
指代任意多层路径execution(public * com.hao.spring…service.impl.UserServiceImpl.save(…))这样就可以匹配spring.aop和spring.aop.aopx下的方法了

3.切入点表达式中逻辑运算符符“&&”、“||”、“!”
“&&”同时满足两个表达式才进行切入:
save方法第一个参数是string且第二个参数是int时切入方法

execution(public * com.hao.spring.aop.service.impl.UserServiceImpl.save(String,*))&&execution(public * com.hao.spring.aop.service.impl.UserServiceImpl.save(*,int))

“||”满足其中一个表达式就进行切入:
save方法的第一个参数是string或者第二个参数是int就切入

execution(public * com.hao.spring.aop.service.impl.UserServiceImpl.save(strign,*))||execution(public * com.hao.spring.aop.service.impl.UserServiceImpl.save(*,int))

“!”不满足的情况就切入:
不切入save方法的参数是int,int的方法

!execution(public * com.hao.spring.aop.service.impl.UserServiceImpl.save(int,int))

4.被切入方法信息的获取
我们可以使用org.aspectj.lang.JoinPoint接口所提供的方法来获取连接点的详细信息,主要方法有:
joinPoint.getArgs():获取方法的参数列表
joinPoint.getSignature():获取方法的完整签名

通过方法签名我们可以获取当前方法的详细信息

String name = signature.getName();//获取方法名
Class declaringType =  signature.getDeclaringType();//获取带修饰符的类路径
String declaringTypeName =  signature.getDeclaringTypeName();//获取类路径
 int modifiers = signature.getModifiers();

joinPoint.getTarget():获取连接点所在的目标对象joinPoint.getThis():获取代理对象本身

ProceedingJoinPoint继承JoinPoint子接口,它新增了两个用于执行连接点方法的方法:
java.lang.Object proceed() :通过反射执行目标对象的连接点处的方法;
java.lang.Object proceed(java.lang.Object[] args) :通过反射执行目标对象连接点处的方法,不过使用新的入参替换原来的入参。
代码示例:

@Before(value = "execution(public * com.hao.spring.aop.service.impl.UserServiceImpl.*(..))")
	public void beforeFunction(JoinPoint joinPoint) {
		Object[] args = joinPoint.getArgs();
		String kind = joinPoint.getKind();
		SourceLocation sourceLocation = joinPoint.getSourceLocation();
		StaticPart staticPart = joinPoint.getStaticPart();
		Object target = joinPoint.getTarget();
		Object this1 = joinPoint.getThis();
		System.out.println("前置方法@Before");
		System.out.println("getArgs()方法:"+args);
		System.out.println("getKind()方法:"+kind);
		System.out.println("getSourceLocation()方法:"+sourceLocation);
		System.out.println("getStaticPart()方法:"+staticPart);
		System.out.println("getTarget()方法:"+target);
		System.out.println("getThis()方法:"+this1);
		

		Signature signature = joinPoint.getSignature();
		System.out.println("signature "+signature);
		String name = signature.getName();
		Class declaringType = signature.getDeclaringType();
		String declaringTypeName = signature.getDeclaringTypeName();
		int modifiers = signature.getModifiers();
		System.out.println("signature的getName()方法:"+name);
		System.out.println("signature的getDeclaringType()方法:"+declaringType);
		System.out.println("signature的getDeclaringTypeName()方法:"+declaringTypeName);
		System.out.println("signature的getModifiers()方法:"+modifiers);
	}

执行结果:

前置方法@Before
getArgs()方法:[Ljava.lang.Object;@4b2bac3f
getKind()方法:method-execution
getSourceLocation()方法:org.springframework.aop.aspectj.MethodInvocationProceedingJoinPoint$SourceLocationImpl@4e08711f
getStaticPart()方法:execution(String com.hao.spring.aop.service.UserService.save(String))
getTarget()方法:com.hao.spring.aop.service.impl.UserServiceImpl@ba2f4ec
getThis()方法:com.hao.spring.aop.service.impl.UserServiceImpl@ba2f4ec
signature String com.hao.spring.aop.service.UserService.save(String)
signature的getName()方法:save
signature的getDeclaringType()方法:interface com.hao.spring.aop.service.UserService
signature的getDeclaringTypeName()方法:com.hao.spring.aop.service.UserService
signature的getModifiers()方法:1025
User保存用户qianghao
后置方法@After

5.抽取可重用切入点表达式

  1. 声明一个没有实现和返回值的空方法
  2. 给方法标注@pointcut注解
  3. 用该方法名替换之前的切入点表达式

在这里插入图片描述

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值