java基础巩固-宇宙第一AiYWM:为了维持生计,Spring全家桶_Part2-1(Spring左膀右臂中的右臂AOP、静态代理、动态代理、事务)~整起

上篇Spring左膀右臂中的左膀IOC,大家可以看看,这篇唠唠spring剩下的内容。
我一个产品,假设里面只有一个类一个方法

public class Demo{
	public static void main(String[] args){
		System.out.println("我回到家吃敏小言给我做的饭")
	}
}

结果就是:
//我吃到我老婆敏小言给我做的饭了呗

  • 然后,我把代码装好,上交给当家的敏小言了

后来出事了,我老婆给我说,不行,为了防止你吃完就躺长一个啤酒肚,你吃饭前先去楼下给我买几串烤肉,并且按照咱俩的老规矩,轮流刷锅。
我给我老婆说,那你把上次给你的代码给我我改一下:

public class Demo{
	public static void main(String[] args){
		System.out.println("吃饭之前先下楼给老婆买烤肉")
		System.out.println("我回到家吃敏小言给我做的饭")
		System.out.println("帮忙刷锅")
	}
}

确实这样就能解决问题呀。但是,我老婆说代码已经在局里登记注册了,要改源代码手续很多还要交很大一笔费用哦,费时又费财还费事,况且谁知道后期还会不会有其他类型的需求呢,我媳妇给我否决了,我给局子发的信也果不其然被我媳妇说中了给否决了。

除了上面这种需求,还有更专业一点的:比如:

  • 咱们想统计项目中所有类的方法的执行耗时–思路就是在每个方法的第一行和最后一行加上时间埋点,再打印一行日志就行了(你可别想着一行一行给源码里面手动加)
  • 或者我想让方法执行前先输出“我这个牛B方法要开始执行了”,然后在方法执行后输出一个“我牛B完了”,那怎么办?
  • 或者,借助一下人家官方的话:为什么需要代理模式呢?
    • 代理模式的优势是可以 很好地遵循设计模式中的开放封闭原则对扩展开发,对修改关闭。你不需要关注目标类的实现细节通过代理模式可以在不修改目标类的情况下,增强目标类功能的行为

小胡此时想起了,三界中流传着两种解法(其实想法很类似,只不过就像民间偏方官方出品有时候都能治病,谁又能说他俩没啥关系或者他俩有啥关系呢):

  • 民间偏方:民间流传着名为代理模式(动态代理和静态代理两种)的非官方解决方案
    在这里插入图片描述
    在这里插入图片描述
    静态代理和动态代理具体如下:

    • 静态代理:
      在这里插入图片描述
      • 如果按照那个时间埋点的需求为例的话,咱们可以这样干,大体思路如下:
        • 给项目中每个类都写一个代理类,让这个代理类与目标类(被代理的类)实现同一个接口
          • 为什么要实现同一个接口,我个人觉得,你找中介你得留下这个中介的联系方式吧,比如手机号,那这个手机号是不是移动公司给你的,那你自己的手机号是不是也是移动公司给你的,相当于你们俩共同实现了移动公司,从而将你俩联系在了一起
          • 代理类中具体啥内容呢,肯定就是得先把目标实现类组合进来,然后咱们调用代理类的方法时就相当于还是会调用目标类的方法实现功能(只不过代理类中的方法比咱们目标类的方法前后多了一些代码,而多的这些代码不就是咱们想要实现的增强的功能或者说测耗时的埋点对应代码嘛)。咱们客户后来就不需要调用目标实现类了直接找中介就行了
            • 代理类 = 增强代码 + 目标实现类
        • 所有【new 目标类】的地方都替换成为【new 代理类】,然后咱不是将目标实现类组合在代理类中了嘛,咱们就可以将目标类作为构造方法的参数传入。然后使用目标实现类调用的地方全部都替换为代理类进行调用【或者说在代理类的对应方法调用目标类中的对应方法。这样的话,我们就可以通过代理类屏蔽对目标对象的访问,并且可以在目标方法执行前后做一些自己想做的事情。】
          在这里插入图片描述
      • 实际应用场景非常非常少,日常开发几乎看不到使用静态代理的场景。从 JVM 层面来说, 静态代理在编译时就将接口、实现类、代理类这些都变成了一个个实际的 class 文件
      • 静态代理的缺点:
        • 肯定,看到那一句,给每个类写一个代理类,你要是项目很多我写到啥时候去
        • 另外,这个目标实现类(被代理类)人家就没实现啥接口,就是没有那个中国移动,你和中介都联系不到一块,你这能被代理,玩呢?
    • 动态代理:动态代理中中所谓的“动态”是针对使用Java代码实际编写了代理类的“静态”代理而言的。(动态代理就是来解决静态代理的两个缺点的)
      • 不要以为动态代理相对于静态代理而言优势就在于省去了编写代理类那一点代码的工作量(虽然说这确实解决了静态代理的一个缺点)。真正的优势是实现了可以在原始类和接口还未知时就确定代理类的代理行为(一般在程序运行过程中动态产生代理类对象),当代理类和原始类脱离直接联系后就可以很灵活的重用与不同的应用场景之中了,其他动态代理的优点如下图:
        在这里插入图片描述
        在这里插入图片描述
        • 从 JVM 角度来说,动态代理是在运行时动态生成类字节码,并加载到 JVM 中的
      • 比如咱们一般在项目中实现 统一拦截这种功能时,比如授权认证、性能统计等等。你可能立马就会想到,我实现过呀,并且我知道可以用 Spring 的 AOP 功能来实现。在 Spring AOP 里面我们是怎么实现统一拦截的效果呢?【AOP 要实现的是在我们原来写的代码的基础上,进行一定的包装,如在方法执行前、方法返回后、方法抛出异常后等地方进行一定的拦截处理或者叫增强处理。】
        • 在我们不需要改动原有代码的前提下,还能实现非业务逻辑跟业务逻辑的解耦。这里的核心就是采用动态代理技术,通过对字节码进行增强或者JDK实现一个代理,实际运行的实例其实是生成的代理类的实例在方法调用的时候进行拦截,以便于在方法调用前后,增加我们需要的额外处理逻辑。那话说回来,动态代理跟 RPC 又有什么关系呢?
          • RPC 框架怎么做到像调用本地接口一样调用远端服务呢?这必须依赖动态代理来实现。需要创建一个代理对象,在代理对象中完成数据报文编码,然后发起调用发送数据给服务提供方,以此屏蔽 RPC 框架的调用细节。因为代理类是在运行时生成的,所以代理类的生成速度、生成的字节码大小都会影响 RPC 框架整体的性能和资源消耗,所以需要慎重选择动态代理的实现方案
      • 动态代理的两种实现方式:对比一下JDK和cglib动态代理如下
        在这里插入图片描述
        在这里插入图片描述
        • cglib动态代理:利用字节码处理框架 ASM框架将目标对象生成的class文件加载进来,通过修改其字节码文件生成代理子类(原理是通过字节码技术为一个类创建子类,然后在创建的这个子类中重写父类的方法,实现对代码的增强
          在这里插入图片描述
          • Cglib 动态代理是基于 ASM 字节码生成框架实现的第三方工具类库,相比于 JDK 动态代理,Cglib 动态代理更加灵活,它是通过字节码技术生成的代理类,所以代理类的类型是不受限制的。使用 Cglib 代理的目标类无须实现任何接口,可以做到对目标类零侵入。Cglib 动态代理是对指定类以字节码的方式生成一个子类,并重写其中的方法,以此来实现动态代理。因为 Cglib 动态代理创建的是目标类的子类,所以目标类必须要有无参构造函数,而且目标类不要用 final 进行修饰Cglib 动态代理的实现需要依赖两个核心组件:MethodInterceptor 接口和 Enhancer 类,类似于 JDK 动态代理中的InvocationHandler 接口和Proxy 类
            • MethodInterceptor 接口:
              • MethodInterceptor 接口只有 intercept() 一个方法,所有被代理类的方法执行最终都会转移到 intercept() 方法中进行行为增强真实方法的执行逻辑则通过 Method 或者 MethodProxy 对象进行调用
            • Enhancer 类
              • Enhancer 类是 Cglib 中的一个字节码增强器,它为我们对代理类进行扩展时提供了极大的便利。Enhancer 类的本质是在运行时动态为代理类生成一个子类,并且拦截代理类中的所有方法。我们可以 通过 Enhancer 设置 Callback 接口对代理类方法执行的前后执行一些自定义行为,其中 MethodInterceptor 接口是我们最常用的 Callback 操作
          • 在我们使用 Cglib 动态代理之前,需要引入相关的 Maven 依赖,如下所示。如果你的项目中已经引入了 spring-core 的依赖,则已经包含了 Cglib 的相关依赖,无须再次引入
            在这里插入图片描述
            • 举例子:模拟数据库操作的事务管理。和下面JDK动态代理的例子一样。UserDao 接口和实现类保持不变,TransactionProxy 需要重新实现
              在这里插入图片描述
              在这里插入图片描述
        • JDK动态代理:利用InvocationHandler基于反射机制生成一个代理接口(所以JDK动态代理要求被代理类必须实现接口)的匿名类(JDK 动态代理时业务类必须要实现某个接口,它是基于反射的机制实现的,生成一个实现同样接口的一个代理类,然后通过重写方法的方式,实现对代码的增强。),在调用方法前调用InvocationHandler来处理;(JDK的动态代理(默认方式)主要是通过java.lang.reflect.Proxy类 和java.lang.reflect.InvocationHandler(Interface)接口这两个实现。或者说在 Java 的JDK动态代理机制中 InvocationHandler 接口和 Proxy 类是核心。
          在这里插入图片描述
          在这里插入图片描述
          • java.lang.reflect.InvocationHandler(Interface)接口
            • JDK 动态代理所代理的对象必须实现一个或者多个接口,生成的代理类也是接口的实现类,然后通过 JDK 动态代理是通过反射调用的方式代理类中的方法,不能代理接口中不存在的方法每一个动态代理对象必须提供 InvocationHandler 接口的实现类,InvocationHandler 接口中只有一个 invoke() 方法。当我们使用代理对象调用某个方法的时候,最终都会被转发到 invoke() 方法执行具体的逻辑
              在这里插入图片描述
          • java.lang.reflect.Proxy类
            • Proxy 类可以理解为动态创建代理类的工厂类,它提供了一组静态方法和接口用于动态生成对象和代理类。通常我们 只需要使用 newProxyInstance() 方法
              在这里插入图片描述
            • 一个简单的例子模拟数据库操作的事务管理
              在这里插入图片描述
          • 反射:赋予了我们在运行时分析类以及执行类中方法的能力。通过反射你可以获取任意一个类的所有属性和方法,你还可以调用这些方法和属性
            • 不过,反射让我们在运行时有了分析操作类的能力的同时,也增加了安全问题,比如可以无视泛型参数的安全检查(泛型参数的安全检查发生在编译时)。另外,反射的性能也要稍差点,不过,对于框架来说实际是影响不大的
            • 反射常见操作:
              • 获取 Class 对象的四种方式
                • Class alunbarClass = TargetObject.class;知道具体类的情况下可以使用,但是我们一般是不知道具体类的,基本都是通过遍历包下面的类来获取 Class 对象,通过此方式获取 Class 对象不会进行初始化
                • 通过 Class.forName()传入类的全路径获取:Class alunbarClass1 = Class.forName("cn.xxx.TargetObject");
                • 通过对象实例instance.getClass()获取:TargetObject o = new TargetObject();Class alunbarClass2 = o.getClass();
                • 通过类加载器xxxClassLoader.loadClass()传入类路径获取:ClassLoader.getSystemClassLoader().loadClass("cn.xxx.TargetObject");【通过类加载器获取 Class 对象不会进行初始化,意味着不进行包括初始化等一系列步骤,静态代码块和静态对象不会得到执行】
          • java.lang.reflect.InvocationHandler(Interface):咱们不是实现了InvocationHandler接口嘛,那这个接口咱得瞅瞅吧。其实在调用代理对象的方法时实际上会 去执行InvocationHandler对象的invoke方法
            在这里插入图片描述
            在这里插入图片描述
            在这里插入图片描述
            在这里插入图片描述
            在这里插入图片描述
            在这里插入图片描述
            在这里插入图片描述
          • java.lang.reflect.Proxy:这个类里面有几个方法还是比较重要的【Proxy 类中使用频率最高的方法是:newProxyInstance() ,这个方法主要用来生成一个代理对象。】
            在这里插入图片描述
            在这里插入图片描述
    • java.lang.reflect.Proxy类中方法很关键,得看看,所以可以看出jdk动态代理中的两核心:InvocationHandler接口和Proxy类是核心,人家能被叫做核心就是因为肚子里全是宝贝
      • public static Object newProxyInstance(ClassLoader loader, 类<?>[] interfaces, InvocationHandler h) throws IllegalArgumentException:
        • 这个newProxyInstance()方法传入的3个形参是类加载器、一组接口和InvocationHandler对象。
          在这里插入图片描述
          在这里插入图片描述
        • 返回指定接口的代理类的实例,该接口将方法调用分派给指定的调用处理程序。
      • public static 类<?> getProxyClass(ClassLoader loader, 类<?>… interfaces) throws IllegalArgumentException:
        • 这个静态方法是:传入类加载器和一组接口就可以返回代理Class对象(不就是咱们获取到了代理类的Class对象)。这个方法其实就是会将咱们传入的一组接口类的结构信息“拷贝”到一个新的Class对象中,新的Class对象带有构造器,是可以创建对象的。其实就是以Class创建Class,创建好了之后咱们不就可以用反射创建实例对象了
        • 给定类加载器和接口数组的代理类的java.lang.Class对象。 代理类将由指定的类加载器定义,并将实现所有提供的接口。 如果任何给定的接口是非公开的,则代理类将是非公开的。 如果类加载器已经定义了接口相同置换的代理类,那么将返回现有的代理类; 否则,这些接口的代理类将被动态生成并由类加载器定义。
      • 然后,除了上面那俩,还有:
        在这里插入图片描述
  • 咱们用JDK 动态代理类的话就可以这样用,或者说JDK 动态代理类使用步骤

    • 定义一个接口及其实现类
    • 自定义 InvocationHandler 并重写invoke方法,在 invoke 方法中我们会调用原生方法(被代理类的方法)并自定义一些处理逻辑
    • 通过 Proxy.newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h) 方法创建代理对象
      在这里插入图片描述
    • 还有,咱们自己实现的类,除了上面重要的三步,里面内容都包含啥呀,以至于能够动态生成代理类呢。从框图可以看出里面主要包含三个方法(方法名字因人而异哦,这个一般没啥固定):getProxy()、Invoke(…)、setXxx(被代理的类 被代理的类的引用名)
      在这里插入图片描述
      • setXXX()?
        在这里插入图片描述
      • getProxy()?:这个方法名可以有自己叫法,里面的代码最重要:
        这个方法中用来动态生成代理类的主要代码就是:Proxy.newProxyInstance(...)
        
    • 最后,感谢bizhan一个老师课上讲的关于动态代理的泛化公式,与大家共享一下,背住哦:
      在这里插入图片描述
      在这里插入图片描述
      在这里插入图片描述
      在这里插入图片描述
    • 俗话说,光说不练假把式,民间偏方里面就包含两个代理模式(动态代理和静态代理),咱就用上面那个,我老婆敏小言叫我吃饭的作为例子,来看看代理模式的使用:
      public class DynamicProxyTest{
      	interface IEatNoodles{//吃面的接口,里面只有一个抽象方法,还记得咱们的接口和抽象方法生来是来干啥的,就是用来被重写的
      		void eatNoodles();//定义一个吃面的抽象方法,生来就是被重写的
      	}
      	
      	static class EatNoodles implements IEatNoodles{
      		@Override
      		public void eatNoodles(){
      			System.out.println("我回到家吃敏小言给我做的饭");
      		}
      	}
      	
      	static class DynamicProxy implements InvocationHandler{
      		Object originalObj;
      
      		Object bind(Object originalObj){
      			this.originalObj = originalObj;
      			/**
      			*Proxy.newProxyInstance(...)这个方法返回了一个IEatNoodles的接口,并且代理了new  EatNoodles()实例行为的对象。再深入其实就是这个方法最后调用了sun.misc.ProxyGenerator.generateProxyClass()方法来完成生成字节码的动作,这个方法可以在运行时产生一个描述代理类的字节码byte[]数组
      			*/
      			return Proxy.newProxyInstance(originalObj.getClassLoadeer(), originalObj.getClass().getInterface(), this);
      		}
      
      		@Override
      		public Object invoke(Object proxy, Method method, Object[] args) throws Throwable{
      			/**
      			*这是咱们要实现的效果,那就看看具体的代理代码咋写
      			*System.out.println("吃饭之前先下楼给老婆买烤肉")
      			*System.out.println("我回到家吃敏小言给我做的饭")
      			*System.out.println("帮忙刷锅")
      			*
      			*/
      			System.out.println("吃饭之前先下楼给老婆买烤肉");
      			return method.invoke(originalObj, args);
      			System.out.println("帮忙刷锅");
      		}
      	}
      	public static void main(String[] args){
      		IEatNoodles eat = (IEatNoodles) new DynamicProxy().bind(new  EatNoodles());
      		eat.eatNoodles();
      	}
      }
      
      
      
      运行结果:
      System.out.println("吃饭之前先下楼给老婆买烤肉")
      System.out.println("我回到家吃敏小言给我做的饭")
      System.out.println("帮忙刷锅")
      
      • 如果想看这个在运行时产生的代理类中写了什么看一在main()方法中加入这句:System.getProperties().put(“sum.misc.ProxyGenerator.saveGeneratedFiles”, “true”);
        ,加入之后磁盘中会产生一个名为“$Proxy0.class”的代理类Class文件,反编译后可以看到这个代理类为传入接口中的每一个方法以及从java.lang.Object中继承来的equals()、hashCode()、toString()方法都生成了对应的实现,并且统一调用了InvocationHandler对象的invoke()方法来实现这些方法的内容,各个方法的区别不过是传入的参数和Method对象有所不同而已,所以无论调用动态代理哪一个方法实际上都是在执行InvocationHandler.invoke()中的代理逻辑
  • 官方,也就是spring集团的三当家,spring的左膀右臂中的右臂AOP

    • (AOP:最小入侵性编程,扩展时不需要或者不会改动原来的代码,AOP(Aspect-Oriented Programming), 即 面向切面编程, 它与 OOP( Object-Oriented Programming, 面向对象编程) 相辅相成, 提供了与 OOP 不同的抽象软件结构的视角. 在 OOP 中, 我们以类(class)作为我们的基本单元, 而 AOP 中的基本单元是 Aspect(切面)
      • 咱们开启AOP其实就是生成一个BeanPostProcessor的bean对象,然后把BeanPostProcessor的bean对象放到Spring容器中去:
        在这里插入图片描述
        • 我们面向的对象是一个个的 bean 实例,而 bean实例可以简单理解为是 BeanDefinition 的实例,Spring 会根据 BeanDefinition 中的信息为我们生产合适的 bean 实例出来。当我们需要使用 bean 的时候,通过 IOC 容器的 getBean(…) 方法从容器中获取 bean 实例,只不过大部分的场景下,我们都用了依赖注入,所以很少手动调用 getBean(...) 方法。或者说我们一般不直接把bean拿出来直接玩,而是认认真真做一个bean的搬运工,哪里出现依赖或者注入冲向哪里
        • BeanPostProcessor 的方法和InstantiationAwareBeanPostProcessor中的方法很相似,而且InstantiationAwareBeanPostProcessor还继承了 BeanPostProcessor。
          在这里插入图片描述
          • InstantiationAwareBeanPostProcessor 是 Instantiation,BeanPostProcessor 是 Initialization,Initialization代表的是 bean 在实例化完成并且属性注入完成,在执行 init-method 的前后进行作用的。而 InstantiationAwareBeanPostProcessor 的执行时机要前面一些
      • AOP:面向切面编程,通过预编译方式和运行期间动态代理实现程序功能的统一维护的一种技术。AOP 是 OOP(面向对象编程) 的延续,是 Spring 框架中的一个重要内容,是函数式编程的一种衍生范型。利用 AOP 可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率
        • 多个切面的执行顺序如何控制?
          • 通常使用@Order 注解直接定义切面顺序
            在这里插入图片描述
          • 实现Ordered 接口重写 getOrder 方法。
            在这里插入图片描述
    • AOP有哪些实现方式?实现 AOP 的技术,主要分为两大类
      在这里插入图片描述
      • 静态 AOP 实现:指使用 AOP 框架提供的命令进行编译,从而在 编译阶段 就可生成 AOP 代理类,因此也称为编译时增强AOP 框架在编译阶段对程序源代码进行修改,生成了静态的 AOP 代理类(生成的 *.class 文件已经被改掉了,需要使用特定的编译器),比如 AspectJ)【Spring AOP 已经集成了 AspectJ ,AspectJ 应该算的上是 Java 生态系统中最完整的 AOP 框架了。】
        • 织入方式:
          • 编译时编织(特殊编译器实现)
          • 类加载时编织(特殊的类加载器实现)
        • AspectJ 定义的通知类型有哪些?
          在这里插入图片描述
      • 动态 AOP 实现:运行时在内存中“临时”生成 AOP 动态代理类,因此也被称为运行时增强( AOP 框架在运行阶段动态生成代理对象(在内存中以JDK 动态代理,或 CGlib 动态地生成 AOP 代理类两种方式为主),如 SpringAOP【spring 中 AOP 的实现是通过动态代理实现的,如果是实现了接口就会使用 JDK 动态代理,否则就使用 CGLIB 代理。】)Spring AOP 需要做的是生成这么一个代理类,然后替换掉真实实现类来对外提供服务。这个替换在 Spring IOC 容器中就是在 getBean(…) 的时候返回的实际上是代理类的实例,而这个代理类我们自己没写代码,它是 Spring 采用 JDK Proxy 或 CGLIB 动态生成的。】
        在这里插入图片描述
        • JDK 动态代理:通过反射来接收被代理的类,并且JDK 动态代理要求被代理的类必须实现一个接口 。JDK 动态代理的核心是 InvocationHandler 接口和 Proxy 类通过接口生成一个代理Class对象,通过代理Class对象就可以创建代理对象
          在这里插入图片描述
          • JDK动态代理中的代理模式其实就是三个人的小游戏:接口 + 真实实现类 + 代理类。其中 真实实现类 和 代理类 都要实现接口实例化的时候要使用代理类,或者说实际上实例化的是代理类,而这个代理类和真实的实现类或者叫被代理类实现了同一个接口。Spring AOP 需要做的是生成这么一个代理类,然后替换掉真实实现类来对外提供服务
        • CGLIB动态代理:CGLIB是一个基于ASM的字节码生成库,可以在运行时动态的生成某个类的子类。它允许我们在运行时对字节码进行修改和动态生成。CGLIB 通过继承方式实现代理如果目标类没有实现接口,那么咱们想用动态代理时就不能用JDK实现了而要用CGLIB动态代理,那么 Spring AOP 会选择使用 CGLIB 来动态代理目标类
          在这里插入图片描述
          • CGLIB 动态代理机制中 MethodInterceptor 接口和 Enhancer 类【可以通过 Enhancer类来动态获取被代理类,当代理类调用方法的时候,实际调用的是 MethodInterceptor 中的 intercept 方法】是核心。咱们需要 自定义 MethodInterceptor 并重写 intercept 方法,intercept 用于拦截增强被代理类的方法
            • 源码:
              在这里插入图片描述
          • CGLIB 动态代理类使用步骤:CGLIB(Code Generation Library) 实际是属于一个开源项目,如果你要使用它的话,需要手动添加相关依赖
            在这里插入图片描述
            • 定义一个类
            • 自定义 MethodInterceptor 并重写 intercept 方法,intercept 用于拦截增强被代理类的方法,和 JDK 动态代理中的 invoke 方法类似
            • 通过 Enhancer 类的 create()创建代理类
          • Spring 采用 CGLIB 生成字节码的方式来生成一个子类时,我们定义的类不能定义为 final class,抽象方法上也不能加 final
          • CGLIB利用asm开源包对代理对象类的class文件加载进来,通过修改其字节码生成子类来处理。
          • CGLIB ( Code Generation Library ),是一个代码生成的类库,可以在运行时动态的生成某个类的子类,注意, CGLIB 是通过继承的方式做的动态代理,因此如果某个类被标记为 final ,那么它是无法使用 CGLIB 做动态代理的
            在这里插入图片描述
            在这里插入图片描述
  • 那Spring框架什么时候用cglib什么时候用JDK动态代理呢?

    • 如果目标对象 实现了接口默认用JDK动态代理, 此时当然也可以强行使用cglib
    • 如果目标对象没有实现接口必须采用cglib
  • Spring AOP and AspectJ AOP 有什么区别?
    在这里插入图片描述

    • Spring AOP 基于动态代理方式实现,是运行时增强Spring AOP 仅支持方法级别的 PointCut
      • 默认地,如果使用接口的则用 JDK 提供的动态代理实现,如果没有接口则使用 CGLIB 实现【Spring 3.2 以后,spring-core 直接就把 CGLIB 和 ASM 的源码包括进来了,这也是为什么我们不需要显式引入这两个依赖】
      • Spring AOP 只能作用于 Spring 容器中的 Bean,它是 使用纯粹的 Java 代码实现的,只能作用于 bean 的方法
      • Spring AOP 是基于代理实现的,在容器启动的时候需要生成代理实例,在方法调用上也会增加栈的深度,使得 Spring AOP 的性能不如 AspectJ 那么好
    • AspectJ AOP基于静态代理方式实现,属于静态织入,AspectJ AOP 是编译时增强,需要特殊的编译器才可以完成,是通过修改代码来实现的,支持三种织入方式或者三种织入时机:
      • 编译时织入:就是在编译字节码的时候织入相关代理类
        在这里插入图片描述
        • Compile-time weaving:编译期织入,在编译的时候一步到位,直接编译出包含织入代码的 .class 文件。如类 A 使用 AspectJ 添加了一个属性,类 B 引用了它,这个场景就需要编译期的时候就进行织入,否则没法编译类 B
        • 在 JVM 运行的时候其实就是加载了一个普通的被织入了代码的类。在编译的时候先修改了代码再进行编译
      • 编译后织入:编译完初始类后发现需要 AOP 增强,然后织入相关代码
        • Post-compile weaving:也就是已经生成了 .class 文件,或已经打成 jar 包了,这种情况我们需要增强处理的话,就要用到编译后织入。
          在这里插入图片描述
      • 类加载时织入:指在加载器加载类的时候织入【JVM 进行类加载的时候进行织入】
        • Load-time weaving:指的是在加载类的时候进行织入,要实现这个时期的织入,有几种常见的方法。1、自定义类加载器来干这个,这个应该是最容易想到的办法,在被织入类加载到 JVM 前去对它进行加载,这样就可以在加载的时候定义行为了。2、在 JVM 启动的时候指定 AspectJ 提供的 agent:-javaagent:xxx/xxx/aspectjweaver.jar。
        • 需要在 JVM 的启动参数中加上以下 agent(或在 IDE 中配置 VM options):-javaagent:/Users/hongjie/.m2/repository/org/aspectj/aspectjweaver/1.8.13/aspectjweaver-1.8.13.jar。之后,我们需要在 resources 中配置 aop.xml 文件(只需要配置 Aspects 和需要被织入的类即可。),放置在 META-INF 目录中(resource/META-INF/aop.xml)
  • 你看这里面的增删改查想不想最开始的我吃我老婆做的饭,然后前面和后面加的验证参数、日志、事务等就像是后期加的新需求,你吃饭前得干啥吃饭后得干啥。刚好对应了Spring AOP 一共有三种配置方式,并且Spring 做到了很好地向下兼容
    在这里插入图片描述

    • 方式一:使用Spring的API有个注意点,返回的代理类写的时候要写接口而不是接口的某个子实现类 【Spring 1.2 基于接口的配置:最早的 Spring AOP 是完全基于几个接口的】在这里插入图片描述

      • 其实总的来说,第一种基于接口的配置方式,咱们使用的话大体思路就是:【先用Advice、Advisor、Interceptor ,试试水】
        • 咱们项目中或者自己DIY出自己的接口以及接口子实现类。
        • 然后呢,可以定义两个 advice【**两个 Advice 分别用于对 UserService被代理的接口或者说被拦截的接口执行前和方法返回后
          • 代理模式需要一个接口、一个具体实现类,然后就是定义一个代理类,用来包装实现类,添加自定义逻辑,在使用的时候,需要用代理类来生成实例,然后用这个实例具体实现咱们的想法
        • 然后呢,写好咱们的xml配置文件,里面内容肯定得有两个接口子实现类对应bean定义、两个advice、以及代理相关如下图:
          在这里插入图片描述
          • 问题就出现了,如果我们需要拦截 XxxService1 中的方法,那么我们还需要定义一个 XxxService1 的代理。如果还要拦截 XxxService2,得定义一个 XxxService2的代理…并且我们的拦截器对类中所有的方法都进行了拦截。我们需要只拦截特定的方法。
          • 在上面的配置中,配置拦截器的时候,interceptorNames 除了指定为 Advice,是还可以指定为 Interceptor 和 Advisor 的。【Advisor 内部需要指定一个 Advice,Advisor 负责匹配或者说决定该拦截哪些方法或者说精确定位到需要被拦截的方法,拦截后需要完成的工作还是内部的 Advice 来做。或者说advisor 内部包装了 advice,advisor 负责决定拦截哪些方法,内部 advice 定义拦截后的逻辑,比如实现我们需要的自定义拦截功能、拦截后的逻辑处理。。】
            在这里插入图片描述
          • 除了Advice 和 Advisor ,还有Interceptor,瞅瞅Interceptor实现代码
            在这里插入图片描述
          • Advice、Advisor、Interceptor 有个共同的问题,那就是我们得为每个 bean 都配置一个代理,之后获取 bean 的时候需要获取这个代理类的 bean 实例(如 (UserService) context.getBean("userServiceProxy")),这显然非常不方便,不利于我们之后要使用的自动根据类型注入。所以引出了autoproxy。
        • autoproxy:是实现自动代理的,也就是说当 Spring 发现一个 bean 需要被切面织入的时候,Spring 会自动生成这个 bean 的一个代理来拦截方法的执行而不需要显式地指定代理类的 bean,确保定义的切面能被执行
          • 去掉上面三个Advice、Advisor、Interceptor中原来的 ProxyFactoryBean 的配置,改为使用 BeanNameAutoProxyCreator 来配置BeanNameAutoProxyCreator 非常好用,它需要指定被拦截类名的模式(如 *ServiceImpl),它可以配置多次,这样就可以用来匹配不同模式的类了。】
            在这里插入图片描述
          • 在 BeanNameAutoProxyCreator 同一个包中,还有一个非常有用的类 DefaultAdvisorAutoProxyCreator【BeanNameAutoProxyCreator 是自己匹配方法,然后交由内部配置 advice 来拦截处理,DefaultAdvisorAutoProxyCreator 是让 ioc 容器中的所有 advisor 来匹配方法,advisor 内部都是有 advice 的,让它们内部的 advice 来执行拦截处理
        • 配置 DefaultAdvisorAutoProxyCreator使得所有的 Advisor 自动生效,无须其他配置
        • 那DefaultAdvisorAutoProxyCreator 类是怎么一步步实现的动态代理,
          • DefaultAdvisorAutoProxyCreator 的继承结构可以看出 DefaultAdvisorAutoProxyCreator 最后居然是一个 BeanPostProcessor
            在这里插入图片描述
          • BeanPostProcessor 的两个方法,分别在 init-method 的前后得到执行
            public interface BeanPostProcessor {
                Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException;
                Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException;
            }
            
          • 继承结构中的AbstractAutowireCapableBeanFactory中的doCreateBean(final String beanName, final RootBeanDefinition mbd, final Object[] args)方法会创建实例、装载属性以及调用initializeBean(…) ,initializeBean方法中会调用 BeanPostProcessor 中的方法进行初始化
            在这里插入图片描述
          • 然后继承结构中的还有一点就是,DefaultAdvisorAutoProxyCreator 的继承结构中,postProcessAfterInitialization() 方法在其父类 AbstractAutoProxyCreator 这一层被覆写了:postProcessAfterInitialization重写后会return wrapIfNecessary(bean, beanName, cacheKey);这个wrapIfNecessary方法将返回代理类(如果需要的话)
            在这里插入图片描述
            • getAdvicesAndAdvisorsForBean(bean.getClass(), beanName, null),这个方法将得到所有的可用于拦截当前 bean 的 advisor、advice、interceptor。
            • 另一个就是 TargetSource ,它用于封装真实实现类的信息,上面用了 SingletonTargetSource 这个实现类
          • 然后再看,上面 创建代理类的关键就是 createProxy(…) 方法这个createProxy(…) 方法主要是在内部创建了一个 ProxyFactory 的实例,然后 set 了一大堆内容,剩下的工作就都是这个 ProxyFactory 实例的了,也就是 通过这个ProxyFactory 实例来创建代理: 而这个ProxyFactory 也是通过它肚子里面的getProxy(classLoader)创建代理的
          • ProxyFactory 肚里面的getProxy(classLoader)方法首先通过 createAopProxy() 创建一个 AopProxy 的实例:
            在这里插入图片描述
            • 创建 AopProxy 之前,你看上面方法内容就可以知道,我们需要一个 AopProxyFactory 实例,而这个AopProxyFactory 实例的获取是在ProxyCreatorSupport 的构造方法中,而ProxyCreatorSupport 的构造方法又是这样:this.aopProxyFactory = new DefaultAopProxyFactory();
            • 所以,我们就回到了DefaultAopProxyFactory 中继续看createAopProxy
          • createAopProxy 方法里面主要的逻辑是,有可能返回 JdkDynamicAopProxy 实例,也有可能返回 ObjenesisCglibAopProxy 实例。如果被代理的目标类实现了一个或多个自定义的接口,那么就会使用 JDK 动态代理,如果没有实现任何接口,会使用 CGLIB 实现代理,如果设置了 proxy-target-class="true",那么都会使用 CGLIB
            • 两个 AopProxy 实现类的 getProxy(classLoader) 实现:
              • JDK 动态代理(JDK代理实现 )JdkDynamicAopProxy 类中的getProxy(classLoader)
                @Override
                public Object getProxy(ClassLoader classLoader) {
                   if (logger.isDebugEnabled()) {
                      logger.debug("Creating JDK dynamic proxy: target source is " + this.advised.getTargetSource());
                   }
                   Class<?>[] proxiedInterfaces = AopProxyUtils.completeProxiedInterfaces(this.advised, true);
                   findDefinedEqualsAndHashCodeMethods(proxiedInterfaces);
                   //java.lang.reflect.Proxy.newProxyInstance(…) 方法需要三个参数,第一个是 ClassLoader,第二个参数代表需要实现哪些接口,第三个参数最重要,是 InvocationHandler 实例,我们看到这里传了 this,因为 JdkDynamicAopProxy 本身实现了 InvocationHandler 接口。
                   return Proxy.newProxyInstance(classLoader, proxiedInterfaces, this);
                }
                
                • 可以看出getProxy的最后一句是return Proxy.newProxyInstance(classLoader, proxiedInterfaces, this);java.lang.reflect.Proxy.newProxyInstance(…) 方法需要三个参数,第一个是 ClassLoader,第二个参数代表需要实现哪些接口,第三个参数最重要,是 InvocationHandler 实例,我们看到这里传了 this,因为 JdkDynamicAopProxy 本身实现了 InvocationHandler 接口。【 JdkDynamicAopProxy 对InvocationHandler 的实现逻辑大概就是在说 在执行每个方法的时候,判断下该方法是否需要被一次或多次增强(执行一个或多个 advice)。】
                  在这里插入图片描述
              • CGLIB 的代理实现 :ObjenesisCglibAopProxy#getProxy(classLoader),ObjenesisCglibAopProxy 继承了 CglibAopProxy,而 CglibAopProxy 继承了 AopProxy。也就是说getProxy(classLoader) 方法在ObjenesisCglibAopProxy的父类 CglibAopProxy 类中:CGLIB 生成代理的核心类是 Enhancer 类
                • ObjenesisCglibAopProxy 使用了 Objenesis 这个库,和 cglib 一样,我们不需要在 maven 中进行依赖,因为 spring-core.jar 直接把它的源代码也搞过来了。
            • JDK 动态代理基于接口,所以只有接口中的方法会被增强,而 CGLIB 基于类继承,需要注意就是如果方法使用了 final 修饰,或者是 private 方法,是不能被增强的。
          • 通过ProxyFactory 肚里面的getProxy(classLoader)方法首先通过 createAopProxy() 创建一个 AopProxy 的实例之后,就该回到ProxyFactory 肚里面的getProxy(classLoader)方法的代码了呗。
    • 方式二:使用咱们的自定义类 【Spring 2.0 schema-based 配置:Spring 2.0 以后使用 XML 的方式来配置,也就是基于 命名空间的 XML 配置,使用 命名空间 】在这里插入图片描述

      • Spring 解析各个命名空间的是通过各种 *NamespaceHandler),解析 的源码也是在 org.springframework.aop.config.AopNamespaceHandler 中。
      • 所有的配置都在 aop:config 下面。<aop:aspect > 中需要指定一个 bean,我们知道该 bean 中我们需要写处理代码。然后,我们写好 Aspect 代码后,将其“织入”到合适的 Pointcut 中(需要配置 Pointcut),这就是面向切面
      • 可以在 <aop:aspect />内部配置 Pointcut,这样该 Pointcut 仅用于该 Aspect;也可以在aop:config分别配置 aop:pointcut...和aop:aspect...
    • 方式三:用注解实现【Spring 2.0 @AspectJ 配置:使用注解的方式来配置,这种方式感觉是最方便的。这里虽然叫做 @AspectJ,但是这个和 AspectJ 其实没啥关系。】
      在这里插入图片描述

      • Spring 延用了 AspectJ 中的概念,包括使用了 AspectJ 提供的 jar 包(需要依赖 aspectjweaver.jar 这个包,这个包来自于 AspectJ)中的注解,但是不依赖于其实现功能。【如 @Aspect、@Pointcut、@Before、@After 等注解都是来自于 AspectJ提供的 jar 包中的注解,但是功能的实现是纯 Spring AOP 自己实现的。】
        在这里插入图片描述
      • Aspect:一旦开启了下面的两个配置,那么所有使用 @Aspect 注解的 bean 都会被 Spring 当做用来实现 AOP 的配置类,我们称之为一个 Aspect。或者说 开启 @AspectJ 的两种方式【原理是一样的,都是通过注册一个 bean 来实现的。】
        • 在 xml 中配置:aop:aspectj-autoproxy/
          • 解析 aop:aspectj-autoproxy/ 需要用到 AopNamespaceHandler:这个AopNamespaceHandler中Spring 注册了一个 AnnotationAwareAspectJAutoProxyCreator 的 bean,beanName 为:“org.springframework.aop.config.internalAutoProxyCreator”。再看看AnnotationAwareAspectJAutoProxyCreator 的继承结构可以知道AnnotationAwareAspectJAutoProxyCreator 和前面的 DefaultAdvisorAutoProxyCreator 一样,它也是一个 BeanPostProcessor
            在这里插入图片描述
        • 使用 @EnableAspectJAutoProxy【@Aspect 注解要作用在 bean 上面,不管是使用 @Component 等注解方式,还是在 xml 中配置 bean(<bean id="myAspectBean" class="org.xxx...">),首先它需要是一个 bean
          在这里插入图片描述
        • 在@Aspect 注解的 bean 中,我们需要配置Pointcut和 Advice 【切点,用于定义哪些方法需要被增强或者说需要被拦截(类似于Advisor 的方法匹配。)】。Spring AOP 只支持 bean 中的方法(不像 AspectJ 那么强大),所以我们可以认为 Pointcut 就是用来匹配 Spring 容器中的所有 bean 的方法的
          //@Pointcut 中使用了 execution 来正则匹配方法签名,这也是最常用的
          @Pointcut("execution(* transfer(..))")// the pointcut expression
          private void anyOldTransfer() {}// the pointcut signature
          
        • 除了 execution还有其他的几个比较常用的匹配方式:
          在这里插入图片描述
      • 对于 web 开发者,Spring 有个最佳实践:定义一个 SystemArchitecture【实际写代码的时候,不要把所有的切面都揉在一个 class 中】:这个SystemArchitecture 对应的Aspect 定义了一堆的 Pointcut【配置 pointcut 就是配置我们需要拦截哪些方法,然后配置需要对这些被拦截的方法做什么,也就是前面的 Advice。,随后在任何需要 Pointcut 的地方都可以直接引用(如 xml 中的 pointcut-ref=“”)
        @Aspect
        public class SystemArchitecture {
        
            // web 层
            @Pointcut("within(com.javadoop.web..*)")
            public void inWebLayer() {}
        
            // service 层
            @Pointcut("within(com.javadoop.service..*)")
            public void inServiceLayer() {}
        
            // dao 层
            @Pointcut("within(com.javadoop.dao..*)")
            public void inDataAccessLayer() {}
        
            // service 实现,注意这里指的是方法实现,其实通常也可以使用 bean(*ServiceImpl)
            @Pointcut("execution(* com.javadoop..service.*.*(..))")
            public void businessService() {}
        
            // dao 实现
            @Pointcut("execution(* com.javadoop.dao.*.*(..))")
            public void dataAccessOperation() {}
        }
        

然后呢,下来就是买框图赠送的小礼品喽:
在这里插入图片描述

  • Spring 事务实现方式有哪些?Spring 管理事务的方式有几种
    • 编程式事务管理:这意味着你可以通过编程的方式管理事务,这种方式带来了很大的灵活性,但很难维护。【 在代码中硬编码(不推荐使用) : 通过 TransactionTemplate或者 TransactionManager 手动管理事务,实际应用中很少使用
      在这里插入图片描述
    • 声明式事务管理:这种方式意味着你可以将事务管理和业务代码分离。你只需要通过注解或者XML配置管理事务推荐使用(代码侵入性最小),实际是通过 AOP 实现(基于@Transactional 的全注解方式使用最多)】,下面就以通过注解实现来举例:
      在这里插入图片描述
      在这里插入图片描述
      在这里插入图片描述
      在这里插入图片描述
      • 通过注解实现
        在这里插入图片描述
        在这里插入图片描述
        比如像这样使用jdbcTemplate
        在这里插入图片描述
  • Spring 框架中,事务管理相关最重要的 3 个接口如下:【PlatformTransactionManager 接口可以被看作是事务上层的管理者,而 TransactionDefinition 和 TransactionStatus 这两个接口可以看作是事务的描述。
    • PlatformTransactionManager: (平台)事务管理器或者叫事务管理接口,Spring 事务策略的核心【Spring 并不直接管理事务,而是提供了多种事务管理器Spring 事务管理器的接口【为什么要定义或者抽象出来一个接口:主要是因为要将事务管理行为抽象出来,然后不同的平台去实现它,这样我们可以保证提供给外部的行为不变,方便我们扩展。】是: PlatformTransactionManager 。通过这个接口,Spring 为各个平台如 JDBC(DataSourceTransactionManager)、Hibernate(HibernateTransactionManager)、JPA(JpaTransactionManager)等都提供了对应的事务管理器,但是具体的实现就是各个平台自己的事情了。】
      • PlatformTransactionManager 会根据 TransactionDefinition 的定义比如事务超时时间、隔离级别、传播行为等来进行事务管理
        在这里插入图片描述
    • TransactionDefinition: 事务定义信息(事务隔离级别、传播行为、超时、只读、回滚规则)或者叫事务属性。【事务属性可以理解成事务的一些基本配置,描述了事务策略如何应用到方法上。】
      • 事务管理器接口 PlatformTransactionManager 通过 getTransaction(TransactionDefinition definition) 方法来得到一个事务,这个方法里面的参数是 TransactionDefinition 类 ,这个类就定义了一些基本的事务属性。
      • 事务属性包含了 5 个方面:TransactionDefinition 接口中定义了 5 个方法以及一些表示事务属性的常量比如隔离级别、传播行为等等。
        在这里插入图片描述
        • 隔离级别
          • spring 事务隔离级别有哪些?【为了方便使用,Spring 也相应地定义了一个枚举类:Isolation】
            在这里插入图片描述
            在这里插入图片描述
            • DEFAULT:采用 DB 默认的事务隔离级别【TransactionDefinition.ISOLATION_DEFAULT :使用后端数据库默认的隔离级别,MySQL 默认采用的 REPEATABLE_READ 隔离级别 Oracle 默认采用的 READ_COMMITTED 隔离级别.】
            • READ_UNCOMMITTED:读未提交【最低的隔离级别,使用这个隔离级别很少,因为它允许读取尚未提交的数据变更,可能会导致脏读、幻读或不可重复读】
            • READ_COMMITTED:读已提交【TransactionDefinition.ISOLATION_READ_COMMITTED : 允许读取并发事务已经提交的数据,可以阻止脏读,但是幻读或不可重复读仍有可能发生】
            • REPEATABLE_READ:可重复读【TransactionDefinition.ISOLATION_REPEATABLE_READ : 对同一字段的多次读取结果都是一致的,除非数据是被本身事务自己所修改,可以阻止脏读和不可重复读,但幻读仍有可能发生。】
            • SERIALIZABLE:串行化【TransactionDefinition.ISOLATION_SERIALIZABLE : 最高的隔离级别,完全服从 ACID 的隔离级别。所有的事务依次逐个执行,这样事务之间就完全不可能产生干扰,也就是说,该级别可以防止脏读、不可重复读以及幻读。但是这将严重影响程序的性能。通常情况下也不会用到该级别。】
        • 传播行为
          在这里插入图片描述
          • spring事务定义的传播规则:【事务传播行为是为了解决业务层方法之间互相调用的事务问题:当事务方法被另一个事务方法调用时【举个例子:我们在 A 类的aMethod()方法中调用了 B 类的 bMethod() 方法。这个时候就涉及到业务层方法之间互相调用的事务问题。如果我们的 bMethod()如果发生异常需要回滚,如何配置事务传播行为才能让 aMethod()也跟着回滚呢?这个时候就需要事务传播行为的知识了】,必须指定事务应该如何传播。例如:方法可能继续在现有事务中运行,也可能开启一个新事务,并在自己的事务中运行。】
            • 为了方便使用,Spring 相应地定义了一个枚举类:Propagation
              在这里插入图片描述
              在这里插入图片描述
          • TransactionDefinition.PROPAGATION_REQUIRED:【使用的最多的一个事务传播行为,我们平时经常使用的@Transactional注解默认使用就是这个事务传播行为。如果当前存在事务,则加入该事务;如果当前没有事务,则创建一个新的事务。】 支持当前事务,如果当前没有事务,就新建一个事务。这是最常见的选择(当前方法「必须在一个具有事务的上下文中运行」,如有客户端有事务在进行,那么被调用端将在该事务中运行,否则的话重新开启一个事务。(如果被调用端发生异常,那么调用端和被调用端事务都将回滚))
            在这里插入图片描述
            • @Transactional(rollbackFor = Exception.class)注解:
              • 在要开启事务的方法上使用@Transactional(rollbackFor = Exception.class)注解即可表示开启事务。
                • @Transactional 注解一般可以作用在类或者方法上:
                  • 作用于类:当把@Transactional 注解放在类上时,表示所有该类的 public 方法都配置相同的事务属性信息。同时,我们也可以在方法级别使用该标注来覆盖类级别的定义。如果类或者方法加了这个注解,那么这个类里面的方法抛出异常,就会回滚,数据库里面的数据也会回滚。
                  • 作用于方法:当类配置了@Transactional,方法也配置了@Transactional,方法的事务会覆盖类的事务配置信息。【推荐将注解使用于方法上,不过需要注意的是:该注解只能应用到 public 方法上,否则不生效。】
              • @Transactional 的常用配置参数:@Transactional注解源码里面包含了基本事务属性的配置:
                在这里插入图片描述
                在这里插入图片描述
              • @Transactional 事务注解原理:@Transactional 的工作机制是基于 AOP 实现的,AOP 又是使用动态代理实现的。如果目标对象实现了接口,默认情况下会采用 JDK 的动态代理,如果目标对象没有实现了接口,会使用 CGLIB 动态代理。DefaultAopProxyFactory中的createAopProxy() 方法 决定了是使用 JDK 还是 Cglib 来做动态代理【如果一个类或者一个类中的 public 方法上被标注@Transactional 注解的话,Spring 容器就会在启动的时候为这个被@Transactional标注的方法对应的类创建一个代理类,在调用被@Transactional 注解的 public 方法的时候,实际调用的是,TransactionInterceptor 类中的 invoke()方法。这个方法的作用就是在目标方法之前开启事务,方法执行过程中如果遇到异常的时候回滚事务,方法调用完成之后提交事务
                在这里插入图片描述
              • Spring AOP 自调用问题:若同一类中的其他没有 @Transactional 注解的方法内部调用有 @Transactional 注解的方法,有@Transactional 注解的方法的事务会失效。这是由于Spring AOP代理的原因造成的,因为只有当 @Transactional 注解的方法在类以外被调用的时候,Spring 事务管理才生效。解决办法就是避免同一类中自调用或者使用 AspectJ 取代 Spring AOP 代理。
                在这里插入图片描述
              • Exception 分为运行时异常 RuntimeException 和非运行时异常。事务管理对于企业应用来说是至关重要的,即使出现异常情况,它也可以保证数据的一致性。当 @Transactional 注解作用于类上时,该类的所有 public 方法将都具有该类型的事务属性,同时,我们也可以在方法级别使用该标注来覆盖类级别的定义。如果类或者方法加了这个注解,那么这个类里面的方法抛出异常,就会回滚,数据库里面的数据也会回滚在 @Transactional 注解中如果不配置rollbackFor属性,那么事务只会在遇到RuntimeException的时候才会回滚,加上 rollbackFor=Exception.class,可以让事务在遇到非运行时异常时也回滚
          • PROPAGATION_SUPPORTS: 支持当前事务,如果当前没有事务,就以非事务方式执行(当前方法不必需要具有一个事务上下文,但是如果有一个事务的话,它也可以在这个事务中运行)
          • TransactionDefinition.PROPAGATION_MANDATORY: 支持当前事务,如果当前没有事务,就抛出异常(表示当前方法「必须在一个事务中运行」,如果没有事务,将抛出异常)。这个使用的很少。
            在这里插入图片描述
          • TransactionDefinition.PROPAGATION_REQUIRES_NEW: 新建事务,如果当前存在事务,把当前事务挂起(当前方法「必须运行在它自己的事务中」。一个新的事务将启动,而且如果有一个现有的事务在运行的话,则这个方法将在运行期被挂起,直到新的事务提交或者回滚才恢复执行。)
            在这里插入图片描述
            • 创建一个新的事务,如果当前存在事务,则把当前事务挂起。也就是说不管外部方法是否开启事务Propagation.REQUIRES_NEW修饰的内部方法会新开启自己的事务,且开启的事务相互独立,互不干扰
          • PROPAGATION_NOT_SUPPORTED: 以非事务方式执行操作,如果当前存在事务,就把当前事务挂起(方法不应该在一个事务中运行。「如果有一个事务正在运行,他将在运行期被挂起,直到这个事务提交或者回滚才恢复执行」)
          • PROPAGATION_NEVER: 以非事务方式执行,如果当前存在事务,则抛出异常(当方法务不应该在一个事务中运行,如果存在一个事务,则抛出异常
            在这里插入图片描述
          • TransactionDefinition.PROPAGATION_NESTED:如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则进行与PROPAGATION_REQUIRED类似的操作(如果当前方法正有一个事务在运行中,则该方法应该「运行在一个嵌套事务」中,被嵌套的事务可以独立于被封装的事务中进行提交或者回滚。如果封装事务存在,并且外层事务抛出异常回滚,那么内层事务必须回滚,反之,内层事务并不影响外层事务。如果封装事务不存在,则同propagation_required的一样)
            在这里插入图片描述
            • 如果当前存在事务,则创建一个事务作为当前事务的嵌套事务来运行;如果当前没有事务,则该取值等价于TransactionDefinition.PROPAGATION_REQUIRED。
        • 回滚规则
        • 是否只读
        • 事务超时
    • TransactionStatus: 事务运行状态。【TransactionStatus接口用来记录事务的状态 该接口定义了一组方法,用来获取或判断事务的相应状态信息。PlatformTransactionManager.getTransaction(…)方法返回一个 TransactionStatus 对象。】
      在这里插入图片描述
      • TransactionStatus 接口则提供了一些方法来获取事务相应的状态比如是否新事务、是否可以回滚等等
  • Spring框架的事务管理有哪些优点?
    • 它提供了跨不同事务api(如JTA、JDBC、Hibernate、JPA和JDO)的一致编程模型
      • JPA相关注解:
        • 创建表:
          在这里插入图片描述
          • @Entity声明一个类对应一个数据库实体。
          • @Table 设置表名
        • 创建主键:
          • @Id :声明一个字段为主键。使用@Id声明之后,我们还需要定义主键的生成策略。我们可以使用 @GeneratedValue 指定主键生成策略。【@GeneratedValue注解默认使用的策略是GenerationType.AUTO】【一般使用 MySQL 数据库的话,使用GenerationType.IDENTITY策略比较普遍一点(分布式系统的话需要另外考虑使用分布式 ID)。】
            在这里插入图片描述
            • 通过 @GeneratedValue直接使用 JPA 内置提供的四种主键生成策略来指定主键生成策略。private Long id;
              在这里插入图片描述
              • JPA 使用枚举定义了 4 种常见的主键生成策略,如下:
                在这里插入图片描述
          • 通过 @GenericGenerator声明一个主键策略,然后 @GeneratedValue使用这个策略
            在这里插入图片描述
            • JPA提供的主键生成策略有如下几种:
              public class DefaultIdentifierGeneratorFactory
              		implements MutableIdentifierGeneratorFactory, Serializable, ServiceRegistryAwareService {
              
              	@SuppressWarnings("deprecation")
              	public DefaultIdentifierGeneratorFactory() {
              		register( "uuid2", UUIDGenerator.class );
              		register( "guid", GUIDGenerator.class );			// can be done with UUIDGenerator + strategy
              		register( "uuid", UUIDHexGenerator.class );			// "deprecated" for new use
              		register( "uuid.hex", UUIDHexGenerator.class ); 	// uuid.hex is deprecated
              		register( "assigned", Assigned.class );
              		register( "identity", IdentityGenerator.class );
              		register( "select", SelectGenerator.class );
              		register( "sequence", SequenceStyleGenerator.class );
              		register( "seqhilo", SequenceHiLoGenerator.class );
              		register( "increment", IncrementGenerator.class );
              		register( "foreign", ForeignGenerator.class );
              		register( "sequence-identity", SequenceIdentityGenerator.class );
              		register( "enhanced-sequence", SequenceStyleGenerator.class );
              		register( "enhanced-table", TableGenerator.class );
              	}
              
              	public void register(String strategy, Class generatorClass) {
              		LOG.debugf( "Registering IdentifierGenerator strategy [%s] -> [%s]", strategy, generatorClass.getName() );
              		final Class previous = generatorStrategyToClassNameMap.put( strategy, generatorClass );
              		if ( previous != null ) {
              			LOG.debugf( "    - overriding [%s]", previous.getName() );
              		}
              	}
              
              }
              
        • 创建或者设置字段类型:@Column 声明字段。
          在这里插入图片描述
        • 指定不持久化的特定字段:@Transient :声明不需要与数据库映射的字段,在保存的时候不需要保存进数据库 。
          在这里插入图片描述
        • 声明大字段:@Lob:声明某个字段为大字段。
          在这里插入图片描述
        • 创建枚举类型的字段:可以使用枚举类型的字段,不过枚举字段要用@Enumerated注解修饰。
          在这里插入图片描述
        • 增加审计字段:只要继承了 AbstractAuditBase的类都会默认加上下面四个字段。【审计功能主要是帮助我们记录数据库操作的具体行为比如某条记录是谁创建的、什么时间创建的、最后修改人是谁、最后修改时间是什么时候
          在这里插入图片描述
        • 删除/修改数据:
          在这里插入图片描述
        • 关联关系:@OneToOne 声明一对一关系、@OneToMany 声明一对多关系、@ManyToOne 声明多对一关系、@ManyToMany 声明多对多关系
      • 如何使用 JPA 在数据库中非持久化一个字段?
        在这里插入图片描述
    • 它为编程事务管理提供了比JTA等许多复杂事务API更简单的API
    • 它支持声明式事务管理
    • 很好地集成了Spring的各种数据访问抽象
      在这里插入图片描述
      在这里插入图片描述
  • Spring Security
    • 控制请求访问权限的方法:
      在这里插入图片描述
    • 对密码进行加密:如果我们需要保存密码这类敏感数据到数据库的话,需要先加密再保存。
      • Spring Security 提供了多种加密算法的实现,开箱即用,非常方便。这些加密算法实现类的父类是 PasswordEncoder ,如果你想要自己实现一个加密算法的话,也需要继承 PasswordEncoder。官方推荐使用基于 bcrypt 强哈希函数的加密算法实现类。
        在这里插入图片描述
      • 如果我们在开发过程中,突然发现现有的加密算法无法满足我们的需求,需要更换成另外一个加密算法,推荐的做法是通过 DelegatingPasswordEncoder 【DelegatingPasswordEncoder 其实就是一个代理类,并非是一种全新的加密算法,它做的事情就是代理上面提到的加密算法实现类。在 Spring Security 5.0之后,默认就是基于 DelegatingPasswordEncoder 进行密码加密的】兼容多种不同的密码加密方案,以适应不同的业务需求
  • 最后的最后,切面、切点、增强、连接点,是 AOP 中非常重要的概念,我们可以把 Spring AOP 技术看作为蛋糕做奶油夹层的工序。如果我们希望找到一个合适的地方把奶油注入蛋糕胚子
    在这里插入图片描述

巨人的肩膀:
https://www.bilibili.com/video/BV1PE411i7CV?p=60
https://www.javalearn.cn/
深入理解Java虚拟机
芋道源码老师关于Spring事务的一些注意事项
极客时间上的各位老师

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值