程序员的春天-Spring学习03

程序员的春天-Spring学习03

Spring AOP

aop:面向切面编程(Aspect Oriented Programming)
spring实现aop使用的是代理模式

代理

What is?

例如:微商,简单来说就是代替厂家卖商品
厂家对于用户是不可见的,而代理商又扩大了厂家的销售途径

Why use?

将上面的厂家和代理商进行抽象,厂家就是目标类代理商就是代理类
这样可以隐藏目标类的具体实现
还可以在不修改目标类的代码的情况下对其功能进行增强

具体的静态代理,和动态代理的实现什么的,等以后在细究底层吧,现在看的真是一愣一愣的不是很明白

AOP介绍

介绍

面向切面编程,就是将交叉的业务逻辑封装成切面,利用AOP的功能将切面织入到主业务逻辑中。所谓交叉业务逻辑是指,通用的,与主业务逻辑无关的代码,如安全检查,事务,日志等。

不是用AOP,则会出现代码纠缠,即交叉业务逻辑于主业务逻辑混合在一起。这样会使主业务逻辑变得混杂不清。

基本术语

  • 切面
    • 切面泛指交叉业务逻辑。常用的切面有通知和顾问,实际上就是对主业务罗杰的一种增强
  • 织入
    • 织入是指将切面代码插入到目标对象的过程
  • 连接点
    • 连接点指切面可以织入的位置
  • 切入点
    • 切入点指切面具体织入的位置
  • 通知(Advice)
    • 切面的一种实现,可以完成简单的织入功能(织入功能就是在这里完成的)。
  • 顾问(Advisor)
    • 顾问是切面的另一种实现,能将通知更为复杂的织入目标对象中,事故将通知包装为更复杂切面的装配器。不仅制定了切入的时间点,还可以指定具体的切入点

基于Schema-based通知

spring对aop的实现

前置通知(MethodBeforeAdvice)

创建切面,前置通知类

前置通知类需要实现MethodBeforeAdvice接口

public class MyMethodBeforeAdvice implements MethodBeforeAdvice {
    public void before(Method method, Object[] args, Object target) throws Throwable {
        System.out.println("前置通知的before执行");
    }
}
参数作用
method目标方法
args目标方法的参数列表
target目标对象

xml配置注册目标类,注册切面,注册代理

注册目标类

<bean id="someServiceImpl" class="top.byfree.service.impl.SomeServiceImpl"/>

注册切面

<bean id="myMethodBeforeAdvice" class="top.byfree.aspects.MyMethodBeforeAdvice"/>

注册代理

<bean id="proxyFactoryBean" class="org.springframework.aop.framework.ProxyFactoryBean">
    <!--指定目标对象-->
    <property name="target" ref="someServiceImpl"/>
    <!--指定目标类的实现所有接口-->
    <property name="interfaces" value="top.byfree.service.SomeService"/>
    <!--指定切面-->
    <property name="interceptorNames" value="myMethodBeforeAdvice"/>
</bean>

注册代理property标签name属性值的含义

含义
target目标对象,可以理解位之前注册的目标类
interfaces目标类实现的所有接口,可能有多个,就得用多个标签声明了
interceptorNames指定切面,就是我们之前注册的那个切面

后置通知(AfterReturningAdvice)

创建切面,后置通知类

实现AfterReturningAdvice接口

public class MyAfterReturningAdvice implements AfterReturningAdvice {
    public void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable {
        System.out.println("后置通知的afterReturning()方法执行目标方法的返回值:" + returnValue );
        if (returnValue != null) {
            System.out.println(((String)returnValue).toUpperCase());
        }
    }
}
参数作用
returnValue返回值
method目标方法
args目标方法的参数列表
target目标

xml配置与上前置通知一致,只需要换成后置通知类即可

环绕通知(MethodInterceptor)

创建切面,环绕通知类
实现MethodInterceptor接口


public class MyMethodInterceptor implements MethodInterceptor {
    public Object invoke(MethodInvocation methodInvocation) throws Throwable {
        System.out.println("环绕通知,目标方法执行之前");
        // 调用执行目标方法
        Object result = methodInvocation.proceed();
        if (result != null) {
            result = ((String) result).toUpperCase();
        }
        System.out.println("环绕通知,目标方法执行之后");
        return result;
    }
}
参数作用
methodInvocation方法调用器
methodInvocation.proceed()执行目标方法
return这里的返回值会替换源目标方法的返回值

xml配置一致,不在赘述

异常通知

创建切面,异常通知类

实现ThrowsAdvice接口

public class MyThrowsAdvice implements ThrowsAdvice {
    public void afterThrowing(Exception e) {
        System.out.println("异常通知执行!");
    }
}

没有参数,只有目标类有异常时会执行

xml配置一致,不在赘述

基于AspectJ通知(XML配置方式实现)

环境搭建

引入maven依赖

<dependency>
      <groupId>org.aspectj</groupId>
      <artifactId>aspectjrt</artifactId>
      <version>1.6.1</version>
    </dependency>
    <dependency>
      <groupId> org.aspectj</groupId >
      <artifactId> aspectjweaver</artifactId >
      <version> 1.6.11</version >
</dependency>

添加配置文件dtd约束

<?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
        http://www.springframework.org/schema/aop/spring-aop.xsd">

切入点表达式

用于标识切入点,通俗点讲我理解的是匹配要增强的方法

execution(
	[modifiers-pattern] 访问权限类型
	ret-type-pattern 返回值类型
	[declaring-type-pattern] 全限定类名
	name-pattern(param-pattern) 方法名(参数名)
	[throws-pattern] 抛出异常类型
)

解释
[]包括的表示可以省略,没有[]的是不可以省略的部分

名称作用示例
[modifiers-pattern]访问权限类型public,private~
ret-type-pattern返回值类型void,int~
[declaring-type-pattern]全限定类名top.byfree.service~
name-pattern(param-pattern)方法名(参数名)toDo(int n)~
[throws-pattern]抛出异常类型IOException~

可以使用下面的符号进行通配

符号意义
*0到多个任意字符
..在方法参数中,表示多个参数。在包名后,表示当前包及其子包的路径
+用在类名后,表示当前类及其子类。接口后表示当前接口及其子接口

基本步骤

1. 创建切面类

public class MyAspect2 {
    // 前置通知
    public void before () {
        System.out.println("前置通知");
    }
    // 前置通知带参
    public void before2 (JoinPoint jt) {
        System.out.println("带参数的前置通知" + jt);
    }

    // 后置通知
    public void afterReturning (Object result) {
        System.out.println("后置通知方法执行目标方法的返回值是:" + result);
    }

    // 环绕通知
    public Object around (ProceedingJoinPoint pjp) throws Throwable {
        System.out.println("环绕通知,目标方法执行前");
        String result = (String)pjp.proceed();
        System.out.println("之后");
        if (result != null) {
            result = result.toUpperCase();
        }
        return result;
    }

    // 异常通知
    public void throwing (Exception e) {
        System.out.println("异常信息" + e);
    }
    
    // 最终通知
    public void after () {
        System.out.println("最终通知执行");
    }
}

2. 在切面类中创建各种通知方法

上述代码
细节
前置通知的JoinPoint参数,可以得到的是我们写入的切入点表达式
后置通知的Object参数,代表目标方法的返回值
环绕通知的ProceedingJoinPoint参数,可以执行目标方法
环绕通知的return,这个return会代替目标方法的return
异常通知的Exception参数,代表的是异常信息

3. xml文件配置切面

<!--注册目标方法bean-->
<bean id="someServiceImpl" class="top.byfree.service.impl.SomeServiceImpl"/>
<!--注册切面-->
<bean id="myAspect" class="top.byfree.aspects.MyAspect2"/>

<!--使用aop配置-->
  <aop:config>
    <!--定义切入点-->
    <aop:pointcut id="doSomePc" expression="execution(* *..service.*.doSome(..))"/>
    <aop:pointcut id="doOtherPc" expression="execution(* *..service.*.doOther(..))"/>
    <aop:aspect ref="myAspect">
      <!--前置通知-->
      <aop:before method="before" pointcut-ref="doSomePc"/>
      <!--带参数的通知方法执行-->
      <aop:before method="before2" pointcut-ref="doSomePc"/>
      <!--后置通知-->
      <aop:after-returning method="afterReturning" returning="result" pointcut-ref="doOtherPc"/>
      <!--环绕通知-->
      <aop:around method="around"  pointcut-ref="doOtherPc"/>
      <!--异常通知-->
      <aop:after-throwing method="throwing" pointcut-ref="doSomePc" throwing="e"/>
      <!--最终通知-->
      <aop:after method="after" pointcut-ref="doSomePc"/>
    </aop:aspect>
</aop:config>

<aop:config>标签的子标签

标签作用
<aop:advisor>配置顾问的,目前不深入这部分
<aop:aspect>配置通知切面
<aop:pointcut>用来声明切入点,注意这个也可以不直接声明。在aspect的标签的子标签pointcut属性写入也可以

<aop:aspect>标签的子标签

标签作用
<aop:beforer>配置前置通知
<aop:after-returning>配置后置通知
<aop:around>配置环绕通知
<aop:after-throwing>配置异常通知
<aop:after>配置最终通知

各种通知配置标签的属性

属性作用
method指定切面方法
pointcut指定切入点,直接写切面表达式
pointcut-ref链接之前声明的切入点,写入<aop:pointcut>的id属性
arg-names设置目标方法的参数获取,还需要在<aop:pointcut>或者pointcut属性的切面表达式后添加参数信息,具体看下述

基于AspectJ通知(注解开发方式)

基本步骤

1. 创建切面类

@Aspect
public class MyAspect {
}

使用@Aspect表示该类是一个切面类

2. 在切面类中创建具体的通知方法

    @Before("execution(* *..service.*.doSome(..))")
    public void before () {
        System.out.println("前置通知方法执行");
    }

这里演示的是前置通知使用@Before
注解传入参数是value值是一个切入点表达式,因为只有一个参数所以这里省略了value

注解含义
@Before前置通知
@AfterReturning后置通知
@Around环绕通知
@AfterThrowing异常通知
@After最终通知

上述注解的参数问题
第一个参数是一个切入点表达式

这个切入点表达式可以在这里书写,也可以引用切入点方法
使用@Pointcut注解一个切入点方法

@Pointcut(value = "execution(* *..service.*.jojo(..)) && args(a)", argNames = "a")
public void pointcut (int a) {}

引用切入点方法

@Before(value = "pointcut(a)", argNames = "a")
    public void before (int a) {
        System.out.println("前置通知方法执行" + a);
}

第二个参数是argNames:用来匹配切入点表达式里面的args配和获取目标方法的参数用的

这里的@Pointcut教程上没有讲,我查寻了资料,理解为一种代码复用的方法,声明一个切入点方法后,其他的通知就可以直接引用了。

Copyright: 采用 知识共享署名4.0 国际许可协议进行许可

Links: https://www.byfree.top/archives/spring03