• 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

  • 相关阅读:
    shell:读取文件的每一行内容并输出
    shell中set命令
    shell中declare命令
    MySQL MID()函数用法
    mysql的取整函数
    二分查找算法
    zookeeper安装和使用
    Redis 集群--------Redis-cluster
    ehcahe + redis 实现多级缓存
    Redis事物
  • 原文地址:https://www.cnblogs.com/pengcode/p/12503284.html
Copyright © 2020-2023  润新知