Spring

不管现实多么惨不忍睹,都要持之以恒地相信,这只是黎明前短暂的黑暗而已。不要惶恐眼前的难关迈不过去,不要担心此刻的付出没有回报,别再花时间等待天降好运。真诚做人,努力做事!你想要的,岁月都会给你。Spring,希望对大家有帮助,欢迎收藏,转发!站点地址:www.bmabk.com,来源:原文

3.Spring5
3.1.介绍
  • Spring 是分层的 Java SE/EE 应用 full-stack 轻量级开源框架,以 IoC(Inverse Of Control:反转控制)和 AOP(Aspect Oriented Programming:面向切面编程)为内核,提供了展现层 SpringMVC 和持久层 Spring JDBC 以及业务层事务管理等众多的企业级应用技术,还能整合开源世界众多
    著名的第三方框架和类库,逐渐成为使用最多的 Java EE 企业应用开源框架。
  • 优势:
    • 方便解耦,简化开发:通过 Spring 提供的 IoC 容器,可以将对象间的依赖关系交由 Spring 进行控制,避免硬编码所造成的过度程序耦合。用户也不必再为单例模式类、属性文件解析等这些很底层的需求编写代码,可以更专注于上层的应用。
    • AOP 编程的支持:通过 Spring 的 AOP 功能,方便进行面向切面的编程,许多不容易用传统 OOP 实现的功能可以通过 AOP 轻松应付。
    • 声明式事务的支持:可以将我们从单调烦闷的事务管理代码中解脱出来,通过声明式方式灵活的进行事务的管理,提高开发效率和质量。
    • 方便程序的测试:可以用非容器依赖的编程方式进行几乎所有的测试工作,测试不再是昂贵的操作,而是随手可做的事情。
    • 方便集成各种优秀框架:Spring 可以降低各种框架的使用难度,提供了对各种优秀框架(Struts、Hibernate、Hessian、Quartz等)的直接支持。
    • 降低 JavaEE API 的使用难度:Spring 对 JavaEE API(如 JDBC、JavaMail、远程调用等)进行了薄薄的封装层,使这些 API 的使用难度大为降低。
    • Java 源码是经典学习范例:Spring 的源代码设计精妙、结构清晰、匠心独用,处处体现着大师对 Java 设计模式灵活运用以及对 Java 技术的高深造诣。它的源代码无意是 Java 技术的最佳实践的范例。
3.2.入门案例
3.2.1.案例简介
  • 导入配置
    <!-- maven工程中导入spring依赖 -->
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-context</artifactId>
        <version>5.2.10.RELEASE</version>
    </dependency>
    
  • 在maven插件中查看
    • org.springframework:spring-context:5.2.10.RELEASE
      • org.springframework:spring-aop:5.2.10.RELEASE
      • org.springframework:spring-beans:5.2.10.RELEASE
      • org.springframework:spring-core:5.2.10.RELEASE
        • org.springframework:spring-jcl:5.2.10.RELEASE
      • org.springframework:spring-expression:5.2.10.RELEASE
  • 创建spring的xml文件,可以看做虚拟容器
    • 文件名称:application.xml(或application-xxx.xml),不可修改为其他名称,放在resource文件夹下
      <?xml version="1.0" encoding="UTF-8"?>
      <beans xmlns="http://www.springframework.org/schema/beans"
             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xmlns:context="http://www.springframework.org/schema/context"
             xsi:schemaLocation="
              http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd
              http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd">
      
          <!-- bean definitions here -->
          <!--通过bean标签完成组件的注册-->
          <!--
              id:组件在容器中的唯一标识
              name:组件名称,通常省略不写,以id作为name
              class:接口的实现类的全类名
          -->
          <!-- 通过反射获得实例,因此应该使用实现类的全类名 -->
          <bean id="userService" class="service.UserServiceImpl"/>
      </beans>
      
  • 在main下java文件夹里创建一个service文件夹,在文件夹中创建UserService接口及其实现类UserServiceImpl;
    public interface UserService {
        public void sayHello(String username);
    }
    
    
    public class UserServiceImpl implements UserService{
        @Override
        public void sayHello(String username) {
            System.out.println("hello " + username);
        }
    }
    
  • 在test文件夹的java文件夹内创建TempTest类
    @Test
    public void test(){
        //ApplicationContext是抽象容器的具体存在
        //0.从容器中取出组件,并测试方法
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext("application.xml");
    
        //1.通过xml文件中的id值取得组件
        UserService userService1 = (UserService) applicationContext.getBean("userService");
        userService1.sayHello("world1");
    
        //2.通过class文件取得组件,容器中(xml文件中)该接口的实现类类型只有一个
        //注意:“该类型只有一个”的语句描述,意思是,该接口的实现类,只有一个,以后也是一样的描述意义
        UserService userService2 = (UserService) applicationContext.getBean(UserService.class);
        userService2.sayHello("world2");
    
        //3.既按id,又按类型取得组件
        UserService userService3 = (UserService) applicationContext.getBean("userService",UserService.class);
        userService3.sayHello("world3");
    }
    

    注意:

    • 在debug中发现,UserService对象都是同一个哈希地址,这是由于spring单例设计模式导致的对同一个组件只会取到同一个对象(不修改设置的前提)
3.2.2.补充案例
  • 在上面案例的基础上,再在main—java文件夹下新建一个mapper文件夹,并新建UserMapper接口及其实现类UserMapperImpl

    public interface UserMapper {
        public void sayHello(String username);
    }
    
    
    public class UserMapperImpl implements UserMapper{
        @Override
        public void sayHello(String username) {
            System.out.println("hello mapper " + username);
        }
    }
    
  • 修改UserService接口的实现类UserServiceImp

    public class UserServiceImpl implements UserService{
    
        UserMapper userMapper;
    
        //这个set方法名要和容器中相对应
        public void setUserMapperzzz(UserMapper userMapper) {
            this.userMapper = userMapper;
        }
    
        @Override
        public void sayHello(String username) {
            userMapper.sayHello(username);
        }
    
        public UserServiceImpl() {
            System.out.println("userService的无参构造方法");
        }
    }
    
  • 修改xml配置文件

    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="http://www.springframework.org/schema/beans"
           xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
           xmlns:context="http://www.springframework.org/schema/context"
           xsi:schemaLocation="
            http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd
            http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd">
    
        <!-- bean definitions here -->
        <bean id="userService" class="com.cskaoyan.service.UserServiceImpl">
            <!--让userService依赖于容器中已经注册的userMapper,建立此依赖关系需要对应变量的set方法-->
            <!--
                name: set方法名,和实现类中的方法名是对应的
                ref: 本容器中的组件id
            -->
            <property name="userMapperzzz" ref="userMapper"/>
        </bean>
        <bean id="userMapper" class="mapper.UserMapperImpl"/>
    </beans>
    
  • 在test—java文件夹下建立测试类

    @Test
    public void test1(){
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext("application.xml");
        UserService userService = applicationContext.getBean(UserService.class);
        userService.sayHello("world4");
    
        UserMapper userMapper = applicationContext.getBean(UserMapper.class);
    }
    
    /*
    输出结果:
        userService的无参构造方法
        hello mapper world4
    */
    

    注:

    • 由此可知,spring实现的方式是通过无参构造方法获得实现类
    • 通过debug可以发现,两种方式获取的UserMapper均相同,是同一个实例,同一个组件获取的是同一个实例
3.3.SpringIOC
3.3.1.ApplicationContext的继承关系
  • 在IDEA中,选中某个类,按住ctrl+h键,即可查看任意类之间的继承关系
                                                        BeanFactory
                                                                ^
                                                            	|
                                                                |
                                                                |
              										    HierachicalBeanFactory<-------------------------|
                                                                ^										|
                                                            	|										|
    ApplicationEventPublisher<----|                             |                             			|
                                  |                             |										|
    MessageSource <---------------|---------------------ApplicationContext--------------------->ListableBeanFactory
                                  |                          	^
    ResourceLoader				  |								|
          ^                       |                  			|                             
          |                       |                             |
    ResourePatternResolver<-------|                     CongfigurableApplicationContext-------->Lifecycle
                                                            	^
                                                  				|
                                                                |
                                                                |
                                  						AbstractApplicationContext
                                                            	^
                                                                |
                                                                |
                                                                |
                                                       AbstractRefreshableApplicationContext
                                                            	^
                                                                |
                                                                |
                                                                |
                                                       AbstractRefreshableConfigApplicationContext
                                                            	^
                                                                |
                                                                |
                                                                |
                                                       AbstractXmlApplicationContext
                                                            	^
                                                                |
                                        -------------------------------------------------------------
                                        |															| 
                                FileSysetemXmlApplicationContext						ClassPathXmlApplicationContext
    
    • BeanFactory 才是 Spring 容器中的顶层接口。ApplicationContext 是它的子接口。
    • BeanFactory 和 ApplicationContext 的区别:
      • 创建对象的时间点不一样。
        • ApplicationContext:只要一读取配置文件,默认情况下就会创建对象。
        • BeanFactory:什么使用什么时候创建对象。
    • ClassPathXmlApplicationContext:从类的根路径下加载配置文件,推荐使用这种
    • FileSystemXmlApplicationContext:从磁盘路径上加载配置文件,配置文件可以在磁盘的任意位置。
    • AnnotationConfigApplicationContext:当我们使用注解配置容器对象时,需要使用此类来创建 spring 容器。它用来读取注解。
3.3.2.IOC 中 bean 标签和管理对象细节
  • bean 标签
    • 作用:用于配置对象让 spring 来创建的。默认情况下它调用的是类中的无参构造函数。如果没有无参构造函数则不能创建成功。
    • 属性:
      • id:给对象在容器中提供一个唯一标识。用于获取对象。
      • class:指定类的全限定类名。用于反射创建对象。默认情况下调用无参构造函数。
      • scope:指定对象的作用范围。
      • singleton:默认值,单例的.
      • prototype:多例的.
      • request:WEB 项目中,Spring 创建一个 Bean 的对象,将对象存入到 request 域中.
      • session:WEB 项目中,Spring 创建一个 Bean 的对象,将对象存入到 session 域中.
      • global session:WEB 项目中,应用在 Portlet 环境.如果没有 Portlet 环境那么globalSession 相当于 session.
      • init-method:指定类中的初始化方法名称。
      • destroy-method:指定类中销毁方法名称。
      • Factory-method:工厂创建对象的方法
      • Factory-bean:指定工厂的id组件
3.3.3.JavaBean的实例化
  • 构造方法:

    • 无参构造(最常用)
      • 在上述补充案例中,UserMapper添加一个无参构造方法,在函数体内打印一句话,经过测试类测试,可以发现,执行到了无参构造方法
      • 同理,如果JavaBean中未提供无参构造方法,而容器中又没有进行相应的配置,那么编译的时候就会报错
    • 有参构造
      • 在main—java中新建mapper文件夹,UserMapper接口及其实现类UserMapperImpl,OrderMapper接口及其实现类OrderMapperImpl,为空即可
      • 在main—java中新建service文件夹,新建UserService接口及其实现类,接口为空即可,实现类内容如下
      public class UserServiceImpl implements UserService{
          UserMapper userMapper;
          OrderMapper orderMapper;
          String parameter;
      
          //有参构造方法的形参名可以为任意,但是必须要和容器中的name属性对应
          public UserServiceImpl(UserMapper userMapperz, OrderMapper orderMapper,String parameter) {
              this.userMapper = userMapperz;
              this.orderMapper = orderMapper;
              this.parameter = parameter;
              System.out.println("userServiceImp的有参构造");
          }
      }
      
      • 在xml文件中注册组件
      <?xml version="1.0" encoding="UTF-8"?>
      <beans xmlns="http://www.springframework.org/schema/beans"
             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xmlns:context="http://www.springframework.org/schema/context"
             xsi:schemaLocation="
              http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd
              http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd">
      
          <!-- bean definitions here -->
          <bean id="userService" class="service.UserServiceImpl">
              <!--
                  name:有参构造方法的形参名
      			ref:引用的容器中其他的组件名,与组件的id对应
      			value:给对应的参数赋值
              -->
              <constructor-arg name="userMapperz" ref="userMapper"/>
              <constructor-arg name="orderMapper" ref="orderMapper"/>
              <constructor-arg name="parameter" value="hello"/>
          </bean>
      
          <bean id="userMapper" class="mapper.UserMapperImpl"/>
          <bean id="orderMapper" class="mapper.OrderMapperImpl"/>
      </beans>
      
      • 测试方法:
      @Test
      public void test(){
          ApplicationContext applicationContext = new ClassPathXmlApplicationContext("application.xml");
          UserService userService = applicationContext.getBean(UserService.class);
      }
      
      /*
      输出结果:
      	userServiceImp的有参构造
      */
      
  • 工厂

    • 静态工厂:工厂中的生产方法是静态方法
    • 实例工厂:工厂中的生产方法不为静态方法
    • FactoryBean:生产指定的组件,而BeanFactory可以生产全部组件。FactoryBean接口中提供了getObject方法
      • 在main—java文件夹下创建一个bean文件夹,并新建一个User类,为空即可
      • 在main—java文件夹下创建一个factory文件夹,新建一个StaticFactory类和一个InstanceFactory类
      • 在factory文件夹里,新建一个UserFactoryBean类,并实现FactoryBean接口
      //静态工厂
      public class StaticFactory {
      
          public static User getInstance() {
              return new User();
          }
      }
      
      
      //实例工厂
      public class InstanceFactory {
      
          public User getInstance() {
              return new User();
          }
      }
      
      
      //FactoryBean
      public class UserFactoryBean implements FactoryBean<User> {
          //这个方法非常重要
          @Override
          public User getObject() throws Exception {
              System.out.println("调用到factoryBean的getObject方法");
              return new User();
          }
      
          //这个方法可以不需要在意
          @Override
          public Class<?> getObjectType() {
              return User.class;
          }
      }
      
      • 在xml中注册组件
      <?xml version="1.0" encoding="UTF-8"?>
      <beans xmlns="http://www.springframework.org/schema/beans"
             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xmlns:context="http://www.springframework.org/schema/context"
             xsi:schemaLocation="
              http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd
              http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd">
      
          <!-- bean definitions here -->
          <!-- 静态工厂
               factory-method: 当前class中的方法名(静态的方法)
          -->
          <bean id="userFromStaticFactory" class="factory.StaticFactory" factory-method="getInstance"/>
      
          <!-- 实例工厂
               factory-bean:工厂组件的id
               factory-method:当前factory-bean中的方法名
      		 组件类型和factory-method对应的方法的返回值类型是相关的
          -->
          <!-- 其实这句可以理解为获取工厂组件,即获取对象 -->
          <bean id="instanceFactory" class="factory.InstanceFactory"/>
          <!-- 这句配置可以理解为执行工厂组件获取的对象的factory-method方法 -->
          <bean id="userFromInstanceFactory" factory-bean="instanceFactory" factory-method="getInstance"/>
      
          <!--factoryBean-->
          <bean id="userFromFactoryBean" class="factory.UserFactoryBean"/>
      </beans>
      
      • 测试:
      @Test
      public void test1() {
          //静态工厂
          User instance = StaticFactory.getInstance();
      
          //实例工厂
          InstanceFactory instanceFactory = new InstanceFactory();//要先去注册工厂组件
          User instance1 = instanceFactory.getInstance();
      }
      
      @Test
      public void test2(){
          ApplicationContext applicationContext = new ClassPathXmlApplicationContext("application.xml");
          Object userFromStaticFactory = applicationContext.getBean("userFromStaticFactory");
          Object userFromInstanceFactory = applicationContext.getBean("userFromInstanceFactory");
      
          Object userFromFactoryBean = applicationContext.getBean("userFromFactoryBean");
      }
      

      注意:

      • 这里获取到的User对象并不相同,因为不是同一个组件获取的
3.4.SpringDI
  • 依赖注入:Dependency Injection。它是 spring 框架核心 IOC 的具体实现。程序在编写时,通过控制反转,把对象的创建交给了 spring,但是代码中不可能出现没有依赖的情况。IOC 解耦只是降低他们的依赖关系,但不会消除。例如:我们的业务层仍会调用持久层的方法。那这种业务层和持久层的依赖关系,在使用 spring 之后,就让 spring 来维护了。简单的说,就是坐等框架把持久层对象传入业务层,而不用我们自己去获取
  • 创建业务层接口及其实现类
    public interface IAccountService {
        void saveAccount();
    }
    
  • 创建持久层接口和实现类
    public interface IAccountDao {
        void saveAccount();
    }
    
3.4.1.构造函数注入
  • 使用类中的构造函数,给成员变量赋值。注意,赋值的操作不是我们自己做的,而是通过配置的方式,让 spring 框架来为我们注入
    public class AccountServiceImpl implements IAccountService {
        private String name;
        private Integer age;
        private Date birthday;
        public AccountServiceImpl(String name, Integer age, Date birthday) {
            this.name = name;
            this.age = age;
            this.birthday = birthday;
    	}
        @Override
        public void saveAccount() {
        	System.out.println(name+","+age+","+birthday);
        }
    }
    
  • xml配置文件
    <!-- 使用构造函数的方式,给 service 中的属性传值
        要求:
        	类中需要提供一个对应参数列表的构造函数。
        涉及的标签:
        	constructor-arg
        属性:
            index:指定参数在构造函数参数列表的索引位置
            type:指定参数在构造函数中的数据类型
            name:指定参数在构造函数中的名称,用这个找给谁赋值
    
    =======上面三个都是找给谁赋值,下面两个指的是赋什么值的==============
    
            value:它能赋的值是基本数据类型和 String 类型
            ref:它能赋的值是其他 bean 类型,也就是说,必须得是在配置文件中配置过的 bean
    -->
    
    <bean id="accountService" class="com.itheima.service.impl.AccountServiceImpl">
        <constructor-arg name="name" value="张三"></constructor-arg>
        <constructor-arg name="age" value="18"></constructor-arg>
        <constructor-arg name="birthday" ref="now"></constructor-arg>
    </bean>
    <bean id="now" class="java.util.Date"></bean>
    
3.4.2.set 方法注入
  • 在类中提供需要注入成员的 set 方法
    public class AccountServiceImpl implements IAccountService {
        private String name;
        private Integer age;
        private Date birthday;
        public void setName(String name) {
        	this.name = name;
        }
        public void setAge(Integer age) {
        	this.age = age;
        }
        public void setBirthday(Date birthday) {
        	this.birthday = birthday;
        }
        @Override
        public void saveAccount() {
            System.out.println(name+","+age+","+birthday);
        }
    }
    
    
  • xml配置文件
    <!-- 通过配置文件给 bean 中的属性传值:使用 set 方法的方式
    涉及的标签:
        property
        属性:
            name:找的是类中 set 方法后面的部分
            ref:给属性赋值是其他 bean 类型的
            value:给属性赋值是基本数据类型和 string 类型的
            实际开发中,此种方式用的较多。
    -->
    <bean id="accountService" class="com.itheima.service.impl.AccountServiceImpl">
        <property name="name" value="test"></property>
        <property name="age" value="21"></property>
        <property name="birthday" ref="now"></property>
    </bean>
    <bean id="now" class="java.util.Date"></bean>
    
3.4.3.使用 p 名称空间注入数据(本质还是调用 set 方法)
  • 通过在 xml 中导入 p 名称空间,使用 p:propertyName 来注入数据,它的本质仍然是调用类中的set 方法实现注入功能
    public class AccountServiceImpl4 implements IAccountService {
        private String name;
        private Integer age;
        private Date birthday;
        public void setName(String name) {
        	this.name = name;
        }
        public void setAge(Integer age) {
        	this.age = age;
        }
        public void setBirthday(Date birthday) {
        	this.birthday = birthday;
        }
        @Override
        public void saveAccount() {
        	System.out.println(name+","+age+","+birthday);
        }
    }
    
  • xml配置文件
    <beans xmlns="http://www.springframework.org/schema/beans"
    	xmlns:p="http://www.springframework.org/schema/p"
        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 id="accountService"class="com.itheima.service.impl.AccountServiceImpl4"
              p:name="test" p:age="21" p:birthday-ref="now"/>
    </beans>
    
3.4.4.注入集合属性
  • 给类中的集合成员传值,它用的也是 set 方法注入的方式,只不过变量的数据类型都是集合。
    public class AccountServiceImpl implements IAccountService {
        private String[] myStrs;
        private List<String> myList;
        private Set<String> mySet;
        private Map<String,String> myMap;
        private Properties myProps;
        public void setMyStrs(String[] myStrs) {
        	this.myStrs = myStrs;
        }
        public void setMyList(List<String> myList) {
       		this.myList = myList;
        }
        public void setMySet(Set<String> mySet) {
        	this.mySet = mySet;
        }
        public void setMyMap(Map<String, String> myMap) {
        	this.myMap = myMap;
        }
        public void setMyProps(Properties myProps) {
        	this.myProps = myProps;
        }
        @Override
        public void saveAccount() {
            System.out.println(Arrays.toString(myStrs));
            System.out.println(myList);
            System.out.println(mySet);
            System.out.println(myMap);
            System.out.println(myProps);
        }
    }
    
  • xml配置文件
    <!-- 注入集合数据
        List 结构的:array,list,set
        Map 结构的:map,entry,props,prop
    -->
    <bean id="accountService" class="com.itheima.service.impl.AccountServiceImpl">
        
        <!-- 在注入集合数据时,只要结构相同,标签可以互换 -->
        <!-- 给数组注入数据 -->
        <property name="myStrs">
            <set>
                <value>AAA</value>
                <value>BBB</value>
                <value>CCC</value>
            </set>
        </property>
    
        <!-- 注入 list 集合数据 -->
        <property name="myList">
            <array>
                <value>AAA</value>
                <value>BBB</value>
                <value>CCC</value>
            </array>
        </property>
    
        <!-- 注入 set 集合数据 -->
        <property name="mySet">
            <list>
                <value>AAA</value>
                <value>BBB</value>
                <value>CCC</value>
            </list>
        </property>
    
        <!-- 注入 Map 数据 -->
        <property name="myMap">
            <props>
                <prop key="testA">aaa</prop>
                <prop key="testB">bbb</prop>
            </props>
        </property>
    
        <!-- 注入 properties 数据 -->
        <property name="myProps">
            <map>
                <entry key="testA" value="aaa"></entry>
                <entry key="testB">
                    <value>bbb</value>
                </entry>
            </map>
        </property>
        
    </bean>
    
3.5.作用域
  • singleton:组件在容器中以单例的形式存在,默认是单例形式
  • prototype:每一次获取组件时都会获取一个新的组件
  • 验证:
    • 在main—java中新建三个类,UserService1,UserService2,UserService3,为空即可
    • 在xml中注册组件
      <?xml version="1.0" encoding="UTF-8"?>
      <beans xmlns="http://www.springframework.org/schema/beans"
             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xmlns:context="http://www.springframework.org/schema/context"
             xsi:schemaLocation="
              http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd
              http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd">
      
          <!-- bean definitions here -->
          <bean id="userService1" class="com.cskaoyan.service.UserService1" scope="singleton"/>
          <bean id="userService2" class="com.cskaoyan.service.UserService2" scope="prototype"/>
          <bean id="userService3" class="com.cskaoyan.service.UserService3"/>
      </beans>
      
    • 在测试类中测试
      @Test
      public void test1() {
          //singleton
          ApplicationContext applicationContext = new ClassPathXmlApplicationContext("application.xml");
          UserService1 bean1 = applicationContext.getBean(UserService1.class);
          UserService1 bean2 = applicationContext.getBean(UserService1.class);
          UserService1 bean3 = applicationContext.getBean(UserService1.class);
      }
      @Test
      public void test2() {
          //prototype
          ApplicationContext applicationContext = new ClassPathXmlApplicationContext("application.xml");
          UserService2 bean1 = applicationContext.getBean(UserService2.class);
          UserService2 bean2 = applicationContext.getBean(UserService2.class);
          UserService2 bean3 = applicationContext.getBean(UserService2.class);
      }
      @Test
      public void mytest3() {
          //没有写scope
          ApplicationContext applicationContext = new ClassPathXmlApplicationContext("application.xml");
          UserService3 bean1 = applicationContext.getBean(UserService3.class);
          UserService3 bean2 = applicationContext.getBean(UserService3.class);
          UserService3 bean3 = applicationContext.getBean(UserService3.class);
      }
      

      在debug中可发现,singleton配置中,是同一个bean,而prototype不是,同理,没有写范围的也是同一个bean。

3.6.生命周期
  • 使用组件经历的过程
                            1.bean的实例化
                                |
                                |
                                |
                            2.参数设置
                                |
                                |
                                |
                            3.Aware接口:只有当前组件实现了这个接口,才会执行到对应的方法
                                |
                                |
                                |
            -----------------------------------------
            |					|					|
            |					|					|
            |					|					|
            |					|					|
    BeanNameAware接口		BeanFactoryAware接口	ApplicationAware接口
    		|					|					|
    setBeanName方法		setBeanFactory方法	setApplicationContext方法
    							|
    							|
    							|
    							|
    					4.BeanPostProcessor接口:新建一个类实现该接口,所有组件都会执行其中的方法
    					个人理解:有点类似于Filter,除了其本身,其余所有组件都会执行这两个方法
    							|
    							|
    							|
    					postProcessBeforeInitialization
    							|
    							|
    							|
    					5.InitializingBean接口
    					AfterPropertiesSet方法
    							|
    							|
    							|
    					6.自定义的某方法,需要在xml容器中配置
    							|
    							|
    							|
    					postProcessAfterInitialization
    							
    					
    					
    					
    特别注意:scope会影响组件到达可用状态之前的这些生命周期的执行(影响执行开始的时间)
    
    	1.对于singleton组件来说,容器初始化的时候(调用getBean方法之间已经执行完了)
    		同时,singletong组件还有后面的destroy方法要执行:7.DisposableBean的destroy方法,8.自定义的destroy方法(xml文件中要进行相应配置)
    	2.对于prototype组件来说,当调用getBean(获得组件时),才开始执行生命周期
    		同时,prototype组件,destroy方法是不执行的
    
  • 演示示例:
    • 在main—java下新建一个life文件夹,里边新建三个类,分别是
      • 用于演示BeanPostProcessor接口的实现类CustomBeanPostProcessor
      • 用于演示生命周期的类LifeCycleBean
      • 用于演示prototype组件的类LifeCycleBean2
      //作用范围:除了他本身,其他的所有组件
      public class CustomBeanPostProcessor implements BeanPostProcessor {
          //bean:当前正在生命周期的组件
          //beanName: 组件的名称
          @Override
          public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
              /*if (beanName.contains("datasource")){
                  DataSource dataSource = (DataSource) bean;
                  String password = dataSource.getPassword();//密文
                  //解密
                  String password2 = jiemi(password);
                  dataSource.setPassword(password2);
              }*/
              //传入的bean通过动态代理生成一个代理对象,return代理对象 → 狸猫换太子
              System.out.println("beanPostProcessor的before:" + beanName);
              return bean;
          }
      
          @Override
          public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
              System.out.println("beanPostProcessor的after");
              return bean;
          }
      }
      
      
      //该类用于演示生命周期的执行
      public class LifeCycleBean implements BeanNameAware, BeanFactoryAware,
              ApplicationContextAware, InitializingBean, DisposableBean {
          String password;
      
          String beanName;//null 👉 通过setBeanName方法完成赋值
      
          BeanFactory beanFactory;
          ApplicationContext applicationContext;
      
          public LifeCycleBean() {
              System.out.println("1、无参构造方法");
          }
      
          public void setPassword(String password) {
              System.out.println("2、设置参数");
              this.password = password;
          }
      
          //获得该组件的name 👉 可以利用获得的组件的name给当前组件的成员变量复制
          @Override
          public void setBeanName(String s) {
              System.out.println("setBeanName:" + s);
              this.beanName = s;
          }
      
          @Override
          public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
              System.out.println("setBeanFactory");
              this.beanFactory = beanFactory;
          }
      
          @Override
          public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
              System.out.println("setApplicationContext");
              this.applicationContext = applicationContext;
          }
      
          @Override
          public void afterPropertiesSet() throws Exception {
              System.out.println("afterPropertiesSet");
          }
      
          public void myinit() {
              System.out.println("自定义的init方法");
          }
      
          public void mydestroy() {
              System.out.println("自定义的lifecycleBean的destroy");
          }
      
          @Override
          public void destroy() throws Exception {
              System.out.println("disposableBean的destroy");
          }
      }
      
      
      //该类用于对比singleton组件,展示prototype组件的不同之处
      //scope prototype
      public class LifeCycleBean2 implements DisposableBean {
          public void mydestroy() {
              System.out.println("lifecycleBean2的destroy");
          }
      
          @Override
          public void destroy() throws Exception {
              System.out.println("life2 disposableBean的destroy");
          }
      }
      
    • 在xml文件中配置
      <?xml version="1.0" encoding="UTF-8"?>
      <beans xmlns="http://www.springframework.org/schema/beans"
             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xmlns:context="http://www.springframework.org/schema/context"
             xsi:schemaLocation="
              http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd
              http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd">
      
          <!-- bean definitions here -->
          <bean id="lifecycleBean" class="life.LifeCycleBean" init-method="myinit" destroy-method="mydestroy">
              <property name="password" value="hello"/>
          </bean>
      
          <bean id="lifeCycleBean2" class="life.LifeCycleBean2" scope="prototype" destroy-method="mydestroy"/>
      
          <bean class="com.cskaoyan.bean.CustomBeanPostProcessor"/>
      
      </beans>
      
    • 在测试类中测试
      public class MyTest {
      
          
          @Test
          public void mytest1(){
              ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("application.xml");
              //singleton组件,在获得组件之前已经完成了实例化,依旧可以打印
              //LifeCycleBean bean = applicationContext.getBean(LifeCycleBean.class);
      
              //prototype组件在getBean之后才可以打印
              LifeCycleBean2 bean1 = applicationContext.getBean(LifeCycleBean2.class);
              LifeCycleBean2 bean2 = applicationContext.getBean(LifeCycleBean2.class);
              LifeCycleBean2 bean3 = applicationContext.getBean(LifeCycleBean2.class);
              
              //关闭才可以看到destroy方法的打印输出
              applicationContext.close();
          }
      }
      
3.7.注解
  • bean标签对应class属性,而通过注解可以免去在xml中配置,无需在容器中注册
  • xml添加注解相应的配置标签
    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="http://www.springframework.org/schema/beans"
           xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
           xmlns:context="http://www.springframework.org/schema/context"
           xsi:schemaLocation="
            http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd
            http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd">
    
        <!-- bean definitions here -->
        <!--扫描该包目录以及所有子包:如果发现了组件注册功能的注解-->
        <context:component-scan base-package="temp"/>
    
    </beans>
    
3.7.1.通用注解
  • 通用注解:@Component
  • 原组件的id就是Component注解中的value属性值,写在对应的类上
  • 如果不写Component注解中的value属性值,那么其默认的值是对应类名首字母小写的形式

@Component是通用注解,可以写在任意类上

3.7.2.其他注解
  • @Service:service层的注解
  • @Respository:dao层的注解
  • @Controller:controller层的注解

可以使用非对应的注解,但是不建议这么做

3.7.3.组件注入
  • @Autowired:按类型注入,只有这个类型实现类唯一的时候,才可以使用
  • @Autowired+@Qualifier:按类型注入,以value属性值指定组件的id值,用于不唯一的类型
  • @Resoure:默认按类型注入,可以使用name属性来指定组件的id值
  • @Value:相当于赋值的value属性

组件注入的使用前提是:当前组件的所在类上已经使用注解注入了容器中,内部嵌套的组件才能使用以上注解注入进该组件

  • 在mapper包下建立UserMapper接口及其实现类UserMapperImpl1,UserMapperImpl2,OrderMapper接口及其实现类OrderMapperImpl
public interface OrderMapper {
    public void hello();
}

//在容器中注册该类,@Component是通用的注解
@Component
public class OrderMapperImpl implements OrderMapper{
    @Override
    public void hello() {
        System.out.println("hello orderMapper2");
    }
}


public interface UserMapper {
    public void sayHello();
}

//在容器中注册该类,使用dao层专属的注解
@Repository
public class UserMapperImpl1 implements UserMapper{
    @Override
    public void sayHello() {
        System.out.println("hello mapper1");
    }
}

//在容器中注册该类
@Repository
public class UserMapperImpl2 implements UserMapper{

    @Override
    public void sayHello() {
        System.out.println("hello mapper2");
    }
}
  • 在service包下建立UserService接口及其实现类UserServiceImpl
public interface UserService {
    public void sayAllHello();
}


//@Component("userService") //组件id为该注解的value属性值
@Component                  //组件id的默认值为类名的首字母小写,即userServiceImpl
public class UserServiceImpl implements UserService {

    //容器中该类型的组件只有一个
    @Autowired
    OrderMapper orderMapper;

    //容器中该类型有两个(两个UserMapper的实现类都注册在了容器里)
    @Autowired
    @Qualifier("userMapperImpl1")
    UserMapper userMapper1;

    @Resource(name = "userMapperImpl2") //默认可以按照类型注入,可以使用name属性来指定组件id
    UserMapper userMapper2;

    /*
    重复的类型,不可以使用该注解注册
    
    @Autowired
    UserMapper userMapper3;
    
    */

    @Override
    public void sayAllHello() {
        orderMapper.hello();
        userMapper1.sayHello();
        userMapper2.sayHello();
    }
}

  • 测试
@Test
public void test() {
    ApplicationContext applicationContext = new ClassPathXmlApplicationContext("application.xml");
    UserService bean = applicationContext.getBean(UserService.class);
    bean.sayAllHello();
}
3.7.4.生命周期&作用域
  • @PostConstruct:自定义的init方法,对应init-method
  • @PreDestroy:自定义的destroy方法,对应destroy-method
  • @scope:将作用域的值写在注解中,写于类上
  • 在上一个案例的基础上,创建一个LifeCycle类,在temp包下
    @Component
    @Scope("singleton")
    public class LifeCycle {
        @PostConstruct
        public void init() {
            System.out.println("自定义init");
        }
        @PreDestroy
        public void destroy() {
            System.out.println("自定义destroy");
        }
    }
    
3.8.Spring单元测试
  • maven工程中导入依赖
    <!-- maven工程中导入spring依赖 -->
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-test</artifactId>
        <version>5.2.10.RELEASE</version>
        <scope>test</scope>
    </dependency>
    
  • 测试容器中组件提供的方法
    //给单元测试类构建一个spring的环境,将单元测试类当作是容器中的组件,注入功能的注解
    @RunWith(SpringJUnit4ClassRunner.class)
    @ContextConfiguration("classpath:application.xml")
    public class AnnotationTest {
    
        @Autowired
        UserService userService;
        @Autowired
        OrderMapper orderMapper;
        @Autowired
        ApplicationContext applicationContext;
        @Test
        public void test() {
            userService.sayAllHello();
        }
    }
    

    test方法以及其ContextConfiguration注解会自动获取ApplicationContext,并在结束的时候自动关闭。

3.9.SpringAOP
3.9.1.Spring动态代理
  • 字节码随用随创建,随用随加载。它与静态代理的区别也在于此。因静态代理是字节码一上来就创建好,并完成加载。装饰者模式就是静态代理的一种体现。
  • 动态代理常用的两种方式
    • 基于接口的动态代理
      • 提供者:JDK 官方的 Proxy 类。
      • 要求:被代理类最少实现一个接口。
    • 基于子类的动态代理
      • 提供者:第三方的 CGLib,如果报 asmxxxx 异常,需要导入 asm.jar。
      • 要求:被代理类不能用 final 修饰的类(最终类)。

    在 spring 中,框架会根据目标类是否实现了接口来决定采用哪种动态代理的方式。

3.9.2. AOP 相关术语
  • Joinpoint(连接点):所谓连接点是指那些被拦截到的点。在 spring 中,这些点指的是方法,因为 spring 只支持方法类型的连接点。
  • Pointcut(切入点):所谓切入点是指我们要对哪些 JoinPoint 进行拦截的定义。
  • Advice(通知/增强):所谓通知是指拦截到 Joinpoint 之后所要做的事情就是通知。
    • 通知的类型:前置通知,后置通知,异常通知,最终通知,环绕通知。
  • Introduction(引介):引介是一种特殊的通知在不修改类代码的前提下, Introduction 可以在运行期为类动态地添加一些方法或 Field。
  • Target(目标对象):代理的目标对象。
  • Weaving(织入):是指把增强应用到目标对象来创建新的代理对象的过程。spring 采用动态代理织入,而 AspectJ 采用编译期织入和类装载期织入。
  • Proxy(代理):一个类被 AOP 织入增强后,就产生一个结果代理类。
  • Aspect(切面):是切入点和通知(引介)的结合,等于切入点(Pointcut) + 通知(Advice)
    		target(被委托类)--------------------weaver(织入)--------------------Proxy(代理类)
                			
                			Pointcut(切入点)---指定增强方法
                			Advice(通知)---通知切入点范围的方法做出何种增强---相当于对委托类的方法
                
                			Joinpoint(连接点)---代理对象正在执行的增强的方法
                                                        |
                                                        |
                                                        V
                								获取方法执行过程中的参数
                                                        |
                                                        |
                                                        V
                								提供了类似于InvocationHandler的invoke方法的参数
    
3.9.3.示例
  • 创建xml文件的Schema约束

    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="http://www.springframework.org/schema/beans"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xmlns:aop="http://www.springframework.org/schema/aop"
        xsi:schemaLocation="http://www.springframework.org/schema/beans
            http://www.springframework.org/schema/beans/spring-beans.xsd
            http://www.springframework.org/schema/aop
            http://www.springframework.org/schema/aop/spring-aop.xsd">
    </beans>
    
  • 在Service层创建UserService接口及其实现类

    public interface UserService {
        public void sayHello(String name);
    }
    
    
    @Service
    public class UserServiceImpl implements UserService {
        @Override
        public void sayHello(String name) {
            System.out.println("hello" + name);
        }
    }
    
  • 创建一个Advice通知类组件,需要实现MethodInterceptor接口

    @Component
    public class CustomAdvice implements MethodInterceptor {
        @Override
        public Object invoke(MethodInvocation methodInvocation) throws Throwable {
    
            System.out.println("hello");
            //执行委托类的代码
            Object proceed = methodInvocation.proceed();
            System.out.println("world");
    
            return proceed;
        }
    }
    
  • 在xml中添加bean标签

    <!-- 注册一个代理类组件,会自动生成代理类,导致对应接口的实现类不唯一,在对应引用类型上通常要添加两个注解配合使用 -->
    <bean id="userServiceProxy" class="org.springframework.aop.framework.ProxyFactoryBean">
        
        <!-- 
    		增强委托类中的全部方法
     			name:固定为target值,表示实现类
    			ref:容器中委托类的组件id,即目标实现类名称
    	-->
        <property name="target" ref="userServiceImpl"/>
        
        <!-- 
    		通知类组件
    			name:固定为interceptorNames
    			value:需要一个字符串的值
     	-->
        <property name="interceptorNames" value="customAdvice"/>
    </bean>
    
  • 在测试类测试

    @RunWith(SpringJUnit4ClassRunner.class)
    @ContextConfiguration("classpath:application.xml")
    public class Temp {
    
        //这里,只写一个Autowired会报错,因为容器中既有UserService的实现类UserServiceImpl,还有容器中生成的userServiceProxy代理类
        //因此,不仅仅需要加Autowired,还需要加上Qualifier注解,指明组件id
        @Autowired
        @Qualifier("userServiceProxy")
        UserService userService;
    
        @Test
        public void test(){
            userService.sayHello("single dog");
        }
    }
    
    /*
    输出结果:
    	hello
    	hello single dog
    	world
    */
    
3.10.AspectJ
  • AspectJ实际上是对AOP编程思想的一个实践,当然,除了AspectJ以外,还有很多其它的AOP实现,例如ASMDex,但目前最好、最方便的,依然是AspectJ。
  • AspectJ最主要的用途就是切面类的配置,通知组件
3.10.1.AspectJ改进
  • 对于3.9.3的案例,可以采用Pointcut组件批量配置的方式,将xml配置更为简洁
  • 配置方式:excution方式,或者@annotation注解方式
    • excution:自定义的切入点表达式,批量注册
    • @annotation:自定义注解方式,增强添加该注解的对应方法
3.10.2.切入点表达式
execution:匹配方法的执行(常用)
    execution(表达式),表达式语法:execution([修饰符] 返回值类型 包名.类名.方法名(参数))
    注意:这里的参数和返回值类型,如果是复杂类型的,要写全类名

写法说明:
	全匹配方式:
		public void service.AccountServiceImpl.saveAccount()
	访问修饰符可以省略
		void service.impl.AccountServiceImpl.saveAccount()
	返回值可以使用*号,表示任意返回值,不可以省略
		* service.impl.AccountServiceImpl.saveAccount()
	包名可以使用*号,表示任意包,但是有几级包,需要写几个*
		* *.*.AccountServiceImpl.saveAccount()
	使用..来表示当前包,及其子包
		* service..AccountServiceImpl.saveAccount()
	类名可以使用*号,表示任意类
		* service..*.saveAccount()
	方法名可以使用*号,表示任意方法
		* service..*.*()
	参数列表可以使用*,表示参数可以是任意数据类型,但是必须有参数
		* service..*.*(*)
	参数列表可以使用..表示有无参数均可,有参数可以是任意类型
		* service..*.*(..)
	全通配方式:
		* *..*.*(..)
注:
    通常情况下,切入点表达式主要用于对业务层的方法进行增强,所以切入点表达式都是切到业务层实现类。
    execution(* com.itheima.service.impl.*.*(..))
总结:
	其实切入点表达式类似于mysql中的占位符和通配符的区别,*相当于占位符,..相当于通配符
3.10.3.AOP相关配置
aop:config
	作用:用于声明开始 aop 的配置
    <aop:config>
        <!-- 配置的代码都写在此处 -->
    </aop:config>


aop:aspect
	作用:用于配置切面。告诉容器哪一个是切面组件
	属性:
        id:给切面提供一个唯一标识。
        ref:引用配置好的通知类 bean 的 id。

    <aop:aspect id="txAdvice" ref="txManager">
    	<!--配置通知的类型要写在此处-->
    </aop:aspect>

aop:advisor
	作用:用于配置通知组件
	属性:
		advice-ref:用于指定通知类中的方法
		pointcut:用于指定切入点表达式
		pointcut-ref::用于指定切入点的表达式的引用
		

aop:pointcut
	作用:用于配置切入点表达式。就是指定对哪些类的哪些方法进行增强。
	属性:
        expression:用于定义切入点表达式。
        id:用于给切入点表达式提供一个唯一标识

    <aop:pointcut expression="execution(
        public void com.itheima.service.impl.AccountServiceImpl.transfer(
                                  java.lang.String, java.lang.String, java.lang.Float))" id="pt1"/>


aop:before
	作用:用于配置前置通知。指定增强的方法在切入点方法之前执行
	属性:
        method:用于指定通知类中的增强方法名称
        ponitcut-ref:用于指定切入点的表达式的引用
        poinitcut:用于指定切入点表达式
        	执行时间点:切入点方法执行之前执行

    <aop:before method="beginTransaction" pointcut-ref="pt1"/>


aop:around
	作用:用于配置通知过程中的方法增强
	属性:
		method:用于指定通知类中的增强方法名称
        ponitcut-ref:用于指定切入点的表达式的引用
        poinitcut:用于指定切入点表达式
        	执行时间点:切入点方法执行时通过代理增强执行



aop:after
	作用:用于配置最终通知
	属性:
        method:指定通知中方法的名称。
        pointct:定义切入点表达式
        pointcut-ref:指定切入点表达式的引用
			执行时间点:无论切入点方法执行时是否有异常,它都会在其后面执行。

	<aop:after method="release" pointcut-ref="pt1"/>


aop:after-returning
	作用:用于配置后置通知
	属性:
        method:指定通知中方法的名称。
        pointct:定义切入点表达式
        pointcut-ref:指定切入点表达式的引用
		returnning: 指定method对应的方法某个参数对应参数来接收方法的执行结果
			执行时间点:切入点方法正常执行之后。它和异常通知只能有一个执行

	<aop:after-returning method="commit" pointcut-ref="pt1" returnning="result"/>


aop:after-throwing
	作用:用于配置异常通知
	属性:
        method:指定通知中方法的名称。
        pointct:定义切入点表达式
        pointcut-ref:指定切入点表达式的引用
		throwing: 指定method对应方法的哪一个参数来接收委托类方法执行过程中抛出的异常
        	执行时间点:切入点方法执行产生异常后执行。它和后置通知只能执行一个

	<aop:after-throwing method="rollback" pointcut-ref="pt1" throwing="exception"/>
3.10.4.excution改进
  • maven中引入依赖

    <dependency>
        <groupId>org.aspectj</groupId>
        <artifactId>aspectjweaver</artifactId>
        <version>1.9.6</version>
    </dependency>
    
  • 在xml中引入AOP的Schema约束

    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="http://www.springframework.org/schema/beans"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xmlns:aop="http://www.springframework.org/schema/aop"
        xsi:schemaLocation="
            http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd
            http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd">
    
        <!-- bean definitions here -->
    	<context:component-scan base-package="temp"/>
        
    </beans>
    
  • 添加配置

    <aop:config>
        <!-- 创建几个切入点 -->
        <aop:pointcut id="mypointcut1" expression="excution(public * service..sayHello(*))"/>
        <aop:pointcut id="mypointcut2" expression="excution(public * service..sayHello())"/>
        <aop:pointcut id="mypointcut3" expression="excution(public * service..sayHello(..))"/>
        
        <!-- 通过此标签选择对应的标签来增强标签里对应匹配的方法 -->
        <!-- 
    			advice-ref:选择通知组件的id
    			pointcut-ref:选择哪一个pointcut的id值,即选择切入点 
        		pointcut:自定义切入点表达式
        -->
        <aop:advisor advice-ref="customAdvice" pointcut-ref="mypointcut3"/>
    </aop:config>
    
  • 在测试类中测试

    @RunWith(SpringJUnit4ClassRunner.class)
    @ContextConfiguration("classpath:application.xml")
    public class Temp {
    	
        //不需要指定Qualifier,此时UserService的引用,已经指向了一个新生成的,并且内部方法已经得到了增强的新的实现类的实例化对象
        @Autowired
        UserService userService;
    
        @Test
        public void test(){
            userService.sayHello("single dog");
        }
    }
    
3.10.5.对于aop配置的应用
  • 新建一个UserService接口及其实现类
    public interface UserService {
    
        public void sayHello();
    
        public String sayHello2(String name);
    }
    
    
    @Service
    public class UserServiceImpl implements UserService{
        @Override
        public void sayHello() {
            System.out.println("hello world");
        }
    
        @Override
        public String sayHello2(String name) {
            String result = "hello " + name;
            System.out.println(result);
            int i = 1/0;
            return result;
        }
    }
    
  • 创建切面类
    @Component
    public class CustomAspect {
    
        public void before(JoinPoint joinPoint){ // before
            //通过joinPoint拿到正在执行的方法中的对应的一些值
    
            Signature signature = joinPoint.getSignature();  //方法的特征值
            String methodName = signature.getName(); //方法名
            Object[] args = joinPoint.getArgs(); //方法的携带参数
            
            System.out.println(methodName + "方法携带的参数是:" + Arrays.asList(args));
            
            //if (args.length != 0 && "景甜".equals(args[0]))  args[0] = "大甜甜";
            
            Object target = joinPoint.getTarget(); //委托类对象
            Object aThis = joinPoint.getThis();    //代理对象
            System.out.println(target.getClass());
            System.out.println(aThis.getClass());
    
            System.out.println("正道的光");
        }
    
        public void after(){ //after
            System.out.println("照在大地上");
        }
    
        //around通知的返回值为Object,委托类方法的执行结果,类似于invocationHandler的invoke
        //参数中包含ProceedingJoinPoint还要执行委托类的方法
        public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
            long start = System.currentTimeMillis();
            System.out.println("开始时间:" + start);
            //执行委托类的方法
            Object proceed = joinPoint.proceed();
            long end = System.currentTimeMillis();
            System.out.println("花费时间为:" + (end - start));
            return proceed;
        }
    
        public void afterReturning(Object result){
            System.out.println("afterReturning:" + result);
        }
        //Throwable也可以
        public void afterThrowing(Exception exception){//需要接收委托类方法执行过程中抛出的异常
            System.out.println("afterThrowing:" + exception.getMessage());
        }
    }
    
  • xml配置
    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="http://www.springframework.org/schema/beans"
           xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
           xmlns:context="http://www.springframework.org/schema/context"
           xmlns:aop="http://www.springframework.org/schema/aop"
           xsi:schemaLocation="
            http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd
            http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd
            http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd">
    
        <!-- bean definitions here -->
        <context:component-scan base-package="com.cskaoyan"/>
    
        <aop:config>
            <!--service层的任意方法-->
            <aop:pointcut id="mypointcut" expression="execution(* service..*(..))"/>
            <aop:aspect ref="customAspect">
                <!--pointcut和通知的结合
                    method属性: 方法名,父标签中的ref属性对应的组件中的方法名 👉 切面组件中的方法名
                -->
                <aop:before method="before" pointcut-ref="mypointcut"/>
                <aop:after method="after" pointcut-ref="mypointcut"/>
                <aop:around method="around" pointcut-ref="mypointcut"/>
                <!--需要通过returning属性指定method对应方法的哪一个参数来接收委托类方法的执行结果-->
                <aop:after-returning method="afterReturning" pointcut-ref="mypointcut" returning="result"/>
                <!--需要通过throwing属性指定method对应方法的哪一个参数来接收委托类方法执行过程中抛出的异常-->
                <aop:after-throwing method="afterThrowing" pointcut-ref="mypointcut" throwing="exception"/>
            </aop:aspect>
        </aop:config>
    </beans>
    
  • 在测试类中测试
    @RunWith(SpringJUnit4ClassRunner.class)
    @ContextConfiguration("classpath:application.xml")
    public class MyTest {
    
        @Autowired
        UserService userService;
    
        @Test
        public void mytest1() {
            userService.sayHello();
        }
    
        @Test
        public void mytest2() {
            userService.sayHello2("single dog");
        }
    }
    
3.10.6.自定义注解
  • 通过自定义注解实现某些功能
    • 自定义一个CountTime注解,位于anno包下
      @Target(ElementType.METHOD)  //注解位于方法上
      @Retention(RetentionPolicy.RUNTIME)  //注解在运行时生效
      public @interface CountTime {
      }
      
    • 实现注解的功能,计算方法运行时间
      @Component
      public class CountTimeAdvice implements MethodInterceptor {
          @Override
          public Object invoke(MethodInvocation methodInvocation) throws Throwable {
      
              long start = System.currentTimeMillis();
              Object proceed = methodInvocation.proceed();
              long end = System.currentTimeMillis();
              System.out.println(methodInvocation.getMethod().getName() + "方法执行时间为:" + (end - start));
              return proceed;
          }
      }
      
    • 新建一个OrderService接口及其实现类
      public interface OrderService {
          public void method1();
          public void method2();
          public void method3();
      }
      
      
      @Service
      public class OrderServiceImpl implements OrderService{
      
          @CountTime
          @Override
          public void method1() {
              System.out.println("method1");
          }
      
          @Override
          public void method2() {
              System.out.println("method2");
      
          }
      
          @CountTime
          @Override
          public void method3()  {
              System.out.println("method3");
              try {
                  Thread.sleep(500);
              } catch (InterruptedException e) {
                  e.printStackTrace();
              }
          }
      }
      
    • 在xml文件中注册
      <!-- Scamea约束对标签顺序有所限制,pointcut必须位于adversor标签上边 -->
      <!-- 指定切入点 -->
      <aop:pointcut id="countTimePointcut" expression="@annotation(注解的全类名)"/>
      <!--
       	指定组件生效范围
      		advice-ref:生效组件的id值
      		pointcut:生效范围
      		pointcut-ref:选择生效范围的对应标签,可以替换pointcut
      -->	
      <aop:advisor advice-ref="countTimeAdvice" pointcut="excution(public * service..sayHello(..))"/>
      
    • 在测试类中测试
      @RunWith(SpringJUnit4ClassRunner.class)
      @ContextConfiguration("classpath:application.xml")
      public class MyTest {
      
          @Autowired
          OrderService orderService;
          
          @Test
          public void test(){
              orderService.method1();
          }
          @Test
          public void test2(){
              orderService.method2();
          }
          @Test
          public void test3(){
              orderService.method3();
          }
      }
      
3.11.AspectJ注解形式
  • 将3.10.5的程序改为注解形式,一一对应
  • 在配置文件中开启注解开关
    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="http://www.springframework.org/schema/beans"
           xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
           xmlns:context="http://www.springframework.org/schema/context"
           xmlns:aop="http://www.springframework.org/schema/aop"
           xsi:schemaLocation="
            http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd
            http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd
            http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd">
    
        <!-- bean definitions here -->
        <context:component-scan base-package="com.cskaoyan"/>
        <!--注解开关-->
        <aop:aspectj-autoproxy/>
    </beans>
    
  • UserService接口及其实现类不必变动,修改CustomAspect类
    @Aspect		//声明该组件是一个通知类组件(Aspect)
    @Component  //注册该组件
    public class CustomAspect {
    
        /**
         * 返回值:void
         * 方法名:任意写 👉 方法名作为id
         * 参数:不需要
         * 方法体
         * @Pointcut:value属性值写的是切入点表达式
         */
        //pointcut以方法的形式存在,声明此方法为Pointcut
        @Pointcut("execution(* com.cskaoyan.service..*(..))")
        public void mypointcut() {}
    
        //@Before("execution(* com.cskaoyan.service..*(..))")//相当于直接写切入点表达式
        @Before("mypointcut()")  //可以使用该简便方式
        public void before(JoinPoint joinPoint){ // 👉 before
            //通过joinPoint拿到正在执行的方法中的对应的一些值
    
            Signature signature = joinPoint.getSignature();
            String methodName = signature.getName();
    
            Object[] args = joinPoint.getArgs();
            System.out.println(methodName + "方法携带的参数是:" + Arrays.asList(args));
            if (args.length != 0 && "景甜".equals(args[0])){
                args[0] = "大甜甜";
            }
    
            Object target = joinPoint.getTarget(); //委托类对象
            Object aThis = joinPoint.getThis();    //代理对象
            System.out.println(target.getClass());
            System.out.println(aThis.getClass());
    
            System.out.println("正道的光");
        }
    
        @After("mypointcut()")
        public void after(){ //after
            System.out.println("照在大地上");
        }
    
        @Around("mypointcut()")
        //around通知的返回值为Object,委托类方法的执行结果,类似于invocationHandler的invoke
        //参数中包含ProceedingJoinPoint,还要执行委托类的方法
        public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
            long start = System.currentTimeMillis();
            System.out.println("开始时间:" + start);
    
    
    
            //执行委托类的方法
            Object proceed = joinPoint.proceed();
            long end = System.currentTimeMillis();
            System.out.println("花费时间为:" + (end - start));
            return proceed;
        }
    
        @AfterReturning(value = "mypointcut()",returning = "result")
        public void afterReturning(Object result){
            System.out.println("afterReturning:" + result);
        }
        @AfterThrowing(value = "mypointcut()",throwing = "exception")
        //Throwable也可以
        public void afterThrowing(Exception exception){//需要接收委托类方法执行过程中抛出的异常
            System.out.println("afterThrowing:" + exception.getMessage());
        }
    }
    
3.12.Spring-Mybatis整合
  • 导入依赖文件
    <dependencies>
            <dependency>
                <groupId>org.springframework</groupId>
                <artifactId>spring-context</artifactId>
                <version>5.2.10.RELEASE</version>
            </dependency>
            <dependency>
                <groupId>org.springframework</groupId>
                <artifactId>spring-test</artifactId>
                <version>5.2.10.RELEASE</version>
                <scope>test</scope>
            </dependency>
            <dependency>
                <groupId>junit</groupId>
                <artifactId>junit</artifactId>
                <version>4.13.1</version>
                <scope>test</scope>
            </dependency>
            <dependency>
                <groupId>org.aspectj</groupId>
                <artifactId>aspectjweaver</artifactId>
                <version>1.9.6</version>
            </dependency>
    
            <!--mybatis-->
            <dependency>
                <groupId>org.mybatis</groupId>
                <artifactId>mybatis</artifactId>
                <version>3.5.6</version>
            </dependency>
            <dependency>
                <groupId>mysql</groupId>
                <artifactId>mysql-connector-java</artifactId>
                <version>5.1.47</version>
                <scope>runtime</scope>
            </dependency>
            <!--需要额外引入的依赖-->
            <!--spring-jdbc\spring-tx transaction-->
            <dependency>
                <groupId>org.springframework</groupId>
                <artifactId>spring-jdbc</artifactId>
                <version>5.2.10.RELEASE</version>
            </dependency>
            <!--mybatis对spring支持的依赖-->
            <dependency>
                <groupId>org.mybatis</groupId>
                <artifactId>mybatis-spring</artifactId>
                <version>2.0.6</version>
            </dependency>
            <!--datasource-->
            <dependency>
                <groupId>com.alibaba</groupId>
                <artifactId>druid</artifactId>
                <version>1.2.4</version>
            </dependency>
    
  • 创建一个UserMapper用于映射Mybatis
    public interface UserMapper {
    
        String selectUsernameById(@Param("id") Integer id);
    }
    
  • 创建一个UserService接口及其实现类
    public interface UserService {
    
        String queryUsernameById(Integer id);
    }
    
    
    @Service
    public class UserServiceImpl implements UserService{
    
        @Autowired
        UserMapper userMapper;
    
        //@Autowired
        //SqlSessionFactory sqlSessionFactory;
    
        @Override
        public String queryUsernameById(Integer id) {
            //SqlSession sqlSession = sqlSessionFactory.openSession();
            //userMapper = sqlSession.getMapper(UserMapper.class);
            return userMapper.selectUsernameById(id);
        }
    }
    
  • spring的xml配置文件
    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="http://www.springframework.org/schema/beans"
           xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
           xmlns:context="http://www.springframework.org/schema/context"
           xmlns:aop="http://www.springframework.org/schema/aop"
           xsi:schemaLocation="
            http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd
            http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd
            http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd">
    
        <!-- bean definitions here -->
        <context:component-scan base-package="com.cskaoyan"/>
    
        <bean id="datasource" class="com.alibaba.druid.pool.DruidDataSource">
            <property name="driverClassName" value="com.mysql.jdbc.Driver"/>
            <property name="url" value="jdbc:mysql://localhost:3306/cskaoyan_db?useUnicode=true&amp;characterEncoding=utf-8"/>
            <property name="username" value="root"/>
            <property name="password" value="123456"/>
        </bean>
    
        <!--FactoryBean的getObject方法的返回值是SqlSessionFactory-->
        <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
            <property name="dataSource" ref="datasource"/>
            <!--typeAliases-->
            <property name="typeAliasesPackage" value="com.cskaoyan.bean"/>
            <!--mybatis.xml-->
            <property name="configLocation" value="classpath:mybatis.xml"/>
        </bean>
        <!--MapperScannerConfigurer,在这里可以加载mybatis的配置,可以不写-->
        <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
            <property name="basePackage" value="com.cskaoyan.mapper"/>
            <!--<property name="sqlSessionFactory" ref="sqlSessionFactory"/>-->
            <property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"/>
        </bean>
    </beans>
    
  • Mybatis.xml的配置
    <?xml version="1.0" encoding="UTF-8" ?>
    <!DOCTYPE configuration
            PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
            "http://mybatis.org/dtd/mybatis-3-config.dtd">
    <configuration>
        <settings>
            <!-- 没有什么特别需要的配置,可以不写此文件 -->
            <setting name="cacheEnabled" value="true"/>
            <setting name="lazyLoadingEnabled" value="true"/>
        </settings>
    </configuration>
    
  • 编写测试类
    @RunWith(SpringJUnit4ClassRunner.class)
    @ContextConfiguration("classpath:application.xml")
    public class MyTest {
    
        @Test
        public void mytest1() throws IOException {
            SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
            InputStream inputStream = Resources.getResourceAsStream("mybatis.xml");
            SqlSessionFactory sqlSessionFactory = builder.build(inputStream);
            SqlSession sqlSession = sqlSessionFactory.openSession();
        }
    
        @Autowired
        UserService userService;
    
        @Test
        public void mytest2() {
            String username = userService.queryUsernameById(2);
            System.out.println(username);
        }
    
        @Autowired
        UserMapper userMapper;
    
        @Test
        public void mytest3() {
            String username = userMapper.selectUsernameById(3);
        }
    }
    
3.13.Spring事务
  • PlatformTransactionManager接口提供事务操作的方法,包含有3个具体的操作
    • 获取事务状态信息:TransactionStatus getTransaction(TransactionDefinition definition)
    • 提交事务:void commit(TransactionStatus status)
    • 回滚事务:void rollback(TransactionStatus status)
  • 在开发中都是使用它的实现类,真正管理事务的对象:
    • 使用 SpringJDBC 或 iBatis 进行持久化数据时使用org.springframework.jdbc.datasource.DataSourceTransactionManager
    • 使用Hibernate 版本进行持久化数据时使用org.springframework.orm.hibernate5.HibernateTransactionManager
3.13.1.TransactionDefinition
  • 事务的定义信息对象,有如下方法
    • 获取事务对象名称:String getName()
    • 获取事务隔离级:int getlsolationLevel()
    • 获取事务传播行为:int getPropagationBehavior()
    • 获取事务超时时间:int getTimeout()
      • 默认值是-1,没有超时限制。如果有,以秒为单位进行设置。
    • 获取事务是否只读:boolean isReadOnly()
      • 建议查询时设置为只读。
  • 开启事务的情形:
    • 读写型事务:增加、除、修改开启事务。
    • 只读型事务:执行查询时,也会开启事务
  • 事务的隔离级别:事务隔离级反映事务提交并发访问时的处理态度
    • ISOLATION_DEFAULT:默认级别,归属下列某一种
    • ISOLATION_READ_UNCOMMITTED:可以读取未提交数据
    • ISOLATION_READ_COMMITTED只能读取已提交数据,解决脏读问题(Oracle默认级别)
    • ISOLATION_REPEATABLE_READ:是否读取其他事务提交修改后的数据,解决不可重复读问题(MySQL默认级别)
    • ISOLATION_SERIALIZABLE:是否读取其他事务提交添加后的数据,解决幻影读问题
  • 事务的传播行为
    • REQUIRED:如果当前没有事务,就新建一个事务,如果已经存在一个事务中,加入到这个事务中。一般的选择(默认值)
      • 要么一起提交,要么一起回滚,任何一个事务发生异常,均回滚
    • SUPPORTS:支持当前事务,如果当前没有事务,就以非事务方式执行(没有事务)
    • MANDATORY:使用当前的事务,如果当前没有事务,就抛出异常
    • REQUERS_NEW:如果没有事务就新建事务,如果当前在事务中,把当前事务挂起,内部事务不受影响(自私型事务)。
      • 如果B中调用了A,A发生异常,那么AB均回滚,B发生异常,那么只有B回滚
    • NOT_SUPPORTED:以非事务方式执行操作,如果当前存在事务,就把当前事务挂起
    • NEVER:以非事务方式运行,如果当前存在事务,抛出异常
    • NESTED:如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则执行 REQUIRED 类似的操作(无私型事务)。
      • 如果B中调用了A,B发生异常,那么AB均回滚,A发生异常,那么只有A回滚
3.13.2.TransactionStatus
  • 此接口提供的是事务具体的运行状态,TransactionStatus接口描述了某个时间点上事务对象的状态信息,包含有6个具体的操作
    • 刷新事务:void flush()
    • 获取是否存在存储点:boolean hasSavepoint()
    • 获取事务是否完成:boolean isCompleted()
    • 获取事务是否为新的事务:boolean isNewTransaction()
    • 获取事务是否回滚:boolean isRollbackOnly()
    • 设置事务回滚:void setRollbackOnly()
3.13.3.基于XML的声明式事务控制
  • 搭建一个转账业务,可以实现异常回滚,保证资金安全
  1. 版本一:TransactionTemplate模板

    • 引入依赖
      <dependency>
                  <groupId>org.springframework</groupId>
                  <artifactId>spring-jdbc</artifactId>
                  <version>5.2.10.RELEASE</version>
              </dependency>
              <dependency>
                  <groupId>mysql</groupId>
                  <artifactId>mysql-connector-java</artifactId>
                  <version>5.1.47</version>
              </dependency>
              <dependency>
                  <groupId>org.mybatis</groupId>
                  <artifactId>mybatis</artifactId>
                  <version>3.5.6</version>
              </dependency>
              <dependency>
                  <groupId>org.mybatis</groupId>
                  <artifactId>mybatis-spring</artifactId>
                  <version>2.0.6</version>
              </dependency>
              <dependency>
                  <groupId>org.springframework</groupId>
                  <artifactId>spring-context</artifactId>
                  <version>5.2.10.RELEASE</version>
              </dependency>
              <dependency>
                  <groupId>org.springframework</groupId>
                  <artifactId>spring-test</artifactId>
                  <version>5.2.10.RELEASE</version>
              </dependency>
              <dependency>
                  <groupId>com.alibaba</groupId>
                  <artifactId>druid</artifactId>
                  <version>1.2.4</version>
              </dependency>
              <dependency>
                  <groupId>junit</groupId>
                  <artifactId>junit</artifactId>
                  <version>4.13.1</version>
                  <scope>test</scope>
              </dependency>
      
    • 配置application.xml
      <?xml version="1.0" encoding="UTF-8"?>
      <beans xmlns="http://www.springframework.org/schema/beans"
             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xmlns:context="http://www.springframework.org/schema/context"
             xmlns:aop="http://www.springframework.org/schema/aop"
             xsi:schemaLocation="
              http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd
              http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd
              http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd">
      
          <!-- bean definitions here -->
          <context:component-scan base-package="com.cskaoyan"/>
          <bean id="datasource" class="com.alibaba.druid.pool.DruidDataSource">
              <property name="driverClassName" value="com.mysql.jdbc.Driver"/>
              <property name="url" value="jdbc:mysql://localhost:3306/cskaoyan_db?useUnicode=true&amp;characterEncoding=utf8"/>
              <property name="username" value="root"/>
              <property name="password" value="123456"/>
          </bean>
          <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
              <property name="dataSource" ref="datasource"/>
          </bean>
          <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
              <property name="basePackage" value="com.cskaoyan.mapper"/>
              <property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"/>
          </bean>
      
          <!--TransactionManager-->
          <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
              <property name="dataSource" ref="datasource"/>
          </bean>
          <!--TransactionTemplate-->
          <bean class="org.springframework.transaction.support.TransactionTemplate">
              <property name="transactionManager" ref="transactionManager"/>
          </bean>
      </beans>
      
    • 创建Dao层
      public interface AccountMapper {
      
          Integer selectMoneyById(@Param("id") Integer id);
      
          int updateMoneyById(@Param("money") Integer money, @Param("id") Integer id);
      }
      
    • 创建映射文件AccountMapper.xml
      <?xml version="1.0" encoding="UTF-8" ?>
      <!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
      <mapper namespace="com.cskaoyan.mapper.AccountMapper">
      
          <select id="selectMoneyById" resultType="integer">
              select money from cskaoyan_account where id = #{id}
          </select>
      
          <update id="updateMoneyById" >
              update cskaoyan_account set money = #{money} where id = #{id}
          </update>
      </mapper>
      
    • 创建Service层
      public interface AccountService {
          void transfer(Integer fromId, Integer destId, Integer money);
      }
      
      
      @Service
      public class AccountServiceImpl implements AccountService{
      
          @Autowired
          AccountMapper accountMapper;
      
          @Autowired
          TransactionTemplate transactionTemplate;
      
          @Override
          public void transfer(Integer fromId, Integer destId, Integer money) {
              Integer fromMoney = accountMapper.selectMoneyById(fromId) - money;
              Integer destMoney = accountMapper.selectMoneyById(destId) + money;
      
              //fromMoney -= money;
              //destMoney += money;
      
              //需要返回值的方法,返回值可以自定义
              /*Object execute = transactionTemplate.execute(new TransactionCallback<Object>() {
                  //哪一部分需要增加事务,哪部分代码就放入到doInTransaction方法中
                  //doInTransaction方法的返回值 → 给到TransactionTemplate的execute方法
                  @Override
                  public Object doInTransaction(TransactionStatus transactionStatus) {
                      accountMapper.updateMoneyById(fromMoney, fromId);
                      //int i = 1 / 0;
                      accountMapper.updateMoneyById(destMoney, destId);
                      return 5;
                  }
              });
              System.out.println(execute);*/
              
              //无需返回值的方法
              transactionTemplate.execute(new TransactionCallbackWithoutResult() {
                  @Override
                  protected void doInTransactionWithoutResult(TransactionStatus transactionStatus) {
                      accountMapper.updateMoneyById(fromMoney, fromId);
                      int i = 1/0;
                      accountMapper.updateMoneyById(destMoney, destId);
                  }
              });
          }
      }
      
    • 创建测试类
      @RunWith(SpringJUnit4ClassRunner.class)
      @ContextConfiguration("classpath:application.xml")
      public class MyTest {
      
          @Autowired
          AccountService accountService;
      
          @Test
          public void test(){
              accountService.transfer(1,2,1000);
          }
      }
      
  2. 版本二:TransactionProxy形式

    • 创建Dao层
      public interface AccountMapper {
      
          Integer selectMoneyById(@Param("id") Integer id);
      
          int updateMoneyById(@Param("money") Integer money, @Param("id") Integer id);
      }
      
    • Mybatis映射文件
      <?xml version="1.0" encoding="UTF-8" ?>
      <!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
      <mapper namespace="com.cskaoyan.mapper.AccountMapper">
      
          <select id="selectMoneyById" resultType="integer">
              select money from cskaoyan_account where id = #{id}
          </select>
      
          <update id="updateMoneyById" >
              update cskaoyan_account set money = #{money} where id = #{id}
          </update>
      </mapper>
      
    • service层
      public interface AccountService {
      
          void transfer(Integer fromId, Integer destId, Integer money);
      }
      
      
      @Service
      public class AccountServiceImpl implements AccountService{
      
          @Autowired
          AccountMapper accountMapper;
      
          @Override
          public void transfer(Integer fromId, Integer destId, Integer money) {
              Integer fromMoney = accountMapper.selectMoneyById(fromId) - money;
              Integer destMoney = accountMapper.selectMoneyById(destId) + money;
      
              //fromMoney -= money;
              //destMoney += money;
      
              accountMapper.updateMoneyById(fromMoney, fromId);
              //int i = 1/0;
              accountMapper.updateMoneyById(destMoney, destId);
          }
      }
      
    • application.xml配置文件
      <?xml version="1.0" encoding="UTF-8"?>
      <beans xmlns="http://www.springframework.org/schema/beans"
             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xmlns:context="http://www.springframework.org/schema/context"
             xmlns:aop="http://www.springframework.org/schema/aop"
             xsi:schemaLocation="
              http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd
              http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd
              http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd">
      
          <!-- bean definitions here -->
          <context:component-scan base-package="com.cskaoyan"/>
          <bean id="datasource" class="com.alibaba.druid.pool.DruidDataSource">
              <property name="driverClassName" value="com.mysql.jdbc.Driver"/>
              <property name="url" value="jdbc:mysql://localhost:3306/cskaoyan_db?useUnicode=true&amp;characterEncoding=utf8"/>
              <property name="username" value="root"/>
              <property name="password" value="123456"/>
          </bean>
          <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
              <property name="dataSource" ref="datasource"/>
          </bean>
          <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
              <property name="basePackage" value="com.cskaoyan.mapper"/>
              <property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"/>
          </bean>
      
          <!--TransactionManager-->
          <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
              <property name="dataSource" ref="datasource"/>
          </bean>
          <!--委托类组件accountServiceImpl-->
          <!--代理组件-->
          <bean id="accountServiceProxy" class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean">
              <property name="target" ref="accountServiceImpl"/>
              <property name="transactionManager" ref="transactionManager"/>
              <!--TransactionAttributes → TransactionDefinition-->
              <property name="transactionAttributes">
                  <!--properties类型:既要表达key,又要表达value-->
                  <props>
                      <!--
                          key的含义:方法名
                          value的含义:TransactionDefinition
                          PROPAGATION_XXX:传播行为
                          ISOLATION_XXX:隔离级别
                          readOnly:只读
                          timeout_数字:超时时间,单位是秒
                          -Exception:当发生XXX异常的时候回滚
                          +Exception:当发生xxx异常是不回滚
                      -->
                      <prop key="transfer">PROPAGATION_REQUIRED,ISOLATION_DEFAULT,timeout_5</prop>
                  </props>
              </property>
          </bean>
      
      </beans>
      
    • 测试类测试
      @RunWith(SpringJUnit4ClassRunner.class)
      @ContextConfiguration("classpath:application.xml")
      public class MyTest {
      
          @Autowired
          @Qualifier("accountServiceProxy")
          AccountService accountService;
      
          @Test
          public void mytest1(){
              accountService.transfer(1,2,1000);
          }
      }
      
  3. 版本三:Transaction-advisor

    • 创建Dao层
      public interface AccountMapper {
      
          Integer selectMoneyById(@Param("id") Integer id);
      
          int updateMoneyById(@Param("money") Integer money, @Param("id") Integer id);
      }
      
    • Mybatis映射文件
      <?xml version="1.0" encoding="UTF-8" ?>
      <!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
      <mapper namespace="com.cskaoyan.mapper.AccountMapper">
      
          <select id="selectMoneyById" resultType="integer">
              select money from cskaoyan_account where id = #{id}
          </select>
      
          <update id="updateMoneyById" >
              update cskaoyan_account set money = #{money} where id = #{id}
          </update>
      </mapper>
      
    • service层
      public interface AccountService {
      
          void transfer(Integer fromId, Integer destId, Integer money);
      }
      
      
      @Service
      public class AccountServiceImpl implements AccountService{
      
          @Autowired
          AccountMapper accountMapper;
      
      
          @Override
          public void transfer(Integer fromId, Integer destId, Integer money) {
              Integer fromMoney = accountMapper.selectMoneyById(fromId) - money;
              Integer destMoney = accountMapper.selectMoneyById(destId) + money;
      
              //fromMoney -= money;
              //destMoney += money;
      
              accountMapper.updateMoneyById(fromMoney, fromId);
              int i = 1/0;
              accountMapper.updateMoneyById(destMoney, destId);
      
          }
      }
      
    • application.xml配置文件
      <?xml version="1.0" encoding="UTF-8"?>
      <beans xmlns="http://www.springframework.org/schema/beans"
             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xmlns:context="http://www.springframework.org/schema/context"
             xmlns:aop="http://www.springframework.org/schema/aop"
             xmlns:tx="http://www.springframework.org/schema/tx"
             xsi:schemaLocation="
              http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd
              http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd
              http://www.springframework.org/schema/tx https://www.springframework.org/schema/tx/spring-tx.xsd
              http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd">
      
          <!-- bean definitions here -->
          <context:component-scan base-package="com.cskaoyan"/>
          <bean id="datasource" class="com.alibaba.druid.pool.DruidDataSource">
              <property name="driverClassName" value="com.mysql.jdbc.Driver"/>
              <property name="url" value="jdbc:mysql://localhost:3306/cskaoyan_db?useUnicode=true&amp;characterEncoding=utf8"/>
              <property name="username" value="root"/>
              <property name="password" value="123456"/>
          </bean>
          <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
              <property name="dataSource" ref="datasource"/>
          </bean>
          <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
              <property name="basePackage" value="com.cskaoyan.mapper"/>
              <property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"/>
          </bean>
      
          <!--TransactionManager-->
          <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
              <property name="dataSource" ref="datasource"/>
          </bean>
      
          <aop:config>
              <aop:pointcut id="txPointcut" expression="execution(* com..service..*(..))"/>
              <!--事务通知组件-->
              <aop:advisor advice-ref="transactionAdvice" pointcut-ref="txPointcut"/>
          </aop:config>
      
          <!--tx:advice-->
          <tx:advice id="transactionAdvice" transaction-manager="transactionManager">
              <!--method definition-->
              <tx:attributes>
                  <tx:method name="transfer*" propagation="REQUIRED" 
                             isolation="REPEATABLE_READ" read-only="false" timeout="5"/>
              </tx:attributes>
          </tx:advice>
      </beans>
      
    • 测试类中测试
      @RunWith(SpringJUnit4ClassRunner.class)
      @ContextConfiguration("classpath:application.xml")
      public class MyTest {
      
          @Autowired
          AccountService accountService;
      
          @Test
          public void test1(){
              accountService.transfer(1,2,1000);
          }
      }
      
  4. 版本四:Transactional

    • Dao层
      public interface AccountMapper {
      
          Integer selectMoneyById(@Param("id") Integer id);
      
          int updateMoneyById(@Param("money") Integer money, @Param("id") Integer id);
      }
      
    • Mybatis映射
      <?xml version="1.0" encoding="UTF-8" ?>
      <!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
      <mapper namespace="com.cskaoyan.mapper.AccountMapper">
      
          <select id="selectMoneyById" resultType="integer">
              select money from cskaoyan_account where id = #{id}
          </select>
      
          <update id="updateMoneyById" >
              update cskaoyan_account set money = #{money} where id = #{id}
          </update>
      </mapper>
      
    • service层
      public interface AccountService {
      
          void transfer(Integer fromId, Integer destId, Integer money);
      }
      
      
      @Transactional//ElementType.Type意味着当前组件里的全部方法都增加上事务
      @Service
      public class AccountServiceImpl implements AccountService{
      
          @Autowired
          AccountMapper accountMapper;
      
          @Transactional(isolation = Isolation.REPEATABLE_READ,propagation = Propagation.REQUIRED,
                  readOnly = true,timeout = 5)//ElementType.Method 当前方法增加上事务
          @Override
          public void transfer(Integer fromId, Integer destId, Integer money) {
              Integer fromMoney = accountMapper.selectMoneyById(fromId) - money;
              Integer destMoney = accountMapper.selectMoneyById(destId) + money;
      
              //fromMoney -= money;
              //destMoney += money;
      
              accountMapper.updateMoneyById(fromMoney, fromId);
              int i = 1/0;
              accountMapper.updateMoneyById(destMoney, destId);
      
          }
      }
      
    • application.xml配置
      <?xml version="1.0" encoding="UTF-8"?>
      <beans xmlns="http://www.springframework.org/schema/beans"
             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xmlns:context="http://www.springframework.org/schema/context"
             xmlns:aop="http://www.springframework.org/schema/aop"
             xmlns:tx="http://www.springframework.org/schema/tx"
             xsi:schemaLocation="
              http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd
              http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd
              http://www.springframework.org/schema/tx https://www.springframework.org/schema/tx/spring-tx.xsd
              http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd">
      
          <!-- bean definitions here -->
          <context:component-scan base-package="com.cskaoyan"/>
          <bean id="datasource" class="com.alibaba.druid.pool.DruidDataSource">
              <property name="driverClassName" value="com.mysql.jdbc.Driver"/>
              <property name="url" value="jdbc:mysql://localhost:3306/cskaoyan_db?useUnicode=true&amp;characterEncoding=utf8"/>
              <property name="username" value="root"/>
              <property name="password" value="123456"/>
          </bean>
          <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
              <property name="dataSource" ref="datasource"/>
          </bean>
          <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
              <property name="basePackage" value="com.cskaoyan.mapper"/>
              <property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"/>
          </bean>
      
          <!--TransactionManager-->
          <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
              <property name="dataSource" ref="datasource"/>
          </bean>
          <!-- 打开事务的注解开关 -->
          <tx:annotation-driven transaction-manager="transactionManager"/>
      </beans>
      
    • 测试类测试
      @RunWith(SpringJUnit4ClassRunner.class)
      @ContextConfiguration("classpath:application.xml")
      public class MyTest {
      
          @Autowired
          AccountService accountService;
      
          @Test
          public void mytest1(){
              accountService.transfer(1,2,1000);
          }
      }
      
  5. 版本五:Transactional-Config纯注解形式,将版本四改为无application.xml配置文件

    • Dao层
      public interface AccountMapper {
      
          Integer selectMoneyById(@Param("id") Integer id);
      
          int updateMoneyById(@Param("money") Integer money, @Param("id") Integer id);
      }
      
    • Mybatis映射
      <?xml version="1.0" encoding="UTF-8" ?>
      <!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
      <mapper namespace="com.cskaoyan.mapper.AccountMapper">
      
          <select id="selectMoneyById" resultType="integer">
              select money from cskaoyan_account where id = #{id}
          </select>
      
          <update id="updateMoneyById" >
              update cskaoyan_account set money = #{money} where id = #{id}
          </update>
      </mapper>
      
    • service层
      public interface AccountService {
      
          void transfer(Integer fromId, Integer destId, Integer money);
      }
      
      
      @Transactional//ElementType.Type意味着当前组件里的全部方法都增加上事务
      @Service
      public class AccountServiceImpl implements AccountService{
      
          @Autowired
          AccountMapper accountMapper;
      
          @Transactional(isolation = Isolation.REPEATABLE_READ,propagation = Propagation.REQUIRED,
                  readOnly = false,timeout = 5)//ElementType.Method 当前方法增加上事务
          @Override
          public void transfer(Integer fromId, Integer destId, Integer money) {
              Integer fromMoney = accountMapper.selectMoneyById(fromId) - money;
              Integer destMoney = accountMapper.selectMoneyById(destId) + money;
      
              //fromMoney -= money;
              //destMoney += money;
      
              accountMapper.updateMoneyById(fromMoney, fromId);
              int i = 1/0;
              accountMapper.updateMoneyById(destMoney, destId);
      
          }
      }
      
    • 创建一个配置类,用于替代原application.xml的功能
      @Configuration  //将此类作为配置类
      @ComponentScan("com.cskaoyan")  //组件扫描功能,value值为包名
      @EnableTransactionManagement    //打开事务注解的开关
      @EnableAspectJAutoProxy // 对应标签aop:aspectj-autoproxy,是aspectj的注解形式的开关,在3.11中用到
      public class ApplicationConfig {
      
          @Autowired
          AccountService accountService;
      
          /**    @Bean注解,相当于原xml配置中的Bean标签
           *         返回值:对应bean标签中的class属性值或其接口,即组件的类或接口
           *         方法体:按照se的代码风格去写即可
           *         组件id:可以使用@Bean的value属性指定;如果不写默认组件id为方法名
           */
          //对应原xml文件中的数据源配置
          //@Bean("datasource") //组件id为datasource
          @Bean                 //不写,默认组件id为方法名dataSource
          public DruidDataSource dataSource(){
              DruidDataSource druidDataSource = new DruidDataSource();
              druidDataSource.setDriverClassName("com.mysql.jdbc.Driver");
              druidDataSource.setUrl("jdbc:mysql://localhost:3306/cskaoyan_db");
              druidDataSource.setUsername("root");
              druidDataSource.setPassword("123456");
              return druidDataSource;
          }
          @Bean                 //组件id为dataSource2
          public DruidDataSource dataSource2(){
              DruidDataSource druidDataSource = new DruidDataSource();
              druidDataSource.setDriverClassName("com.mysql.jdbc.Driver");
              druidDataSource.setUrl("jdbc:mysql://localhost:3306/shenguancheng_db");
              druidDataSource.setUsername("root");
              druidDataSource.setPassword("123456");
              return druidDataSource;
          }
          //public DataSource
      
          /**
           * 可以通过形参按照类型从容器中取出组件
           *     如果容器中该类型组件不止一个,需要使用@Qualifier指定组件id
           */
          @Bean
          public SqlSessionFactoryBean sqlSessionFactory(@Qualifier("dataSource") DataSource dataSource){
              SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
              sqlSessionFactoryBean.setDataSource(dataSource);
              return sqlSessionFactoryBean;
          }
      
          @Bean
          public MapperScannerConfigurer mapperScannerConfigurer(){
              MapperScannerConfigurer mapperScannerConfigurer = new MapperScannerConfigurer();
              mapperScannerConfigurer.setBasePackage("com.cskaoyan.mapper");
              mapperScannerConfigurer.setSqlSessionFactoryBeanName("sqlSessionFactory");
              return mapperScannerConfigurer;
          }
          @Bean
          public DataSourceTransactionManager dataSourceTransactionManager(@Qualifier("dataSource") DataSource dataSource){
              DataSourceTransactionManager dataSourceTransactionManager = new DataSourceTransactionManager();
              dataSourceTransactionManager.setDataSource(dataSource);
              return dataSourceTransactionManager;
          }
      }
      
    • 测试类中测试
      @RunWith(SpringJUnit4ClassRunner.class)
      @ContextConfiguration(classes = ApplicationConfig.class)  // 加载配置类
      public class MyTest {
      
          @Autowired
          AccountService accountService;
      
          @Test
          public void mytest1(){
              accountService.transfer(1,2,1000);
          }
      }
      

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/181045.html

(0)
飞熊的头像飞熊bm

相关推荐

发表回复

登录后才能评论
极客之音——专业性很强的中文编程技术网站,欢迎收藏到浏览器,订阅我们!