美文网首页
spring整理

spring整理

作者: 昵称已使用换一个吧 | 来源:发表于2021-07-06 18:00 被阅读0次

    ioc

    什么是ioc?

    1.IOC是Inversion of Control的缩写,翻译为控制反转。ioc是容器, 控制反转,目的:降低耦合度,高内聚,低耦合。把对象创建和对象之间的调用过程,交给Spring进行管理

    2.IOC也叫依赖注入(DI)
    所谓依赖注入,就是由IOC容器在运行期间,动态地将某种依赖关系注入到对象之中。所以,依赖注入(DI)和控制反转(IOC)是从不同的角度的描述的同一件事情,就是指通过引入IOC容器,利用依赖关系注入的方式,实现对象之间的解耦。

    3.IOC的优缺点
    第一、软件系统中由于引入了第三方IOC容器,生成对象的步骤变得有些复杂,本来是两者之间的事情,又凭空多出一道手续,所以,我们在刚开始使用IOC框架的时候,会感觉系统变得不太直观。所以,引入了一个全新的框架,就会增加团队成员学习和认识的培训成本,并且在以后的运行维护中,还得让新加入者具备同样的知识体系。
    第二、由于IOC容器生成对象是通过反射方式,在运行效率上有一定的损耗。如果你要追求运行效率的话,就必须对此进行权衡。
    第三、具体到IOC框架产品(比如:Spring)来讲,需要进行大量的配制工作,比较繁琐,对于一些小的项目而言,客观上也可能加大一些工作成本。
    第四、IOC框架产品本身的成熟度需要进行评估,如果引入一个不成熟的IOC框架产品,那么会影响到整个项目,所以这也是一个隐性的风险。

    反射

    1.什么是反射
    JAVA反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法,对于任意一个对象,都能够调用它的任意一个方法和属性,这种动态获取的信息以及动态调用对象的方法的功能称为java语言的反射机制。

    说一下反射

    package com.reflect;
    
    public class Animal {
    
        public String name = "Dog";
        private int age = 30;
    
        //默认无参构造函数
        public Animal() {
            System.out.println("Animal");
        }
    
        //带参数的构造函数
        public Animal(String name, int age) {
            System.out.println(name + "," + age);
        }
    
        //公开 方法  返回类型和参数均有
        public String sayName(String name) {
            return "Hello," + name;
        }
    
    }
    
    package com.reflect;
    
    import java.lang.reflect.Constructor;
    
    public class ReflectTest {
       public static void main(String[] args) throws Exception {
    
           //1、加载类,指定类的完全限定名:包名+类名
           Class c1 = Class.forName("com.reflect.Animal");
           System.out.println(c1); //打印c1,发现值和字节码中的类的名称一样
    
           //2、解刨(反射)类c1的公开构造函数,且参数为null
           Constructor ctor1 = c1.getConstructor();
    
           //3、构造函数的用途,就是创建类的对象(实例)的
           //除了私有构造函数外(单列模式,禁止通过构造函数创建类的实例,保证一个类只有一个实例)
           //ctor1.newInstance()默认生成一个Object对象,我们需要转化成我们要的Animal类对象
           Animal a1 = (Animal) ctor1.newInstance();
    
           //4、证明一下a1确实是Animal的实例,我们通过访问类中的变量来证明
           System.out.println(a1.name);
    
       }
    }
    
    

    获得类中的变量(字段)和方法,两种方式,一个是getXXX 获得本类中的所有公有的字段,并获得指定对象的字段值,一个是getDeclaredXXX,获得类中的所有的字段 包括public、private和protected,不包括父类中申明的字段,demo

    package com.reflect;
    
    import java.lang.reflect.Constructor;
    import java.lang.reflect.Field;
    import java.lang.reflect.Method;
    
    public class ReflectTest {
        public static void main(String[] args) throws Exception {
    
            System.out.println("A(无参构造函数)--加载类、反射类的构造函数、利用构造函数new一个Animal实例instance--");
    
            //1、加载类,指定类的完全限定名:包名+类名
            Class c1 = Class.forName("com.reflect.Animal");
            System.out.println(c1); //打印c1,发现值和字节码中的类的名称一样
    
            //2、解刨(反射)类c1的公开构造函数,且参数为null
            Constructor ctor1 = c1.getConstructor();
    
            //3、构造函数的用途,就是创建类的对象(实例)的
            //除了私有构造函数外(单列模式,禁止通过构造函数创建类的实例,保证一个类只有一个实例)
            //ctor1.newInstance()默认生成一个Object对象,我们需要转化成我们要的Animal类对象
            Animal a1 = (Animal) ctor1.newInstance();
    
            //4、证明一下a1确实是Animal的实例,我们通过访问类中的变量来证明
            System.out.println(a1.name);
    
            System.out.println("A(有参构造函数)--加载类、反射类的构造函数、利用构造函数new一个Animal实例instance--");
    
            //2.b、 解刨(反射)类c1的公开构造函数,参数为string和int
            Constructor ctor2 = c1.getConstructor(String.class, int.class);
            Animal a2 = (Animal) ctor2.newInstance("cat", 20);
    
            System.out.println("B--获得本类中的所有的字段----------------------------");
    
            //5、获得类中的所有的字段  包括public、private和protected,不包括父类中申明的字段
            Field[] fields = c1.getDeclaredFields();
            for (Field field : fields) {
                System.out.println(field);
            }
    
            System.out.println("C--获得本类中的所有公有的字段,并获得指定对象的字段值-----");
    
            //6、获得类中的所有的公有字段
            fields = c1.getFields();
            for (Field field : fields) {
                System.out.println(field + ", 字段值 = " + field.get(a1));
                //注意:私有变量值,无法通过field.get(a1)进行获取值
                //通过反射类中的字段name,修改name的值(注意,原值在类中name="Dog")
                //如果,字段名称等于"name",且字段类型为String,我们就修改字段的值,也就是类中变量name的值
                if (field.getName() == "name" && field.getType().equals(String.class)) {
                    String name_new = (String) field.get(a1); //记得转换一下类型
                    name_new = "哈士奇"; //重新给name赋值
                    field.set(a1, name_new); //设置当前实例a1的name值,使修改后的值生效
                }
            }
    
            System.out.println("利用反射出的字段,修改字段值,修改后的name = "+a1.name);
            System.out.println("D--获取本类中的所有的方法--------------------");
    
            //7、获取本类中所有的方法 包括public、private和protected,不包括父类中申明的方法
            Method[] methods = c1.getDeclaredMethods();
            for (Method m : methods) {
                System.out.println(m);//我们在类Animal中只定义了一个public方法,sayName
            }
    
            System.out.println("E--获取本类中的所有的公有方法,包括父类中和实现接口中的所有public方法-----------");
    
            //8、获取类中所有公有方法,包括父类中的和实现接口中的所有public 方法
            methods = c1.getMethods();
            for (Method m : methods) {
                System.out.println(m);//我们在类Animal中只定义了一个public方法,sayName
            }
    
            System.out.println("F--根据方法名称和参数类型获取指定方法,并唤起方法:指定所属对象a1,并给对应参数赋值-----------");
    
            //9、唤起Method方法(执行) getMethod:第一个参数是方法名,后面跟方法参数的类
            Method sayName = c1.getMethod("sayName", String.class);
            System.out.println(sayName.invoke(a1, "riemann"));
    
        }
    }
    
    输出结果:
    
    A(无参构造函数)--加载类、反射类的构造函数、利用构造函数new一个Animal实例instance--
    class com.reflect.Animal
    Animal
    Dog
    A(有参构造函数)--加载类、反射类的构造函数、利用构造函数new一个Animal实例instance--
    cat,20
    B--获得本类中的所有的字段----------------------------
    public java.lang.String com.reflect.Animal.name
    private int com.reflect.Animal.age
    C--获得本类中的所有公有的字段,并获得指定对象的字段值-----
    public java.lang.String com.reflect.Animal.name, 字段值 = Dog
    利用反射出的字段,修改字段值,修改后的name = 哈士奇
    D--获取本类中的所有的方法--------------------
    public java.lang.String com.reflect.Animal.sayName(java.lang.String)
    E--获取本类中的所有的公有方法,包括父类中和实现接口中的所有public方法-----------
    public java.lang.String com.reflect.Animal.sayName(java.lang.String)
    public final void java.lang.Object.wait() throws java.lang.InterruptedException
    public final void java.lang.Object.wait(long,int) throws java.lang.InterruptedException
    public final native void java.lang.Object.wait(long) throws java.lang.InterruptedException
    public boolean java.lang.Object.equals(java.lang.Object)
    public java.lang.String java.lang.Object.toString()
    public native int java.lang.Object.hashCode()
    public final native java.lang.Class java.lang.Object.getClass()
    public final native void java.lang.Object.notify()
    public final native void java.lang.Object.notifyAll()
    F--根据方法名称和参数类型获取指定方法,并唤起方法:指定所属对象a1,并给对应参数赋值-----------
    Hello,riemann
    

    比如,在Spring中,我们经常看到:

    image.png

    针对上述的配置,我们Spring是怎么帮助我们实例化对象,并放到容器中去了呢? 没错,就是通过反射!!!!

    我们看下,下面的伪代码实现过程:

            //解析<bean .../>元素的id属性得到该字符串值为"sqlSessionFactory" 
            String idStr = "sqlSessionFactory";  
            //解析<bean .../>元素的class属性得到该字符串值为"org.mybatis.spring.SqlSessionFactoryBean"  
            String classStr = "org.mybatis.spring.SqlSessionFactoryBean";  
            //利用反射知识,通过classStr获取Class类对象  
            Class cls = Class.forName(classStr);  
            //实例化对象  
            Object obj = cls.newInstance();  
            //container表示Spring容器  
            container.put(idStr, obj);  
            
            //当一个类里面需要用另一类的对象时,我们继续下面的操作
            
            //解析<property .../>元素的name属性得到该字符串值为“dataSource”  
            String nameStr = "dataSource";  
            //解析<property .../>元素的ref属性得到该字符串值为“dataSource”  
            String refStr = "dataSource";  
            //生成将要调用setter方法名  
            String setterName = "set" + nameStr.substring(0, 1).toUpperCase()  
                    + nameStr.substring(1);  
            //获取spring容器中名为refStr的Bean,该Bean将会作为传入参数  
            Object paramBean = container.get(refStr);  
            //获取setter方法的Method类,此处的cls是刚才反射代码得到的Class对象  
            Method setter = cls.getMethod(setterName, paramBean.getClass());  
            //调用invoke()方法,此处的obj是刚才反射代码得到的Object对象  
            setter.invoke(obj, paramBean); 
    

    是不是很熟悉,虽然是伪代码,但是和我们本篇讲的反射机制的使用是相同的,现在知道我们的反射机制用在哪了吧,没错就是我们经常提到的Java web框架中,里面就用到了反射机制,只要在代码或配置文件中看到类的完全限定名(包名+类名),其底层原理基本上使用的就是Java的反射机制。

    public class Person implements China{
          private String name;
          private int age ;
          private char sex ;
    
          public Person() {
               super ();
         }
    
          public Person(String name, int age, char sex) {
               super ();
               this .name = name;
               this .age = age;
               this .sex = sex;
         }
    
          public String getName() {
               return name ;
         }
    
          public void setName(String name) {
               this .name = name;
         }
    
          public int getAge() {
               return age ;
         }
    
          public void setAge(int age) {
               this .age = age;
         }
    
          public char getSex() {
               return sex ;
         }
    
          public void setSex(char sex) {
               this .sex = sex;
         }
          public void eat()
         {
              System. out .println("吃了" );
         }
    
          @Override
          public String toString() {
               return "Person [name=" + name + ", age=" + age + ", sex=" + sex + "]" ;
         }
    
          @Override
          public void sayChina() {
               // TODO Auto-generated method stub
              System. out .println("作者:" + AUTHOR + "国籍:"+ NATIONAL );
         }
    
          @Override
          public String sayHello(String name, int age, char sex) {
               // TODO Auto-generated method stub
               return "姓名:" + name + "年龄:"+ age + "性别:" + sex;
         }
    
    }
    
    public class ClassDemo {
    
         public static void main(String[] args) {
              Person p1 = new Person("小明" ,20,'男' );
              Person p2 = new Person("小红" ,23,'女' );
    
               //创建Class对象的方式一:(对象.getClass()),获取person类中的字节码文件
               Class class1 = p1.getClass();
              System. out.println(p1.getClass().getName());
               Class class2 = p2.getClass();
              System. out.println(class1 == class2 );
    
              System. out.println("==============================" );
               //创建Class对象的方式二:(类.class:需要输入一个明确的类,任意一个类型都有一个静态的class属性)
               Class class3 = Person.class;
              System. out.println(class1 == class2);
    
              System. out.println("==============================" );
               //创建Class对象的方式三:(forName():传入时只需要以字符串的方式传入即可)
               //通过Class类的一个forName(String className)静态方法返回一个Class对象,className必须是全路径名称;
               //Class.forName()有异常:ClassNotFoundException
    
               Class class4 = null;
               try {
                  class4 = Class.forName("cn.itcast.Person");
              } catch (ClassNotFoundException e) {
                   // TODO Auto-generated catch block
                  e.printStackTrace();
              }
              System. out.println(class4 == class3);
         }
    }
    
    

    在开发中一般使用第三种方法,因为第三种接收的是一个字符串路径,将来可以通过配置文件获取,通用性好;

    spring中的ioc草图

    软件所文本提取.png image.png

    简单描述一下流程

    1.准备容器对象
    2.加载配置文件,解析成对应的BeanDefinition
    3.执行BeanFactoryPostProcessor
    4.准备工作(注册BeanPostProcessor,准备广播器,监听器等工作)
    5.直接执行实例化及初始化相关工作
    6.DiposableBean接口 调用DiposibleBean.destory()
    7.调用<bean>的destroy-method属性指定的初始化方法

    复杂点描述

    IOC 容器的初始化分为三个过程实现:

    Resource资源定位,这个Resouce指的是BeanDefinition的资源定位,这个过程就是容器找数据的过程。

    BeanDefinition的载入过程,这个载入过程是把用户定义好的Bean表示成Ioc容器内部的数据结构,而这个容器内部的数据结构就是BeanDefition。

    向IOC容器注册这些BeanDefinition的过程,这个过程就是将前面的BeanDefition保存到HashMap中的过程。

    Resource定位

    我们一般使用外部资源来描述Bean对象,所以IOC容器第一步就是需要定位Resource外部资源。Resource的定位其实就是BeanDefinition的资源定位,它是由ResourceLoader通过统一的Resource接口来完成的,这个Resource对各种形式的BeanDefinition的使用都提供了统一接口。

    载入

    第二个过程就是BeanDefinition的载入,BeanDefinitionReader读取,解析Resource定位的资源,也就是将用户定义好的Bean表示成IOC容器的内部数据结构也就是BeanDefinition,在IOC容器内部维护着一个BeanDefinition Map的数据结构,通过这样的数据结构,IOC容器能够对Bean进行更好的管理。

    在配置文件中每一个都对应着一个BeanDefinition对象。

    注册

    第三个过程则是注册,即向IOC容器注册这些BeanDefinition,这个过程是通过BeanDefinitionRegistery接口来实现的。

    在IOC容器内部其实是将第二个过程解析得到的BeanDefinition注入到一个HashMap容器中,IOC容器就是通过这个HashMap来维护这些BeanDefinition的。

    上面提到的过程一般是不包括Bean的依赖注入的实现,Bean的载入和依赖注入是两个独立的过程,依赖注入是发生在应用第一次调用getBean向容器所要Bean时。

    当然我们可以通过设置预处理,即对某个Bean设置lazyinit属性,那么这个Bean的依赖注入就会在容器初始化的时候完成。

    经过这 (Resource定位,载入,注册)三个步骤,IOC容器的初始化过程就已经完成了。

    Bean生命周期

    1.实例化
    当容器是beanFactory时,第一次请求时会需要注入依赖时开始进行实例化,当为ApplicationContext容器,容器启动结束后,就开始实例化所有bean。

    2.实例化结束后开始设置属性,即依赖注入。
    3.根据是否实现了Aware接口,将相关的xxxAware实例注入bean。此时bean已经被构造完成。
    4.如果实现BeanPostProcessor 接口,会将bean传递进接口的方法中,进行前置处理和后置处理。这个前置和后置是针对InitialzationBean接口而言的。
    5.InitialzationBean接口方法会对bean增加逻辑,但不能对bean本身进行操作。这种方式会有侵入性,所以Spring提供了init-method属性,用来指定要执行的方法。本质还是执行了InitialzationBean的方法。
    6.最后,检查DisposableBean 和 destroy-method,原理和InitialzationBean接口是一样的。

    总结一下
    Spring框架中,一旦把一个Bean纳入Spring IOC容器之中,这个Bean的生命周期就会交由容器进行管理,一般担当管理角色的是BeanFactory或者ApplicationContext,认识一下Bean的生命周期活动,对更好的利用它有很大的帮助:

    下面以BeanFactory为例,说明一个Bean的生命周期活动。

    Bean的建立, 由BeanFactory读取Bean定义文件,并生成各个实例;

    Setter注入,执行Bean的属性依赖注入;

    BeanNameAware的setBeanName(), 如果实现该接口,则执行其setBeanName方法;

    BeanFactoryAware的setBeanFactory(),如果实现该接口,则执行其setBeanFactory方法;

    BeanPostProcessor的processBeforeInitialization(),如果有关联的processor,则在Bean初始化之前都会执行这个实例的processBeforeInitialization()方法;

    InitializingBean的afterPropertiesSet(),如果实现了该接口,则执行其afterPropertiesSet()方法;

    Bean定义文件中定义init-method;

    BeanPostProcessors的processAfterInitialization(),如果有关联的processor,则在Bean初始化之前都会执行这个实例的processAfterInitialization()方法;

    DisposableBean的destroy(),在容器关闭时,如果Bean类实现了该接口,则执行它的destroy()方法;

    Bean定义文件中定义destroy-method,在容器关闭时,可以在Bean定义文件中使用“destory-method”定义的方法;

    如果使用ApplicationContext来维护一个Bean的生命周期,则基本上与上边的流程相同,只不过在执行BeanNameAware的setBeanName()后,若有Bean类实现了org.springframework.context.ApplicationContextAware接口,则执行其setApplicationContext()方法,然后再进行BeanPostProcessors的processBeforeInitialization() 实际上,ApplicationContext除了向BeanFactory那样维护容器外,还提供了更加丰富的框架功能,如Bean的消息,事件处理机制等

    spring循环依赖注入

    Spring循环依赖的处理

    在Spring中有两种循环依赖的场景
    1.构造器循环依赖,对于构造器循环依赖Spring是无法解决的,会直接抛出循环依赖异常
    2.filed属性循环依赖,在处理属性循环以来的时候,其实只是解决了单利模式的循环的依赖问题,对于prototype的bean,也会直接抛出异常。

    对于singleton的bean,并不是等完全创建好后再加入缓存中,而是在创建过程中通过ObjectFactory提前将自己暴露出来。
    比如说beanA,在初始化A的过程中会提前暴露自己(ObjectFactory)到缓存中。加载过程中发现A依赖B,这时就会去初始化B,初始化B的过程中发现B依赖A,就从缓存中通过ObjectFactory.getObject获取A,等B拿到A,B初始化完成,将B放到缓存中,紧接着A也初始化完成。这时候A和B都放到缓存中,并将ObjectFactory删除。

    image.png image.png

    BeanFactory和FactoryBean

    BeanFactory

    1.BeanFactory是ioc容器的必备数据结构之一,2.BeanFactory中维护了BeanDefinition中的一个BeanDefiniton map
    3.在容器初始化的时候会把资源加载到BeanDefinition中,
    4.根据BeanDefinition的描述对Bean进行和管理,这过程包括处理对象的依赖和Bean生命周期

    FactoryBean

    1.FactoryBean是一个接口,实现这个接口的Bean就代表了一个工厂Bean,
    2.和普通的bean不同,从容器中通过BeanName获取工厂Bean,其实就是FactoryBean的getObject()方法返回的对象,如果需要获取FactoryBean对象本身,就是需要在name前加上&

    总结:

    FactoryBean比BeanFactory在生产上更加灵活,还可以修饰对象,还可以修饰对象,带有一些装饰模式思想在里面。比如使用spring-mybatis时,mybatis底层就是把mapper定义为一个FactoryBean,在getObject中通过动态代理生成一个代理对象放在BeanFactory容器中。

    BeanFactory:在启动的时候不会去实例化Bean,中有从容器中拿Bean的时候才会去实例化;

    ApplicationContext:在启动的时候就把所有的Bean全部实例化了。它还可以为Bean配置lazy-init=true来让Bean延迟实例化;

    AOP

    所谓AOP,即Aspect orientied program,就是面向方面(切面)的编程。
    特点
    让关注点代码与业务代码分离,可以动态地添加和删除在切面上的逻辑而不影响原来的执行代码。

    AOP核心概念
    1、横切关注点
    对哪些方法进行拦截,拦截后怎么处理,这些关注点称之为横切关注点
    2、切面(aspect)
    类是对物体特征的抽象,切面就是对横切关注点的抽象,面向切面编程,就是指 对很多功能都有的重复的代码抽取,再在运行的时候往业务方法上动态植入“切面类代码”。
    3、连接点(joinpoint)
    被拦截到的点,因为Spring只支持方法类型的连接点,所以在Spring中连接点指的就是被拦截到的方法,实际上连接点还可以是字段或者构造器
    4、切入点(pointcut)
    切入点在AOP中的通知和切入点表达式关联,指定拦截哪些类的哪些方法, 给指定的类在运行的时候动态的植入切面类代码。
    5、通知(advice)
    所谓通知指的就是指拦截到连接点之后要执行的代码,通知分为前置、后置、异常、最终、环绕通知五类
    6、目标对象
    被一个或者多个切面所通知的对象。
    7、织入(weave)
    将切面应用到目标对象并导致代理对象创建的过程
    8、引入(introduction)
    在不修改代码的前提下,引入可以在运行期为类动态地添加一些方法或字段
    9、AOP代理(AOP Proxy)
    在Spring AOP中有两种代理方式,JDK动态代理和CGLIB代理
    使用注解

    image.png
    @Aspect指定一个类为切面类;

    @Pointcut(“execution(* cn.itcast.e_aop_anno..(..))”) 指定切入点表达式;

    @Before(“pointCut_()”)前置通知: 目标方法之前执行;

    @After(“pointCut_()”)后置通知:目标方法之后执行(始终执行);

    @AfterReturning(“pointCut_()”)返回后通知: 执行方法结束前执行(异常不执行);

    @AfterThrowing(“pointCut_()”)异常通知: 出现异常时候执行;

    @Around(“pointCut_()”)环绕通知:环绕目标方法执行;
    AOP的实现原理
    Spring AOP使用的动态代理,所谓的动态代理就是说AOP框架不会去修改字节码,而是在内存中临时为方法生成一个AOP对象,这个AOP对象包含了目标对象的全部方法,并且在特定的切点做了增强处理,并回调原对象的方法

    Spring AOP中的动态代理主要有两种方式,JDK动态代理和CGLIB动态代理JDK动态代理通过反射来接收被代理的类,并且要求被代理的类必须实现一个接口。JDK动态代理的核心是InvocationHandler接口和Proxy类

    如果目标类没有实现接口,那么Spring AOP会选择使用CGLIB来动态代理目标类。CGLIB(Code Generation Library),是一个代码生成的类库,可以在运行时动态的生成某个类的子类,注意,CGLIB是通过继承的方式做的动态代理,因此如果某个类被标记为final,那么它是无法使用CGLIB做动态代理的

    Spring中AOP代理由Spring的IOC容器负责生成、管理,其依赖关系也由IOC容器负责管理。因此,AOP代理可以直接使用容器中的其它bean实例作为目标,这种关系可由IOC容器的依赖注入提供。

    Spring创建代理的规则为

    默认使用Java动态代理来创建AOP代理,这样就可以为任何接口实例创建代理了

    当需要代理的类不是代理接口的时候,Spring会切换为使用CGLIB代理,也可强制使用CGLIB

    AOP编程其实是很简单的事情,程序员只需要参与三个部分:

    定义普通业务组件

    定义切入点,一个切入点可能横切多个业务组件

    定义增强处理,增强处理就是在AOP框架为普通业务组件织入的处理动作

    所以进行AOP编程的关键就是定义切入点和定义增强处理,一旦定义了合适的切入点和增强处理,AOP框架将自动生成AOP代理

    即:代理对象的方法=增强处理+被代理对象的方法。

    Spring 是如何管理事务的,事务管理机制
    编程式事务管理:Spring推荐使用TransactionTemplate,实际开发中使用声明式事务较多。

    声明式事务管理:将我们从复杂的事务处理中解脱出来,获取连接,关闭连接、事务提交、回滚、异常处理等这些操作都不用我们处理了,Spring都会帮我们处理。

    声明式事务管理使用了AOP面向切面编程实现的,本质就是在目标方法执行前后进行拦截。在目标方法执行前加入或创建一个事务,在执行方法执行后,根据实际情况选择提交或是回滚事务。

    如何管理的

    Spring事务管理主要包括3个接口,Spring的事务主要是由它们
    (PlatformTransactionManager,TransactionDefinition,TransactionStatus)三个共同完成的。

    1. PlatformTransactionManager:事务管理器–主要用于平台相关事务的管理

    主要有三个方法:

    commit 事务提交;

    rollback 事务回滚;

    getTransaction 获取事务状态。

    2. TransactionDefinition:事务定义信息–用来定义事务相关的属性,给事务管理器PlatformTransactionManager使用

    这个接口有下面四个主要方法:

    getIsolationLevel:获取隔离级别;

    getPropagationBehavior:获取传播行为;

    getTimeout:获取超时时间;

    isReadOnly:是否只读(保存、更新、删除时属性变为false–可读写,查询时为true–只读)

    事务管理器能够根据这个返回值进行优化,这些事务的配置信息,都可以通过配置文件进行配置。
    3. TransactionStatus:事务具体运行状态–事务管理过程中,每个时间点事务的状态信息。

    例如它的几个方法:

    hasSavepoint():返回这个事务内部是否包含一个保存点,

    isCompleted():返回该事务是否已完成,也就是说,是否已经提交或回滚

    isNewTransaction():判断当前事务是否是一个新事务

    声明式事务的优缺点

    优点:不需要在业务逻辑代码中编写事务相关代码,只需要在配置文件配置或使用注解(@Transaction),这种方式没有侵入性。

    缺点:声明式事务的最细粒度作用于方法上,如果像代码块也有事务需求,只能变通下,将代码块变为方法。

    事务的隔离级别

    从理论上来说, 事务应该彼此完全隔离, 以避免并发事务所导致的问题,然而, 那样会对性能产生极大的影响, 因为事务必须按顺序运行, 在实际开发中, 为了提升性能, 事务会以较低的隔离级别运行, 事务的隔离级别可以通过隔离事务属性指定。

    ISOLATION_DEFAULT: 这是一个PlatfromTransactionManager默认的隔离级别,使用数据库默认的事务隔离级别.另外四个与JDBC的隔离级别相对应

    ISOLATION_READ_UNCOMMITTED: 这是事务最低的隔离级别,它充许令外一个事务可以看到这个事务未提交的数据。这种隔离级别会产生脏读,不可重复读和幻像读。

    ISOLATION_READ_COMMITTED: 保证一个事务修改的数据提交后才能被另外一个事务读取。另外一个事务不能读取该事务未提交的数据

    ISOLATION_REPEATABLE_READ: 这种事务隔离级别可以防止脏读,不可重复读。但是可能出现幻像读。它除了保证一个事务不能读取另一个事务未提交的数据外,还保证了避免下面的情况产生(不可重复读)。

    ISOLATION_SERIALIZABLE: 这是花费最高代价但是最可靠的事务隔离级别。事务被处理为顺序执行。除了防止脏读,不可重复读外,还避免了幻像读。

    事务的隔离级别要得到底层数据库引擎的支持, 而不是应用程序或者框架的支持.

    Oracle 支持的 2 种事务隔离级别:READ_COMMITED , SERIALIZABLE

    Mysql 支持 4 中事务隔离级别 默认是REPEATABLE READ隔离级别

    事务回滚属性

    默认情况下只有未检查异常(RuntimeException和Error类型的异常)会导致事务回滚. 而受检查异常不会.

    事务的回滚规则可以通过@Transactional 注解的 rollbackFor 和 noRollbackFor 属性来定义,这两个属性被声明为 Class[] 类型的, 因此可以为这两个属性指定多个异常类。

    rollbackFor: 遇到时必须进行回滚

    noRollbackFor: 一组异常类,遇到时必须不回滚

    不推荐使用的手动回滚事务的方法:
    TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();

    Spring 的不同事务传播行为有哪些,干什么用的?

    当事务方法被另一个事务方法调用时, 必须指定事务应该如何传播. 例如: 方法可能继续在现有事务中运行, 也可能开启一个新事务, 并在自己的事务中运行. 事务的传播行为可以由传播属性指定.

    Spring 定义了7 种类传播行为.(一二两种最常用)

    ==PROPAGATION_REQUIRED==: 如果存在一个事务,则支持当前事务,如果没有事务则开启

    ==PROPAGATION_REQUIRES_NEW==: 总是开启一个新的事务,如果一个事务已经存在,则将这个存在的事务挂起

    PROPAGATION_SUPPORTS: 如果存在一个事务,支持当前事务。如果没有事务,则非事务的执行

    PROPAGATION_MANDATORY: 如果已经存在一个事务,支持当前事务。如果没有一个活动的事务,则抛出异常。

    PROPAGATION_NOT_SUPPORTED: 总是非事务地执行,并挂起任何存在的事务。

    PROPAGATION_NEVER: 总是非事务地执行,如果存在一个活动事务,则抛出异常

    PROPAGATION_NESTED:如果一个活动的事务存在,则运行在一个嵌套的事务中. 如果没有活动事务, 则按TransactionDefinition.PROPAGATION_REQUIRED 属性执行

    Spring 中用到了那些设计模式?

    Spring框架中使用到了大量的设计模式,下面列举了比较有代表性的:

    代理模式—在AOP和remoting中被用的比较多。

    单例模式—在spring配置文件中定义的bean默认为单例模式。

    模板方法—用来解决代码重复的问题。比如. RestTemplate, JmsTemplate, JpaTemplate。

    工厂模式—BeanFactory用来创建对象的实例。

    适配器–spring aop

    装饰器–spring data hashmapper

    观察者– spring 时间驱动模型

    回调–Spring ResourceLoaderAware回调接口

    工厂模式(Factory Method)

    Spring容器就是实例化和管理Bean的工厂

    工厂模式隐藏了创建类的细节,返回值必定是接口或者抽象类,而不是具体的某个对象,工厂类根据条件生成不同的子类实例。当得到子类的实例后,就可以调用基类中的方法,不必考虑返回的是哪一个子类的实例。

    这个很明显,在各种BeanFactory以及ApplicationContext创建中都用到了;

    Spring通过配置文件,就可以管理所有的bean,而这些bean就是Spring工厂能产生的实例,因此,首先我们在Spring配置文件中对两个实例进行配置

    单态模式【单例模式】(Singleton)

    Spring默认将所有的Bean设置成 单例模式,即对所有的相同id的Bean的请求,都将返回同一个共享的Bean实例。这样就可以大大降低Java创建对象和销毁时的系统开销

    使用Spring将Bean设置称为单例行为,则无需自己完成单例模式。

    可以通过singleton=“truefalse” 或者 scope=“?”来指定

    适配器(Adapter)

    在Spring的Aop中,使用的Advice(通知)来增强被代理类的功能。Spring实现这一AOP功能的原理就使用代理模式(1、JDK动态代理。2、CGLib字节码生成技术代理。)对类进行方法级别的切面增强,即,生成被代理类的代理类, 并在代理类的方法前,设置拦截器,通过执行拦截器重的内容增强了代理方法的功能,实现的面向切面编程

    代理(Proxy)

    Spring实现了一种能够通过额外的方法调用完成任务的设计模式 - 代理设计模式,比如JdkDynamicAopProxy和Cglib2AopProxy。

    代理设计模式的一个很好的例子是org.springframework.aop.framework.ProxyFactoryBean该工厂根据Spring bean构建AOP代理。该类实现了定义getObject()方法的FactoryBean接口。此方法用于将需求Bean的实例返回给bean factory。在这种情况下,它不是返回的实例,而是AOP代理。在执行代理对象的方法之前,可以通过调用补充方法来进一步“修饰”代理对象(其实所谓的静态代理不过是在装饰模式上加了个要不要你来干动作行为而已,而不是装饰模式什么也不做就加了件衣服,其他还得由你来全权完成)。

    观察者(Observer)

    定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新。spring中Observer模式常用的地方是listener的实现。如ApplicationListener

    Spring MVC 的工作原理?

    https://www.cnblogs.com/xiaoxi/p/6164383.html

    SpringMVC流程

    用户发送请求至前端控制器DispatcherServlet。

    DispatcherServlet收到请求调用HandlerMapping处理器映射器。

    处理器映射器找到具体的处理器(可以根据xml配置、注解进行查找),生成处理器对象及处理器拦截器(如果有则生成)一并返回给DispatcherServlet。

    DispatcherServlet调用HandlerAdapter处理器适配器。

    HandlerAdapter经过适配调用具体的处理器(Controller,也叫后端控制器)。

    Controller执行完成返回ModelAndView。

    HandlerAdapter将controller执行结果ModelAndView返回给DispatcherServlet。

    DispatcherServlet将ModelAndView传给ViewReslover视图解析器。

    ViewReslover解析后返回具体View。

    DispatcherServlet根据View进行渲染视图(即将模型数据填充至视图中)。

    DispatcherServlet响应用户。

    第一步: 用户发起请求到前端控制器(DispatcherServlet)

    第二步:前端控制器请求处理器映射器(HandlerMappering)去查找处理器(Handle):通过xml配置或者注解进行查找

    第三步:找到以后处理器映射器(HandlerMappering)像前端控制器返回执行链(HandlerExecutionChain)

    第四步:前端控制器(DispatcherServlet)调用处理器适配器(HandlerAdapter)去执行处理器(Handler)

    第五步:处理器适配器去执行Handler

    第六步:Handler执行完给处理器适配器返回ModelAndView

    第七步:处理器适配器向前端控制器返回ModelAndView

    第八步:前端控制器请求视图解析器(ViewResolver)去进行视图解析

    第九步:视图解析器像前端控制器返回View

    第十步:前端控制器对视图进行渲染

    第十一步:前端控制器向用户响应结果


    image.png

    看到这些步骤我相信大家很感觉非常的乱,这是正常的,但是这里主要是要大家理解springMVC中的几个组件

    前端控制器(DispatcherServlet):接收请求,响应结果,相当于电脑的CPU。

    处理器映射器(HandlerMapping):根据URL去查找处理器

    处理器(Handler):(需要程序员去写代码处理逻辑的)

    处理器适配器(HandlerAdapter):会把处理器包装成适配器,这样就可以支持多种类型的处理器,类比笔记本的适配器(适配器模式的应用)

    视图解析器(ViewResovler):进行视图解析,多返回的字符串,进行处理,可以解析成对应的页面

    Spring如何解决循环依赖?

    http://www.importnew.com/17580.html

    一、构造器循环依赖:表示通过构造器注入构成的循环依赖,此依赖是无法解决的,只能抛出BeanCurrentlyInCreationException异常表示循环依赖。

    如在创建CircleA类时,构造器需要CircleB类,那将去创建CircleB,在创建CircleB类时又发现需要CircleC类,则又去创建CircleC,最终在创建CircleC时发现又需要CircleA;从而形成一个环,没办法创建。

    Spring容器将每一个正在创建的Bean 标识符放在一个“当前创建Bean池”中,Bean标识符在创建过程中将一直保持在这个池中,因此如果在创建Bean过程中发现自己已经在“当前创建Bean池”里时将抛出BeanCurrentlyInCreationException异常表示循环依赖;而对于创建完毕的Bean将从“当前创建Bean池”中清除掉

    1)首先让我们看一下配置文件(chapter3/circleInjectByConstructor.xml):

    2)写段测试代码(cn.javass.spring.chapter3.CircleTest)测试一下吧:

    @Test(expected=BeanCurrentlyInCreationException.class)publicvoidtestCircleByConstructor()throwsThrowable{try{newClassPathXmlApplicationContext("chapter3/circleInjectByConstructor.xml");}catch(Exceptione){//因为要在创建circle3时抛出;Throwablee1=e.getCause().getCause().getCause();throwe1;}}

    让我们分析一下吧:

    1、Spring容器创建“circleA” Bean,首先去“当前创建Bean池”查找是否当前Bean正在创建,如果没发现,则继续准备其需要的构造器参数“circleB”,并将“circleA” 标识符放到“当前创建Bean池”;

    2、Spring容器创建“circleB” Bean,首先去“当前创建Bean池”查找是否当前Bean正在创建,如果没发现,则继续准备其需要的构造器参数“circleC”,并将“circleB” 标识符放到“当前创建Bean池”;

    3、Spring容器创建“circleC” Bean,首先去“当前创建Bean池”查找是否当前Bean正在创建,如果没发现,则继续准备其需要的构造器参数“circleA”,并将“circleC” 标识符放到“当前创建Bean池”;

    4、到此为止Spring容器要去创建“circleA”Bean,发现该Bean 标识符在“当前创建Bean池”中,因为表示循环依赖,抛出BeanCurrentlyInCreationException。

    二、setter循环依赖:表示通过setter注入方式构成的循环依赖。

    对于setter注入造成的依赖是通过Spring容器提前暴露刚完成构造器注入但未完成其他步骤(如setter注入)的Bean来完成的,而且只能解决单例作用域的Bean循环依赖。

    addSingletonFactory(beanName,newObjectFactory(){publicObjectgetObject()throwsBeansException{returngetEarlyBeanReference(beanName,mbd,bean);}});

    具体步骤如下:

    1、Spring容器创建单例“circleA” Bean,首先根据无参构造器创建Bean,并暴露一个“ObjectFactory ”用于返回一个提前暴露一个创建中的Bean,并将“circleA” 标识符放到“当前创建Bean池”;然后进行setter注入“circleB”;

    2、Spring容器创建单例“circleB” Bean,首先根据无参构造器创建Bean,并暴露一个“ObjectFactory”用于返回一个提前暴露一个创建中的Bean,并将“circleB” 标识符放到“当前创建Bean池”,然后进行setter注入“circleC”;

    3、Spring容器创建单例“circleC” Bean,首先根据无参构造器创建Bean,并暴露一个“ObjectFactory ”用于返回一个提前暴露一个创建中的Bean,并将“circleC” 标识符放到“当前创建Bean池”,然后进行setter注入“circleA”;进行注入“circleA”时由于提前暴露了“ObjectFactory”工厂从而使用它返回提前暴露一个创建中的Bean;

    4、最后在依赖注入“circleB”和“circleA”,完成setter注入。

    Spring 如何保证 Controller 并发的安全?

    Spring 多线程请求过来调用的Controller对象都是一个,而不是一个请求过来就创建一个Controller对象。

    并发的安全? 原因就在于Controller对象是单例的,那么如果不小心在类中定义了类变量,那么这个类变量是被所有请求共享的,这可能会造成多个请求修改该变量的值,出现与预期结果不符合的异常

    那有没有办法让Controller不以单例而以每次请求都重新创建的形式存在呢

    答案是当然可以,只需要在类上添加注解@Scope(“prototype”)即可,这样每次请求调用的类都是重新生成的(每次生成会影响效率)

    虽然这样可以解决问题,但增加了时间成本,总让人不爽,还有其他方法么?答案是肯定的!

    使用ThreadLocal来保存类变量,将类变量保存在线程的变量域中,让不同的请求隔离开来。

    相关文章

      网友评论

          本文标题:spring整理

          本文链接:https://www.haomeiwen.com/subject/dvnwultx.html