Spring-AOP:JoinPoint、各种通知、基于XML和注解的AOP、声明式事务

Aop的细节

1.Aop底层是动态代理

获取类型是接口

Aop的底层就是动态代理,容器中保存的组件是他的代理对象,

Calculator bean = app.getBean(Calculator.class);
System.out.println(bean);//com.jiang.impl.MyCalculator@1bb5a082
System.out.println(bean.getClass());//class com.sun.proxy.$Proxy15

打印 bean 是com.jiang.impl.MyCalculator@1bb5a082,看起来bean是MyCalculator类型的,
但实际上当你打印bean.getClass得到class com.sun.proxy.$Proxy15,发现实际类型是代理对象

所以如果从容器拿对象,通过类型拿需要写接口类型而不是本类,或者你通过id获取。

获取类型是本类

即使没有接口,也可以创建对象,获取的时候就是本类

我们此时将实现类中的接口给注释掉

public class MyCalculator /*implements Calculator*/ {

然后运行

MyCalculator bean = app.getBean(MyCalculator.class); 使用的是本类获取
System.out.println(bean);//com.jiang.impl.MyCalculator@740773a3
System.out.println(bean.getClass());//class com.jiang.impl.MyCalculator$$EnhancerBySpringCGLIB$$44159919

还是一个代理对象,只是CGLIB帮我们创建好的对象


2.切入点表达式

固定格式:execution(访问权限符 返回值类型 方法全签名(参数表))

通配符
*

匹配一个或者多个字符execution(public int com.jiang.impl.*(int,int))

匹配任意一个参数 假设我的add(int,int) 有重载方法add(int,double)
​ 那么我此时的就无法进行切入,因为我们的表达式的参数值是int,int
​ 所以此时就需要execution(public int com.jiang.impl.*(int,*))

​ 权限位置不能表示任意,不写表示任意:execution(* com..impl.*(..))

..

匹配所有参数的方法 execution(public int com.jiang.impl.*(..))
匹配任意多层路径 execution(public int com..impl.*(..)) com包下任何多层路径的impl包

最模糊execution(* *.*(..))任意权限下 任意返回值 任意包任意类任意方法下任意参数
最精确的execution(public int com.jiang.impl.MyCalculator.add(int,int))

&&与:切入的位置要满足这两个表达式
execution(public int com..impl.*(..))&& execution(* com.jiang.impl.*(int,int))
add(int,double)满足第一个,不满足第二个,所以不行

||或:切入的位置满足一个即可
!:只要不是这种的都切

抽取可重用的切入点表达式:1.声明一个没实现的带void的方法 2.在方法上@Point注解写需要抽取的表达式

@Point("exect.......")
public void demo(){}

//使用
@Before(demo())

3.通知方法执行顺序

try{
@Before
method.invoke(obj,args);
@AfterReturning
}catch(e){
@AfterThrowing
}finally{
@After
}
通知方法的执行顺序:
正常执行:@Before(前置通知)=》@After(后置通知)=》@AfterReturning(正常返回)
异常执行:@Before(前置通知)=》@After(后置通知)=》@AfterThrowing(异常返回)

4.JoinPoint获取详细信息

我们可以在通知方法运行的时候,拿到目标方法的详细信息

参数JoinPoint

只需要为通知方法的参数列表上写一个参数JoinPoint(封装了当前目标方法的详细信息)

@Aspect
@Component
public class LogUtil {
    //想在执行目标之前运行,写入切入点表达式
    //execution(访问权限符 返回值类型 方法签名)
    @Before("execution(public int com.jiang.impl.MyCalculator.add(int,int))")
    public static void logStart(JoinPoint joinPoint){
        //获取到目标方法运行时候的参数
        Object[] args = joinPoint.getArgs();
        //获取到目标方法的签名
        Signature signature = joinPoint.getSignature();
        //从签名中获取方法名
        String name = signature.getName();

        System.out.println("【"+name+"】方法开始前,参数值是【"+Arrays.asList(args)+"】");
    }
}

通过name也可以简写为 joinPoint.getSignature().getName()

参数值简写为 Arrays.asList(joinPoint.getArgs())

@AfterReturning

@AfterReturning(value = "execution(public int com.jiang.impl.MyCalculator.*(..))",returning = "result")
public static void logReturn(JoinPoint joinPoint,Object result){

    String name = joinPoint.getSignature().getName();
    System.out.println(name+"方法正常执行完成,计算结果是"+result);
}

告诉Spring这个result就是用来接受返回值的:returning = "result"

@AfterThrowing

@AfterThrowing(value = "execution(public int com.jiang.impl.MyCalculator.*(..))",throwing = "e")
public static void logExc(JoinPoint joinPoint,Exception e){

    String name = joinPoint.getSignature().getName();
    System.out.println(name+"方法出现异常,异常是"+e);
}

告诉Spring这个e就是用来接受异常的throwing = "e"

唯一的要求就是参数列表一定不能乱写

  • 通知方法是Spring利用反射调的,每次方法调用得确定这个方法的参数表的值;
  • 参数表上的每一个参数,Spring必须都得知道是谁

5.环绕通知

@Around:环绕,Spring中最强大的通知,四合一通知就是环绕通知

@Around("execution(public int com.jiang.impl.MyCalculator.*(..))")
public static Object myAround(ProceedingJoinPoint pjp) throws Throwable {
/*
    环绕通知下有一个参数:ProceedingJoinPoint pjp,这个方法很强大。

 */
    //获取方法参数
    Object[] args = pjp.getArgs();
    String name = pjp.getSignature().getName();

    Object proceed = null;
    try{
        //就是利用反射调用目标方法即可 它就是method.invoke
        System.out.println("在proceed前就是前置通知 @Before");
        System.out.println("【环绕前置】【"+name+"方法开始】");
        pjp.proceed(args);
        System.out.println("在proceed后就是 @AfterReturning");
        System.out.println("【环绕返回】【"+name+"方法返回,返回值】"+proceed);
    }catch (Exception e){
        System.out.println("这就是异常通知 @AfterThrowing");
        System.out.println("【环绕异常】【"+name+"方法返回,异常】"+e);
        throw new Exception(e);//为了让外面接收到异常,一定要抛出去
    }finally {
        System.out.println("结束通知 @After");
        System.out.println("【环绕后置】【"+name+"方法结束】");
    }

    System.out.println("环绕。。。");

    //反射调用后一定要返回出去
    return proceed;
}
  • 环绕通知下有一个参数:ProceedingJoinPoint pjp,这个方法很强大。
  • pjp.proceed就是利用反射调用目标方法即可 它就是method.invoke
  • 在proceed前就是前置通知 @Before
  • 在proceed后就是 @AfterReturning
  • 在catch中 这就是异常通知 @AfterThrowing
  • 在finally 就是后置通知 @After
  • 反射调用后一定要返回出去
  • 为了让外面接收到异常,一定要抛出

顺序:环绕前置--》普通前置--》目标方法执行--》环绕正常返回/出现异常--》环绕后置--》普通后置--》普通返回/异常

6.多切面运行顺序

@Order(1) 使用order改变切面顺序,数值越小,优先级越高

Aop使用场景

  • AOP做日志保存到数据库

  • AOP做权限验证

  • AOP做安全检查

  • AOP做事务控制

AOP基于XML

基于注解的Aop过程

  • 将切面类和目标类都加入到ioc容器中
  • 告诉Spring那个是切面类 @Aspect
  • 在切面类中通过五个通知注解配置切面中这些通知方法何时运行
  • 开启基于注解的AOP功能

基于XML的Aop过程

1.注册bean

<bean id="myCalculator" class="com.jiang.impl.MyCalculator"></bean>
<bean id="logUtil" class="com.jiang.utils.LogUtil"></bean>

2.需要aop空间

<aop:config>
    <!--制定切面 ref指定切面 相当于加了@Aspect   -->
    <aop:aspect ref="logUtil">
        <!--那个方法是前置通知 method=@Before point=切入表达式-->
        <aop:before method="logStart" pointcut="execution(* com.jiang.impl.MyCalculator.*(..))" />

        <!--抽取切面表达式 -->
        <aop:pointcut id="mypoint" expression="execution(* com.jiang.impl.MyCalculator.*(..))"/>
        <!--通过pointcut-ref引入-->
        <aop:after-returning method="logReturn" pointcut-ref="mypoint" returning="result"/>

        <aop:after-throwing method="logExc" pointcut-ref="mypoint" throwing="e"/>
        
    </aop:aspect>
</aop:config>

注解:快速方便 配置:功能完善;

重要的用配置,不重要的用注解

声明式事务

Spring提供了JdbcTemplate能快速的帮我们操作数据库

声明式事务:以前通过复杂的编程来编写一个事物,替换需要告诉Spring那个方法是事务方法即可;Spring自动进行事务控制;

事务要么都执行,要么都不执行

事务四个关键属性ACID

  • 原子性(atomicity)
  • 一致性(consistency)
  • 隔离性(isolation)
  • 持久性(durability)

使用

1.配置事务管理器,让其进行事务控制,一定要导入面向切面的包

<bean id="dateSource" class="xx..xxxxxx.DataSoruce">
	数据源.......
</bean>

2.开启基于注解的事务控制模式,依赖tx空间

<tx:annotation-driven transaction-manager = "dateSource"/>

3.给事务方法加注解 @Transactional

原文地址:https://www.cnblogs.com/pengcode/p/12503284.html