zoukankan      html  css  js  c++  java
  • spring学习笔记

    0.Spring介绍

    • Spring是一个框架,核心技术为IOC和AOP,主要功能在于实现解耦合,同时spring也可以理解为是一个容器(Spring底层使用concurrentHashMap容器存储创建的对象),容器中存放的是Java对象(bean),可以根据需要从容器中取出相应的对象。如何把对象放入spring容器中呢?主要有以下两种方法:
      • 使用xml配置文件,具体来说就是使用<bean>标签。
      • 通过注解将对象放入spring容器中,
    • Spring 容器是一个超级大工厂,负责创建、管理所有的 Java 对象,这些 Java 对象被称 为 Bean。Spring 容器管理着容器中 Bean 之间的依赖关系,Spring 使用“依赖注入”的方式来管理 Bean 之间的依赖关系。使用 IoC 实现对象之间的解耦和。
    • 存在两种方法将对象放入容器中:(1)通过创建spring容器的配置文件,并在配置文件中利用<bean>声明对象。(2)通过一系列注解(注解的底层原理也是基于配置文件)。而后根据需求通过BeanFactory或者 ApplicationContext中的getBean("id")方法获取容器中的对象。

    1.Spring IOC

    1.1 概念

    Spring IOC也称为控制反转,把对象的创建,赋值,管理工作都交给Spring容器实现,也就是对象的创建是由其它外部资源完成。

    • 控制指的就是创建对象,对对象的属性赋值,对象之间的关系管理的权力。

    • 反转指的就是把原来的开发人员管理,创建对象的权限转移给代码之外的容器实现。 由容器代替开发人员管理对象,创建对象。

    • 正转:开发人员主动管理对象。在代码中,使用new构造方法创建对象。

      public static void main(String args[]){
      	Student student = new Student(); // 在代码中, 创建对象。--正转。
      }
      

    IOC的作用:实现解耦合,让对象的管理更加方便(可以在减少代码的改动的基础上,实现不同的功能。)

    1.2 技术实现

    • IoC 是一个概念,是一种思想,其实现方式多种多样。当前比较流行的实现方式是依赖注入(DI)
    • IOC的技术实现:Spring框架使用依赖注入(DI)实现 IoC。而Ioc拥有创建对象的权力,究其根本,Spring底层创利用反射机制实现对象的创建。
    所谓的依赖注入,其实是当一个bean实例引用到了另外一个bean实例时,spring容器帮助我们创建依赖bean实例并注入(传递)到另一个bean中。分析这个过程,可以发现存在着循环依赖等问题。
    

    (另外一种理解方式)在调用无参构造函数创建对象后,就要对对象的属性进行初始化。初始化是由容器自动完成的,整个赋值过程称为依赖注入。

    1.3 spring IOC的简单使用

    • 创建maven项目
    • maven项目引入依赖
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-context</artifactId>
        <version>5.2.5.RELEASE</version>
    </dependency>
    
    • 定义接口和接口的实现类(创建类)
    //接口
    package com.sysu.service;
    public interface SomeService {
        public void doSome();
    }
    
    //接口实现类
    package com.sysu.service.impl;
    import com.sysu.service.SomeService;
    public class SomeServiceImpl implements SomeService {
        @Override
        public void doSome() {
            System.out.println("执行dosome方法");
        }
    }
    
    • 创建spring配置文件,使用<bean>标签声明对象。

    main/resources目录下创建一个beans.xml文件,该文件即为spring的配置文件

    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="http://www.springframework.org/schema/beans"
           xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
           xsi:schemaLocation="http://www.springframework.org/schema/beans
           http://www.springframework.org/schema/beans/spring-beans.xsd">
        
        <!--bean标签的作用:告诉spring创建对象
        id:对象的名称,唯一值。spring通过这个名称找到对象
        class:类的全限定名称(不能是接口,因为spring是反射机制创建对象,必须使用类.
    
        一个bean标签的作用等同于让spring完成Someservice someService(id值) = new SomeserviceImpl();
    	在bean标签内使用<property name="" value=""/>标签可以实现给对象内的属性赋值。
        然后spring会把创建好的对象放入到map中,springMap.put(id的值,对象);
        例如springMap.put("someservice", new SomeserviceImpl())
        一个bean标签声明一个对象。根据id值就能获取唯一对应的对象
        -->
        
        <bean id="someService" class="com.sysu.service.impl.SomeServiceImpl"></bean>
    
    </beans>
    
    <!--
        spring的配置文件
        1.beans: 根标签,spring把java对象成为bean。
        2.spring-beans.xsd是约束文件,和mybatis指定dtd是一样的。
    -->
    
    • 定义测试类,通过ApplicationContext接口和它的实现类ClassPathXmlApplicationContext中的方法getBean完成容器中对象的获取。
    @Test
    public void test02(){
        String config = "beans.xml";
        //创建spring容器对象,同时会创建配置文件中所有bean标签声明的对象(默认采用无参构造函数)
        ApplicationContext ac = new ClassPathXmlApplicationContext(config);
        //根据id从spring容器中获取目标对象
        SomeService someService = (SomeService) ac.getBean("someService");
        someService.doSome();
    }
    

    1.4 基于XML的DI(了解)

    • 在调用无参构造函数创建对象后,就要对对象的属性进行初始化。初始化是由容器自动完成的,整个赋值过程称为依赖注入。 依赖注入(DI)主要分为两种:基于XML的DI(了解) + 基于注解的DI(重点)
    • 基于XML的DI分为三种:set 注入、构造注入以及自动注入,其中自动注入只负责给引用类型属性赋值,分为byName以及byType(见@Autowired注解)两种。
      • byName:按名称注入,类中引用类型的【属性名】和spring容器中bean的【id】一样、数据类型一样,则把这样的bean赋值给引用类型。
    • set注入以及构造注入既可以给简单类型(8种基本数据类型以及string)赋值也可以给引用类型赋值。
      • set注入底层原理:spring会调用类中属性对应的set方法,在set方法中完成属性赋值。
      • 构造注入底层原理:spring调用类的有参构造函数完成属性赋值。
    • 有关基于XML的DI可以参考视频。

    1.5 基于注解的DI(重点)

    使用注解的简单步骤(@Component)

    • 使用maven构建项目

    • 在maven配置文件中加入依赖

      • 在加入spring-context依赖的同时,会自动间接加入spring-aop的依赖。使用注解必须使用spring-aop依赖
    • 创建类,在类中加入spring的注解

    package com.sysu.entity
    
    //@Component注解的作用
    //(1)告诉spring创建一个名为myStudent的对象(默认调用无参构造函数),并放置spring容器中。起作用等同于spring配置文件中的bean标签,该注解有一个value属性,该属性对应的值即为创建的对象的名称,等同于bean Id。
    //(2)@Component("自定义创建对象名")【最常用的方式】
    //(3)value属性可以省略,直接使用@Component即可。则创建类的名称由spring默认提供:类名首字母小写
    //(4)当某个类不属于业务层、持久层、控制层的时候,即使用@Component注解进行对象的创建。
    @Component(value = "myStudent")
    public class Student(){
        private String name;
        private Integer age;
        //省略get、set方法
    }
    
    spring中和@Component注解功能一致、使用语法相同的注解有:
    1. @Repository(用在持久层类的上面): 放在dao的实现类上面,表示创建dao对象, dao对象是能访问数据库的。
    2. @Service(用在业务层类的上面): 放在service的实现类上面,创建service对象, service对象是做业务处理,可以有事务等功能的。
    3. @Controller(用在控制器的上面):放在控制器(处理器)类的上面,创建控制器对象的,控制器对象能够接收用户提交的参数,显示请求的处理结果。
    
    • 创建spring的配置文件(applicationContext.xml),加入一个组件扫描器的标签,说明注解在你的项目中的位置
    <?xml version="1.0" encoding= "UTF-8" ?>
    <beans xmIns="http://www.springframework.org/schema/beans”
    	   xmlns:xsi="http://www.W3.org/2001/XMschema-instance"
       	   xmLns:context="http://www.springframework.org/schema/context"
    	   xsi:schemalocation="http://www.springframework.org/schema/beans
    		http://www.springframework.org/schema/beans/spring-beans.xsd
    		http://www.springframework.org/schema/context
    		http://www.springframework.org/schema/context/spring-context.xsd">
        
            <!--声明组件扫描器(Component-scan),组件就是java对象
            basepackage:指定注解在你的项目中的包名。
            component-scan工作方式: spring会扫描遍历base-package指定的包
     		把包中和子包中的所有类中的注解,按照注解的功能创建对象,或给属性赋值。
            -->
            <context:component-scan base-package="com.sysu.entity"/>
    </beans>
    
    • 测试方法
    @Test
    public void test01(){
        String config = "applicationContext.xml";
        ApplicationContext ctx = new ClassPathXmlApplicationContext(config);
        Student stu = (Student)ctx.getBean("myStudent");
        System.out.println(stu);
    }
    

    @Value

    • @Value:给类中的简单类型(8种基本数据类型+string)属性进行赋值
    • 使用位置: 在属性定义的上面,无需set方法。
    package com.sysu.entity
    
    @Component("myStudent")
    public class Student(){
       
        @Value("张飞")
        private String name;
        @Value("29")
        private Integer age;
    }
    
    @Test
    public void test01(){
        String config = "applicationContext.xml";
        ApplicationContext ctx = new ClassPathXmlApplicationContext(config);
        Student stu = (Student)ctx.getBean("myStudent");
        System.out.println(stu);
    }
    

    @Autowired

    • @Autowired: 给类中的引用类型属性进行赋值。
    • @Autowired使用的是自动注入原理(byName,byType),默认使用的是byType自动注入。
    • 使用的位置:在属性定义的上面,无需set方法。
    • 在本实例中,当扫描到@Autowired注解时,需要提供一个和School类同源的对象,因此还需要先实例化一个School对象mySchool.
    • 同源对象:
      • 由相同类生成的两个对象
      • 类和类之间是继承关系,由这两个类生成的对象也为同源对象
      • 类和类之间是接口与实现类关系,由这两个类生成的对象也为同源对象
    package com.sysu.entity
    
    @Component("myStudent")
    public class Student(){
       
        @Value("张飞")
        private String name;
        @Value("29")
        private Integer age;
        @Autowired
        private School school;
    }
    -------------------------------------------------
    package com.sysu.entity
        
    @Component("mySchool")
    public class School{
        @Value("中山大学")
        private String name;
        @Value("广州")
        private String address;
    }
    
    
    @Test
    public void test01(){
        String config = "applicationContext.xml";
        ApplicationContext ctx = new ClassPathXmlApplicationContext(config);
        Student stu = (Student)ctx.getBean("myStudent");
        System.out.println(stu);
    }
    
    • @Autowired注解 +@Qualifier注解即可实现采用byName的方式对引用类型属性进行赋值
    • @Qualifier注解使用方法:@Qualifier("给属性赋值的对象的名称")注解
    package com.sysu.entity
    
    @Component("myStudent")
    public class Student(){
       
        @Value("张飞")
        private String name;
        @Value("29")
        private Integer age;
        @Autowired
        @Qualifier("mySchool")
        private School school;
    }
    -------------------------------------------------
    package com.sysu.entity
        
    @Component("mySchool")
    public class School{
        @Value("中山大学")
        private String name;
        @Value("广州")
        private String address;
    }
    
    @Test
    public void test01(){
        String config = "applicationContext.xml";
        ApplicationContext ctx = new ClassPathXmlApplicationContext(config);
        Student stu = (Student)ctx.getBean("myStudent");
        System.out.println(stu);
    }
    

    @Resource注解

    • 该注解来自JDK,spring框架提供了对这个注解的功能支持,可以使用该注解给引用类型属性进行赋值,该注解的功能和@Autowired相同,底层原理也是使用自动注入,支持byName(默认)、byType。
    • 使用的位置:在引用类型属性定义的上面,无需set方法。
    • 先采用byName注入,如果失败则采用byType方式注入。
    package com.sysu.entity
    
    @Component("myStudent")
    public class Student(){
       
        @Value("张飞")
        private String name;
        @Value("29")
        private Integer age;
        @Resource("mySchool")
        private School school;
    }
    -------------------------------------------------
    package com.sysu.entity
        
    @Component("mySchool")
    public class School{
        @Value("中山大学")
        private String name;
        @Value("广州")
        private String address;
    }
    
    @Test
    public void test01(){
        String config = "applicationContext.xml";
        ApplicationContext ctx = new ClassPathXmlApplicationContext(config);
        Student stu = (Student)ctx.getBean("myStudent");
        System.out.println(stu);
    }
    

    常用注解参考文章

    1.6 使用多个Spring配置文件

    • 在实际应用里,随着应用规模的增加,系统中Bean数量也大量增加,导致配置文件变得非常庞大、臃肿。为了避免这种情况的产生,提高配置文件的可读性与可维护性,可以将Spring配置文件分解成多个配置文件。
    • 多个配置文件中有一个总配置文件,总配置文件会引入其余配置文件。在 Java 代码中只需要使用总配置文件对容器进行初始化即可。
    例如一个项目中存在三个spring配置文件
    spring-school.xml
    spring-student.xml
    total.xml
    
    total.xml配置文件的内容如下:(classpath为类路径,即配置文件相对于target/classes目录的路径)
    <import resource="classpath:spring-school.xml"/>
    <import resource="classpath:spring-student.xml"/>
    

    1.7 注解与xml的对比

    注解优点:

    • 方便、直观、高效(代码少,没有配置文件的书写那么复杂)。

    注解缺点:

    • 以硬编码的方式写入到 Java 代码中,修改是需要重新编译代码的。

    XML优点:

    • 配置和代码是分离的
    • 在 xml 中做修改,无需编译代码,只需重启服务器即可将新的配置加载。

    XML缺点

    • 编写麻烦,效率低,大型项目过于复杂。

    2.AOP

    2.1 AOP基本概念

    • 理解角度一:AOP(Aspect Orient Programming),面向切面编程。它是一种思想,常见的实现该思想的框架有Spring以及AspectJ。这两个框架底层都是通过动态代理实现AOP,即AOP是从动态角度考虑程序的运行,所以AOP这种思想的作用可以等价于动态代理的作用。

    • AOP(面向切面编程)的含义:合理的安排切面的执行时间(在目标方法前, 还是目标方法后)、合理的安排切面执行的位置(例如在哪个类或者哪个方法需要进行功能增强)

    • AOP作用

      • 可以在你类源码不变的情况下,给类增加一些额外的功能,减少代码的冗余。同时能够实现业务功能和非业务功能【日志、事务等】的解耦合。
      • 具体来说就是在程序执行过程中,通过创建代理对象,然后让代理对象执行方法,从而实现给目标类的方法增加额外的功能。
    • aop的使用场景

      • 当你需要修改系统中某个类的功能,原有类的功能不完善,而你又没有源代码的情况。
      • 当你需要给项目中多个类增加相同的功能时。
      • 给业务方法增加事务、日志输出等功能时。

    2.2 AOP的实现

    对于AOP这种编程思想,很多框架都进行了实现。最常见的是Spring框架以及AspectJ框架。

    • spring:spring在内部实现了aop规范,能做aop的工作。spring主要在事务处理时使用aop。然而项目开发中很少使用spring的aop实现。 因为spring的aop比较笨重。
    • aspectJ: 一个开源的专门做aop的框架。spring框架中集成了aspectj框架,通过spring就能使用aspectj的功能。在Spring中使用AOP开发时,一般都使用AspectJ的实现方式。aspectJ框架实现aop有两种方式:使用xml的配置文件以及使用注解

    2.3 AOP相关术语以及aspectJ实现原理

    Aspect(切面)

    给你的目标类增加的功能就可以认为是一个切面,切面一般都是非业务方法且独立使用的。常见的切面有:日志记录、输出执行时间、参数检查、事务等等。

    JoinPoint(连接点)

    表示切面的执行位置。例如给业务方法A增加切面,则业务方法A就是JoinPoint。JoinPoint一般表示一个业务方法。

    Pointcut(切入点)

    和JoinPoint的唯一区别在于:JoinPoint表示一个方法,Pointcut表示多个方法(多个连接点的集合)。

    目标对象

    给类A的方法增加功能,则类A就是目标对象。

    Advice(通知)

    表示切面执行的时间,比如切面是在业务方法之前执行,还是在业务方法之后执行。AspectJ有5个常用的注解用于表示切面的执行时间,@Before(切入点表达式)@AfterReturning(切入点表达式)@Around(切入点表达式)@AfterThrowing(切入点表达式)@After(切入点表达式)

    切面三要素

    一个切面有三个关键的要素:

    • (切面)切面的功能代码:切面是干什么的
    • (切入点)切面的执行位置,使用Pointcut表示切面执行的位置
    • (切面执行时间)切面的执行时间,使用Advice表示时间,在目标方法之前,还是目标方法之后。

    切面三要素在aspectJ框架中的实现

    上文提到切面三要素:切面的功能代码(自己书写的)、切面的执行位置、切面的执行时间

    • 在aspectJ框架中,使用5个常见的注解(也称为5个常用的通知[Advice]注解)表示切面的执行时间,切面的执行时间又叫做Advice(通知):

      • @Before(切入点表达式)
      • @AfterReturning(切入点表达式)
      • @Around(切入点表达式)
      • @AfterThrowing(切入点表达式)
      • @After(切入点表达式)
    • 在aspectJ框架中,使用切入点表达式表示切面执行的位置

    execution(方法访问权限、方法返回值方法声明(参数类型)、异常类型)不加粗部分表示可以省略。

    execution(public * *(..)) 
    指定切入点为:任意公共方法。
    
    execution(* set*(..)) 
    指定切入点为:任何一个以“set”开始的方法。
    
    execution(* com.xyz.service.*.*(..)) 
    指定切入点为:定义在 service 包里的任意类的任意方法。
    
    execution(* com.xyz.service..*.*(..))
    指定切入点为:定义在 service 包或者子包里的任意类的任意方法。“..”出现在类名中时,后面必须跟“*”,表示包、子包下的所有类。
    
    execution(* *..service.*.*(..))
    指定所有包下的service包里的任意类的任意方法为切入点
    

    2.4 AspectJ框架在项目中的使用

    具体可以见动力节点视频(P52开始)

    使用aspectj实现aop的基本步骤:
    1.新建maven项目
    2.加入依赖
    	spring依赖
    	aspectj依锁
    3.创建目标类:接口和他的实现类。要做的是给类中的方法增加功能
    4.创建切面类:普通类
    	1)在类的上方加入@Aspect注解,表明该类为切面类
    	2)在类中定义方法,方法就是切面要执行的功能代码,然后在方法的上面加入aspectj中的通知注解(5个中的任意一个),例如@Before(切入点表达式)
    5.创建spring的配置文件:声明对象,把对象交给容器统一管理.声明对象你可以使用注解或者xml配置文件<bean>标签
    	1)声明目标对象
    	2)声明切面类对象
    	3)声明aspectj框架中的自动代理生成器标签<aop:aspectj-autoproxy/>。自动代理生成器:用来完成代理对象的自动创建功能(将spring容器中所有的目标对象一次性都生成代理对象)。
    6.创建测试类,从spring容器中获取目标对象(实际就是代理对象).通过代理执行方法,实现aop的功能增强。
    

    2.5 AspectJ5个通知注解和常用注解

    @Before

    • @Before注解修饰的方法称为前置通知方法,即在目标方法执行之前执行。
    • @Before(value="切入点表达式")
    • 前置通知方法的定义要求:
      • 公共方法public
      • 方法没有返回值
      • 方法可以没有参数,也可以有参数[JoinPoint]。JoinPoint参数的作用:可以在切面方法中获取业务方法执行时的信息,例如方法名,方法的形参等等。这个参数的值由框架赋予,且必须放在第一个位置。
    @Aspect
    public class MyAspect {
        @Before("execution(* *..SomeServiceImpl.do*(..))")
        public void myBefore(JoinPoint jp){
            //JoinPoint能够获取到方法的定义,方法的参数等信息
            System.out.println("连接点的方法定义: "+ jp.getsignature());
            System.out.println("连接点方法的参数个数:"+jp.getArgs().length);方法参数的信息
            Object args []= jp.getArgs();
            for(Object arg: args){
                System.out.println(arg);
            }
            //切面代码的功能,例如日志的输出,事务的处理
            System.out.println("前置通知:在目标方法之前先执行,例如输出日志");
        }
    }
    

    @AfterReturning

    • @AfterReturning注解修饰的方法称为后置通知方法,即在目标方法执行之后执行。能够获取到目标方法的返回值,可以修改这个返回值或者根据这个返回值进行不同的处理。
    • @AfterReturning(value="切入点表达式",returning=“自定义变量名,表示目标方法的返回值”)。注意自定义变量名必须和 @AfterReturning注解修饰的方法的形参名一样。
    • 后置通知方法的定义
      • 公共方法public
      • 方法没有返回值
      • 该方法除了可以包含 JoinPoint 参数外,还可以包含用于接收目标方法返回值的变量。该变量最好为 Object 类型,因为目标方法的返回值可能是任何类型。
    @Aspect
    public class MyAspect {
        @AfterReturning(value="execution(* *..SomeServiceImpl.doOther(..))",returnint="result")
        public void myAfterReturning(Object result){
            if(result != null){
                String s = (String)result;
                System.out.println("接收到目标方法的返回值");
            }
            System.out.println("后置通知:在目标方法之后执行的功能增强,例如执行事务处理〈切面〉");
        }
    }
    

    @Around

    • @AfterReturning注解修饰的方法称为环绕通知方法,即在目标方法执行之前之后执行。
    • @Around(value="切入点表达式")
    • 环绕通知方法的定义
      • 公共方法public
      • 方法有返回值, 推荐使用Object,该返回值就是目标方法的返回值,是可以被修改的
      • 该方法除了可以包含 JoinPoint 参数外,还有固定的参数ProceedingJoinPoint,该参数用于执行目标方法。
    @Aspect
    public class MyAspect {
        @Around("execution(* *..SomeServiceImpl.doFirst(..))")
        public Object myAround(ProceedingJoinPoint pjp) throws Throwable{
            Object obj = null;
            //增强功能
            System.out.print1n("环绕通知:在目标方法之前执行的,例如输出日志");
            //执行目标方法的调用,等同于JDK动态代理中的method.invoke(target,angs)
            obj = pjp.proceed();
            //增强功能
            System.out.print1n("环绕通知:在目标方法之后执行的,例如处理事务");//返回目标方法的执行结果
            return obj;
        }
    }
    

    @AfterThrowing

    • @AfterThrowing注解修饰的方法称为异常通知方法,即在目标方法抛出异常之后执行。类似于try/catch
    • @AfterThrowing(value="切入点表达式",throwing="自定义变量名")。注意自定义变量名必须和 @AfterThrowing注解修饰的方法的形参名一样。
    • 异常通知方法的定义
      • 公共方法public
      • 方法没有返回值
      • 该方法除了可以包含 JoinPoint 参数外,还可以包含Throwable对象,用于表示目标方法发生的异常对象。
    @AfterThrowing(value="execution(* *..SomeServiceImpl.doSecond(..))",throwing="ex")
    public void myAfterThrowing(Throwable ex){
        //把异常发生的时间,位置,原因记录到数据库,日志文件等等,
        //可以在异常发生时,把异常信息通过短信,邮件发送给开发人员。
    	System.out.print1n("异常通知:在目标方法抛出异常时执行的,异常原因:" + ex.gethessage());
    }
    

    @After

    • @After注解修饰的方法称为最终通知方法,无论目标方法是否抛出异常都会执行。类似于finally
    • @Around(value="切入点表达式")
    • 最终通知方法的定义要求:
      • 公共方法public
      • 方法没有返回值
      • 方法可以没有参数,也可以有参数[JoinPoint]
    @After("execution(* *..SomeServiceImpl.doThird(..))")
    public void myAfter(){
        System.out.println("最终通知:总是会被执行的方法");
    }
    

    @Pointcut

    • 用于定义和管理切入点,如果项目中有多个切入点表达式是重复的,可以复用的。那么可以使用@Pointcut对切入点表达式进行定义。
    • @Pointcut(value="切入点表达式"),使用位置在自定义的方法上面
    • 当在一个通知方法的上面使用@Pointcut,此时这个方法的名称就是切入点表达式的别名。在其它的通知方法中,value属性就可以直接使用这个方法名称代替切入点表达式。
    @Aspect
    public class MyAspect {
        @Pointcut(value="execution(* *..SomeServiceImpl.doThird(..))")
        private void mypt(){
            //无需代码
        }
    }
    可以使用value="mypt()"代替value="execution(* *..SomeServiceImpl.doThird(..))"。
    

    2.6 动态代理

    • 动态代理的功能:可以在你类源码不变的情况下,给类增加一些额外的功能,减少代码的冗余。
    • 具体来说就是在程序执行过程中,通过创建代理对象,然后让代理对象执行方法,从而实现给目标类的方法增加额外的功能。动态代理有两种实现方式:JDK动态代理CGLIB动态代理

    (1) JDK动态代理

    JDK动态代理要求需要进行功能扩展的类必须实现接口,Java语言中通过Java.lang.reflect包提供三个类支持代理模式:Proxy、Method和InovcationHandler。

    JDK动态代理实现步骤:
    1.创建目标类
    2.创建InovacationHandler接口的实现类,在这个类中给目标方法增加功能。
    3.使用JDK中的类Proxy,创建代理对象,使用代理对象实现功能增加。
    具体看动力节点的动态代理视频。
    

    (2) CGLIB动态代理

    • 如果目标类实现了接口,使用JDK动态代理来生成代理类及代理类实例对象(底层通过反射实现)。
    • 对于没有实现接口的对象,利用cglib为其提供代理,在运行时动态的生成某个类的子类,注意,CGLIB是通过继承的方式做的动态代理,因此如果某个类被标记为final,那么它是无法使用CGLIB做动态代理的。
    • CGLIB原理:动态生成一个要代理类的子类,子类继承并重写要代理的类的所有不是final的方法。在子类中采用方法拦截的技术拦截所有父类方法的调用,顺势织入横切逻辑。它比使用java反射的JDK动态代理要快。

    3.Spring与MyBatis的整合

    使用Spring的IOC核心技术,把MyBatis框架中使用的对象交给Spring统一创建和管理。

    1.新建maven项目
    2.加入maven的依赖
        1) spring依赖
        2) mybatis依赖
        3) mysql驱动
        4) spring的事务的依赖
        5) mybatis和spring集成的依赖:mybatis官方提供的,用来在spring项目中创建mybatis的sqlsesissonFactory, dao对象的
    3.创建实体类
    4.创建dao接口和mapper文件
    5.创建mybatis主配置文件
    6.创建service接口和实现类,属性是dao。
    7.创建spring的配置文件:利用spring中的bean标签创建与mybatis相关的对象
        1)创建数据源对象
        2)创建sqlsessionFactory对象
        3)创建Dao对象
        4)声明自定义的service
    8.创建测试类,获取Service对象,通过service调用dao完成数据库的访问。
    源码见GitHub仓库ch06-Spring-MyBatis
    

    4.Spring事务

    4.1 使用事务的时机

    • 当我的操作,涉及得到多个表或者多个sql语句(例如insert,update,delete)时,需要保证这些语句要么全部都执行成功,要么全部都执行失败(事务的原子性),这时候就需要使用事务对多个sql语句进行管理。
    • 在Java代码中,一般都在service类的业务方法上进行事务的操作,因为通常业务方法会调用多个dao方法,执行多个sql语句

    4.2 JDBC和Mybatis如何处理事务

    JDBC创建连接Connection conn; 利用conn.commit()conn.rollback()进行事务的处理。

    mybatis访问数据库,利用SqlSession.commit()SqlSession.rollback()进行事务的处理。

    缺点:多种数据库的访问技术,有不同的事务处理的机制,对象,方法。

    4.3 Spring事务

    Spring提供一种处理事务的统一模型, 能使用统一步骤和方式完成多种不同数据库的事务处理功能。例如使用spring的事务处理机制,既可以完成mybatis访问数据库的事务处理,同时也可以完成hibernate访问数据库的事务处理。

    Spring处理事务的模型以及使用的步骤都是固定的。开发人员只需要把事务相关信息(例如使用哪种数据库访问技术对事务进行管理,JDBC、Mybatis还是hibernate)提供给spring就可以了。

    (1) 事务管理器对象

    事务管理器是一个接口和他的众多实现类。它的作用在于完成commit()、rollback()等操作。同时spring把每一种数据库访问技术对应的事务处理类都创建好了。

    • 如果使用mybatis访问数据库,则spring在内部会创建mybatis对应的事务管理器对象:DataSourceTransactionManager

    • 如果使用hibernate访问数据库,则spring在内部会创建hibernate对应的事务管理器对象:HibernateTransactionManager

      -如何把事务相关信息提供给Spring?
      -在Spring的配置文件中使用标签声明数据库访问技术对应的事务管理器对象,例如,你要使用mybatis访问数据库,你应该在xml配置文件中进行如下的配置:<bean id=“xxx" class="...DataSourceTransactionManager">

    (2) 事务定义接口

    事务定义接口TransactionDefinition中定义了事务描述相关的三类常量:事务隔离级别事务传播行为事务默认超时时限,及对它们的操作。

    (3) 事务提交、回滚的时机

    • 当你的业务方法,执行成功并且没有抛出异常,在该方法执行完毕之后,spring会自动调用事务管理对象中的commit()方法进行事务的提交。

    • 当你的业务方法抛出运行时异常或ERROR时候,spring会自动调用事务管理器对象中的rollback()方法进行事务的回滚。

    • 当你的业务方法抛出非运行时异常, 主要是受查异常时,spring会自动调用事务管理对象中的commit()方法进行事务的提交。

    4.4 事务描述的三要素

    (1) 事务隔离级别

    事务的隔离级别
    DEFAULT:采用 DB 默认的事务隔离级别。MySql的默认为REPEATABLE_READ; Oracle默认为READ_COMMITTED。
    ➢ READ_UNCOMMITTED:读未提交。未解决任何并发问题。
    ➢ READ_COMMITTED:读已提交。解决脏读,存在不可重复读与幻读。
    ➢ REPEATABLE_READ:可重复读。解决脏读、不可重复读,存在幻读
    ➢ SERIALIZABLE:串行化。不存在并发问题。
    

    (2) 事务传播行为

    事务传播行为是指,处于不同事务中的方法在相互调用时,执行期间事务的维护情况。如A事务中的方法doSome()调用B事务中的方法oOther(),在调用执行期间,事务的维护情况就称为事务传播行为。事务传播行为是加在方法上的。事务传播行为常量都是以 PROPAGATION_ 开头,形如 PROPAGATION_XXX。
    
    7个传播行为,表示你的业务方法调用时,事务在方法之间是如何使用的。
    PROPAGATION_REQUIRED(掌握)
    PROPAGATION_REQUIRES_NEW(掌握)
    PROPAGATION_SUPPORTS(掌握)
    PROPAGATION_MANDATORY
    PROPAGATION_NESTED
    PROPAGATION_NEVER
    PROPAGATION_NOT_SUPPORTED
    

    (3) 事务默认超时时限

    表示一个方法最长的执行时间,如果方法执行时间超过了默认超时时限,事务就会回滚。单位是秒, 整数值, 默认是-1(不配置). 该值一般就使用默认值即可。

    4.5 使用Spring的事务注解管理事务

    • 事务在Spring框架中的实现是依赖AOP思想的,上文说到AOP的实现方式有两种:spring框架自己实现的aop以及借助Aspectj框架实现的AOP,因此事务的管理方式也存在两种:使用Spring的事务注解管理事务、使用AspectJ的AOP配置管理事务。
    • Spring的事务注解适合在中小型项目中使用,spring框架底层采用自己实现的AOP给业务方法增加事务的功能。具体而言采用@Around环绕通知实现。
    • 使用@Transactional注解增加事务。必须将该注解放在public方法的上面,表示当前方法具有事务。并且可以给注解的属性赋值,表示具体的隔离级别,传播行为,异常信息等等,不同通常都采用默认值即可。
    具体的使用步骤
    1.在Spring配置文件中声明事务管理器对象:<bean id="xx" class="DataSourceTransactionManager">
    2.开启事务注解驱动,告诉spring框架,我要使用注解的方式进行事务的管理。
    <!--声明事务注解驱动-->
    <tx: annotation-driven transaction-manager="transactionManager"/>
    3.在业务层public方法的上面加入@Trancational注解。
    
    • Spring的事务注解管理事务的底层原理
    spring使用aop机制,创建@Transactional所在的类代理对象,给方法加入事务的功能。具体而言就是:在你的业务方法执行之前,先开启事务,在业务方法之后提交或回滚事务,使用aop的环绕通知。  
    @Around("你要增加的事务功能的业务方法名称")
    Object myAround(){
    //开启事务,spring给你开启
    try{
        	buy(1001,10);
    		//spring的事务管理器.commit();
    	}catch(Exception e){
            //spring的事务管理器.rollback();
    	}
    }								
    

    代码见github

    4.6 使用AspectJ的AOP配置管理事务

    AspectJ的AOP配置管理事务适合大型项目,当有大量的类、方法需要进行事务管理,则使用aspectj框架功能进行事务的配置与管理。

    使用该方法进行事务管理的优势在于:只需要在spring配置文件中声明类,方法需要的事务,可以让业务方法和事务配置解耦合。

    实现步骤: 都是在xml配置文件中实现。 
    1.加入aspectJ依赖
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-aspects</artifactId>
        <version>5.2.5.RELEASE</version>
    </dependency>
    2.声明事务管理器对象
    <bean id="xx" class="DataSourceTransactionManager">
    3.声明方法需要的事务类型(配置方法的事务属性【隔离级别,传播行为,超时】)
    4.配置aop:指定哪些哪类要创建代理。
    

    具体代码过程见github

    Spring常见面试题

    1.BeanFactory 和 ApplicationContext的区别

    共同点:BeanFactoryApplicationContext都是Spring IOC的核心接口,都是用于从容器中获取Spring beans的,但是,他们二者有很大不同。

    (1) Spring beans就是被Spring容器所管理的Java对象。
    (2) Spring容器本质是一个concurrentHashMap,负责实例化,配置和装配Spring beans,然后对象的获取则由接口BeanFactory或ApplicationContext进行获取。
    
    • (懒加载)BeanFactroy采用的是延迟加载形式来注入Bean的,即只有在使用到某个Bean时(调用getBean()),才对该Bean进行加载实例化,

    • (即时加载)ApplicationContext则相反,它是在容器启动时,一次性创建了所有的Bean。

    BeanFacotry优缺点:应用启动的时候占用资源很少,对资源要求较高的应用,比较有优势;但是这样就不能发现一些存在的Spring的配置问题,例如Bean的某一个属性没有注入,BeanFacotry加载后,直至第一次使用调用getBean方法才会抛出异常;

    ApplicationContext优缺点:在容器启动时,就可以发现Spring中是否存在配置错误,但是内存空间占用更大,且当应用程序配置Bean较多时,程序启动较慢。

    2.Bean的作用域

    所谓Bean的作用域是指spring容器创建Bean后的生存周期即由创建到销毁的整个过程。之前我们所创建的所有Bean其作用域都是Singleton,这是Spring默认的,在这样的作用域下,每一个Bean的实例只会被创建一次,而且Spring容器在整个应用程序生存期中都可以使用该实例。因此之前的代码中spring容器创建Bean后,通过代码获取的bean,无论多少次,都是同一个Bean的实例

    <!-- 默认情况下无需声明Singleton -->
    <bean name="accountDao" scope="singleton"    
    class="com.zejian.spring.springIoc.dao.impl.AccountDaoImpl"/>
    
    • singleton : 唯一bean实例,Spring 中的 bean 默认都是单例的(单例模式)。
    • prototype : 每次请求都会创建一个新的 bean 实例。
    • request : 每一次HTTP请求都会产生一个新的bean,该bean仅在同一个HTTP request内相同。不同http请求则bean也不相同
    • session :在一个HTTP Session中,一个Bean对应一个实例。同一个浏览器中访问属于同一次会话,因此SessionBean实例都是同一个实例对象。当换一个浏览器则不同。

    3.解释一下AOP

    参考:https://blog.csdn.net/javazejian/article/details/56267036

    AOP(Aspect-Oriented Programming:面向切面编程)能够将那些与业务⽆关,却为业务模块所共同调⽤的逻辑(例如事务处理、⽇志管理、权限控制等)封装起来,便于减少系统的重复代码,降低模块间的耦合度,并有利于未来的可拓展性和可维护性。

    aop的作用

    • 在目标类不增加代码的情况下,给目标类增加功能。
    • 减少重复的代码
    • 让开发人员更加专注于业务逻辑的实现
    • 解耦合:将业务功能和日志、事务等非业务功能解耦

    aop的使用场景

    • 当你需要修改系统中某个类的功能,原有类的功能不完善,而你又没有源代码的情况。
    • 当你需要给项目中多个类增加相同的功能时。
    • 给业务方法增加事务、日志输出等功能时。

    AOP可以简单理解为aspect(切面)应用到目标函数(类)的过程。对于这个过程,一般分为动态织入(两种方式)和静态织入,因此AOP有两种实现方式:

    • Spring aop(动态织入,又分为两种方式)
    • aspectj aop(静态织入)

    (1)Spring aop

    Spring aop底层通过代理实现

    • 如果目标类实现了接口,使用JDK动态代理来生成代理类及代理类实例对象(底层通过反射实现)。
    • 对于没有实现接口的对象,利用cglib为其提供代理,在运行时动态的生成某个类的子类,注意,CGLIB是通过继承的方式做的动态代理,因此如果某个类被标记为final,那么它是无法使用CGLIB做动态代理的。
    • CGLIB原理:动态生成一个要代理类的子类,子类继承并重写要代理的类的所有不是final的方法。在子类中采用方法拦截的技术拦截所有父类方法的调用,顺势织入横切逻辑。它比使用java反射的JDK动态代理要快。

    代理对象可以看作是目标对象的加强版,它是对目标对象中方法功能的一个扩充。

    Joinpoint(连接点) 指那些被拦截到的点,可以理解为是目标类的方法
    Pointcut(切入点) 指要对哪些Joinpoint(目标类的方法)进行拦截,即被拦截的连接点
    Advice(通知) 指拦截到Joinpoint(目标类的方法)之后要做的事情,对方法增强的内容
    Aspect(切面) 切入点和通知的结合

    (2)aspectj aop

    aspectj aop底层是基于字节码操作实现的

    • ApectJ采用的就是静态织入的方式。 ApectJ主要采用的是编译期织入,在这个期间使用AspectJ的acj编译器(类似javac)把aspect类编译成class字节码后,在java目标类编译时织入,即先编译aspect类再编译目标类。
    • Spring 2.0版本之后,spring aop采用了aspectj语法来定义切面,但仍然没有使用 AspectJ 的编译器,底层依是动态代理技术的实现,因此并不依赖于 AspectJ 的编译器。
  • 相关阅读:
    生成错误:对路径".dll"的访问被拒绝
    实现一个类似于收趣APP的功能
    使用Jquery.form.js ajax表单提交插件弹出下载提示框
    关于Mysql错误:./bin/mysqld_safe --user=mysql& [1] 32710 121003 16:40:22 mysqld_safe Logging to '/var/log/mysqld.log'. 121003 16:40:22 mysqld_s
    Linux下更新时间
    关于轻松安装LNMP和LAMP的编译环境
    LAMP环境CentOS6.4 PHP5.4随笔未整理
    论Linux运维的一些基础安全知识和简单办法
    shell脚本笔记(原创不断记录)
    MySQL创建一个用户,指定一个数据库 授权
  • 原文地址:https://www.cnblogs.com/XDU-Lakers/p/14915974.html
Copyright © 2011-2022 走看看