玩命加载中 . . .

Spring框架


Spring

Spring概述

  • Spring是分层的 Java SE/EE应用 full-stack 轻量级开源框架,以IoC(Inverse Of Control: 反转控制)和 AOP(Aspect Oriented Programming:面向切面编程)为内核

  • spring 的优势

    • 方便解耦,简化开发
    • AOP编程的支持
    • 声明式事务的支持
    • 方便程序的测试
    • 方便集成各种优秀框架
    • 降低 JavaEE API的使用难度
  • spring的体系结构

    image-20200601133424281
  • IOC和AOP

    **IOC:**所谓控制反转就是应用本身依赖对象的创建和维护权力交给外部容器负责的。这样控制权就由应用转移到了外部容器,控制权的转移就是所谓反转,目的是为了解耦,获得更好的扩展性和良好的可维护性。

    核心容器的主要组件是BeanFactory和ApplicationContext,它是工厂模式的实现,通过BeanFactory反射实例化一个bean对象,将其加入的spring容器中管理。

    AOP:面向切面编程,使用动态代理的设计模式,在不改变原来代码的基础上动态的增加功能以满足新的需求,如:动态的增加日志、安全或异常处理等。AOP使业务逻辑各部分间的耦合度降低,提高程序可重用性,提高开发效率。

  • 原代码耦合(程序间的依赖关系)问题的解决

image-20200601135015492
  • 解决方案:

    1. 使用反射创建对象,避免使用New关键字

      在创建好的工厂类中,使用反射的方法创建实例化对象,其中beanPath就是配置文件中的值
      bean = Class.forName(beanPath).newInstance();
      
    2. 通过读取配置文件来获取要创建的对象全限定类名

      在创建好的工厂类中,使用静态代码块加载配置文件
      props = new Properties();
      //读取配置文件
      InputStream is = BeanFactory.class.getClassLoader().getResourceAsStream("Bean.properties");
      //加载配置文件
      props.load(is);
      
    3. 优化:让类对象只被初始化(创建)一次,效率更高

      创建一个map集合,将配置文件中的key,value全部遍历出来,生成对象,保存在map中,
      其他人调用的是mapz中的key,返回的是对象
      
      //读取配置文件中所有的值,将起存入到map中
                  //实例化一个map对象
                  beans = new HashMap<String,Object>();
                  //读取props中的所有Key
                  Enumeration keys = props.keys();
                  //遍历所有的key
                  while(keys.hasMoreElements()){
                      //取出每个key
                      String key = keys.nextElement().toString();
                      //根据key获取value
                      String beanPath = props.getProperty(key);
                      //反射创建实例化对象
                      Object bean = Class.forName(beanPath).newInstance();
                      //将key和对象都存入到map集合中
                      beans.put(key,bean);
                  }
      
      /*
          根据key值获取bean对象
      */
      public static Object getBean(String beanName){
              return beans.get(beanName);
          }
      

    IOC中最基本的技术就是“反射(Reflection)”,通俗来讲就是根据给出的类名(字符串方式)来动态地生成对象,这种编程方式可以让对象在生成时才被决定到底是哪一种对象。其实我们可以把IOC容器看作是一个工厂,这个工厂里要生产的对象都在配置文件中给出定义,然后利用编程语言提供的反射机制,根据配置文件中给出的类名生成相应的对象。这就是IOC的实现原理–反射机制在工厂模式中的应用。

  • 使用 spring的 IOC解决程序耦合

      1. 准备 spring 的开发包 :

        官网:http://spring.io/

        下载地址: http://repo.springsource.org/libs-release-local/org/springframework/spring

      1. 创建一个 xml 文件
        1. 增加约束:
      <?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"> </beans> 
      
        1. 在配置文件中配置 service 和 dao
      <!-- bean 标签:用于配置让 spring 创建对象,并且存入 ioc 容器之中       
      id 属性:对象的唯一标识。       
      class 属性:指定要创建对象的全限定类名 --> 
      <!-- 配置 service -->    
      <bean id="accountService" class="com.itheima.service.impl.AccountServiceImpl"> </bean> 
      <!-- 配置 dao --> 
      <bean id="accountDao" class="com.itheima.dao.impl.AccountDaoImpl"></bean>
      
        1. 类中获取spring容器,获取容器中的对象
      //1.使用 ApplicationContext 接口,就是在获取 spring 容器   
      ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");   
      //2.根据 bean 的 id 获取对象   
      IAccountService aService = (IAccountService) ac.getBean("accountService"); 
      
    • Spring基于 XML 的 IOC 细节

      image-20200601172657256
      • ApplicationContext 接口的实现类
      ClassPathXmlApplicationContext:  它是从类的根路径下加载配置文件  (推荐) FileSystemXmlApplicationContext: 它是从磁盘路径上加载配置文件,配置文件可以在磁盘的任意位置。 AnnotationConfigApplicationContext: 使用注解配置容器对象时,需要使用此类来创建 spring 容器。它用来读取注解。 
      
      • BeanFactory和 ApplicationContext 的区别

        BeanFactory 才是 Spring 容器中的顶层接口。 ApplicationContext 是它的子接口。 
        BeanFactory 和 ApplicationContext 的区别:  
        创建对象的时间点不一样。   
        ApplicationContext:只要一读取配置文件,默认情况下就会创建对象。(创建之后就不会再创建,适用于单例,不过它是智能的,可以通过具体情况改变)   
        BeanFactory:什么使用什么时候创建对象。(适用于多例)
        
    • IOC 中 bean 标签和管理对象细节

      • 实例化 Bean 的三种方式

        第一种方式:使用**默认无参构造函数**  
        <!--在默认情况下:它会根据默认无参构造函数来创建类对象。如果 bean 中没有默认无参构造函数,将会创建失败。  --> 
        <bean id="accountService" class="com.itheima.service.impl.AccountServiceImpl"/>
        
        第二种方式:spring管理静态工厂-使用**静态**工厂的方法创建对象 
            /** 
             * 模拟一个静态工厂,创建业务层实现类  
             */ 
        public class StaticFactory {   
            public static IAccountService createAccountService(){   
                return new AccountServiceImpl();  } } 
        <!-- 使用 StaticFactory 类中的静态方法 createAccountService 创建对象,并存入 spring 容器   
            id 属性:指定 bean 的 id,用于从容器中获取的唯一标识   
            class 属性:指定静态工厂的全限定类名   
            factory-method 属性:指定生产对象的静态方法  --> 
        <bean id="accountService"  
           class="com.itheima.factory.StaticFactory"     factory-method="createAccountService"></bean>
        
        第三种方式:spring管理实例工厂-使用**实例**工厂的方法创建对象 
        /** 
         * 模拟一个实例工厂,创建业务层实现类  
         * 此工厂创建对象,必须现有工厂实例对象,再调用方法  */ 
        public class InstanceFactory {   
            public IAccountService createAccountService(){   
                return new AccountServiceImpl();  } } 
         <!--先把工厂的创建交给 spring 来管理。 然后再使用工厂的 bean 来调用里面的方法   
        factory-bean 属性:用于指定实例工厂 bean 的 id。   
        factory-method 属性:用于指定实例工厂中创建对象的方法。  --> 
        <bean id="instancFactory" class="com.itheima.factory.InstanceFactory"></bean>  
        <bean id="accountService"  factory-bean="instancFactory" 
              factory-method="createAccountService"></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:指定类中销毁方法名称。 
        
    • spring 的依赖注入

      • 概念:依赖关系交给spring来维护,框架把持久层对象传入业务层,而不用我们自己去获取

      • 构造函数注入

        //有参构造函数
        public AccountServiceImpl(String name, Integer age, Date birthday) {   
        this.name = name;   this.age = age;   this.birthday = birthday;  } 
        
        <!-- 使用构造函数的方式,给 service 中的属性传值  
        要求:   类中需要提供一个对应参数列表的构造函数。  
        涉及的标签:   constructor-arg    
        属性:     
        index:指定参数在构造函数参数列表的索引位置 从0开始    
        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> 
        
      • set 方法注入 (常用)

        只需要成员的set方法,不需要有参构造函数
        <!-- 通过配置文件给 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>
        
      • 注入集合属性(常用)

        使用set注入的方式
        <!-- 在注入集合数据时,只要结构相同,标签可以互换   
        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>
        

基于注解的 IOC 配置

常用注解:

  • 用于创建bean对象的

  • @Component

    作用:  把资源让 spring 来管理。相当于在 xml 中配置一个 bean。 
    属性:  value:指定 bean 的 id。如果不指定 value 属性,默认 bean 的 id 是当前类的类名。首字母小写。
    
  • @Controller @Service @Repository

作用和Component一摸一样,更加明确的语义化。  
@Controller:一般用于表现层的注解。  
@Service:一般用于业务层的注解。 
@Repository:一般用于持久层的注解。 
细节:如果注解中有且只有一个属性要赋值时,且名称是 value时可以不写。 
  • 用于注入数据的

  • @Autowired

    作用:  自动按照类型注入。    当使用注解注入属性时,set方法可以省略。
    它只能注入bean 类型。
    当有多个相同类型时,使用变量名称作为 bean 的 id,在spring容器查找,找到了也可以注入成功。找不到就报错
    
  • @Qualifier

    作用:  在自动按照类型注入的基础之上,再按照 Bean 的 id 注入。
    它在给字段注入时不能独立使用,必须和 @Autowire 一起使用;但是给方法参数注入时,可以独立使用。 
    属性:  value:指定 bean 的 id。 
    
  • @Resource

作用:  直接按照 Bean 的 id 注入。也只能注入bean 类型。 
属性:  name:指定 bean 的 id。
  • @Value

    作用:  注入基本数据类型和 String 类型数据的 
    属性:  value:用于指定值
    
  • 用于改变作用范围(单例、多例)的

  • @Scope

作用:  指定 bean 的作用范围。 
属性:  value:指定范围的值。      
取值:singleton(单)  prototype(多)                         request session globalsession 
  • 和生命周期相关的

  • @PostConstruct

    作用:  用于指定初始化方法。 
    
  • @PreDestroy

    作用:  用于指定销毁方法。 
    

环境搭建

  • 导入spring-aop依赖

  • 使用@Component 注解配置管理的资源

    @Component("accountDao") 
    public class AccountDaoImpl  implements IAccountDao { 
     private DBAssit dbAssit; } 
    
  • 创建 spring 的 xml配置文件并开启对注解的支持

    <?xml version="1.0" encoding="UTF-8"?> 
    <beans xmlns="http://www.springframework.org/schema/beans"  xmlns:context="http://www.springframework.org/schema/context" 
        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 
            http://www.springframework.org/schema/context         http://www.springframework.org/schema/context/spring-context.xsd"> 
        <!-- 告知 spring 创建容器时要扫描有注解的的包 -->  
        <context:component-scan base-package="com.itheima"></context:component-scan> 
    </beans> 
    

关于 Spring 注解和 XML的选择问题

  • 注解的优势: 配置简单,维护方便(我们找到类,就相当于找到了对应的配置)。

  • XML 的优势: 修改时,不用改源码。不涉及重新编译和部署

  • Spring 管理 Bean方式的比较:

    image-20200602220049920

基于XML的IOC案例

  • 需求:实现单表的CRUD操作

  • 技术要求:使用 spring 的 IoC 实现对象的管理 ,使用 DBUtils 作为持久层解决方案,使用 c3p0 数据源

    1. 环境搭建
    <dependencies>
            <dependency>
                <groupId>org.springframework</groupId>
                <artifactId>spring-context</artifactId>
                <version>5.0.2.RELEASE</version>
            </dependency>
            <dependency>
                <groupId>mysql</groupId>
                <artifactId>mysql-connector-java</artifactId>
                <version>5.1.6</version>
            </dependency>
            <dependency>
                <groupId>commons-dbutils</groupId>
                <artifactId>commons-dbutils</artifactId>
                <version>1.4</version>
            </dependency>
            <dependency>
                <groupId>c3p0</groupId>
                <artifactId>c3p0</artifactId>
                <version>0.9.1.2</version>
            </dependency>
            <dependency>
                <groupId>junit</groupId>
                <artifactId>junit</artifactId>
                <version>4.10</version>
                <scope>test</scope>
            </dependency>
        </dependencies>
    
    1. 创建数据库,根据数据库创建bean类

    2. 编写业务层代码

     接口:
     /*
        保存账户
         */
        void saveAccount();
        /*
        查询所有
         */
        List<Account> findAll();
        /*
        通过Id查询单个
         */
        Account findAccountById(int id);
        /*
        通过id修改账户信息
         */
        void updateAccount(Account account);
        /*
        通过id删除账户信息
         */
        void deleteAccount(int id);
        
    实现类:
        private IAccountDao accountDao;
        //xml配置需要Set方法,注解不需要
        public void setAccountDao(IAccountDao accountDao) {
            this.accountDao = accountDao;
        }
    
    1. 编写持久层代码
        //创建dbUtils对象
        private QueryRunner runner;
    
        public void setRunner(QueryRunner runner) {
            this.runner = runner;
        }
    
        @Override
        public List<Account> findAll() {
            try {
                String sql ="select * from account";
                return runner.query(sql,new BeanListHandler<Account>(Account.class));
            } catch (SQLException e) {
                throw new RuntimeException();
            }
        }
    
        @Override
        public Account findAccountById(int id) {
            try {
                String sql ="select * from account where id = ? ";
                return runner.query(sql,new BeanHandler<Account>(Account.class),id);
            } catch (SQLException e) {
                throw new RuntimeException();
            }
        }
    
        @Override
        public void updateAccount(Account account) {
            try {
                String sql ="update account set name = ?,money = ? where id = ? ";
                runner.update(sql,account.getName(),account.getMoney(),account.getId());
            } catch (SQLException e) {
                throw new RuntimeException();
            }
        }
    
    1. 创建并编写配置文件bean.xml
        <!--配置service-->
    <bean id="accountService" class="cn.hm.service.impl.AccountServiceImpl">
        <!--注入dao-->
        <property name="accountDao" ref="accountDao"></property>
    </bean>
        <!--配置dao对象-->
        <bean id="accountDao" class="cn.hm.dao.impl.AccountDaoImpl">
            <!--注入QueryRunner-->
            <property name="runner" ref="runner"></property>
        </bean>
        <!--配置QueryRunner对象-->
    <bean id="runner" class="org.apache.commons.dbutils.QueryRunner">
        <!--注入数据源-->
        <constructor-arg name="ds" ref="dataSource"></constructor-arg>
    </bean>
        <!--配置数据源c3p0-->
        <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
            <!--连接数据库必备信息-->
            <property name="driverClass" value="com.mysql.jdbc.Driver"></property>
            <property name="jdbcUrl" value="jdbc:mysql://localhost:3306/spring"></property>
            <property name="user" value="root"></property>
            <property name="password" value="1234"></property>
        </bean>
    
    1. 编写测试类
     @Test
        public void testFindAll(){
            //获得spring容器
            ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
            //根据bean的id获取对象
            IAccountService accountService = (IAccountService) ac.getBean("accountService");
            List<Account> accounts = accountService.findAll();
            for (Account account:accounts) {
                System.out.println(account);
            }
        }
    

基于注解的IOC案例

  1. 环境搭建:当使用Resource注解时,需要加上此依赖

    <dependency>
        <groupId>javax.annotation</groupId>
        <artifactId>javax.annotation-api</artifactId>
        <version>1.3.2</version>
    </dependency>
    
  2. 创建数据库,根据数据库创建bean类

  3. 编写业务层代码

    @Service("accountService")//可省略值,那么就是类名,首字母小写
    public class AccountServiceImpl implements IAccountService {
        @Resource(name="accountDao")//这里是dao的id
        private IAccountDao accountDao;
    
  4. 编写持久层代码

    @Repository("accountDao")
    public class AccountDaoImpl implements IAccountDao {
        @Resource(name = "runner")//这里是xml中的bean id
        private QueryRunner runner;
    
  5. 创建并编写配置文件bean.xml

    <!--告知spring在创建容器时要扫描的包-->
     <context:component-scan base-package="com.itheima"></context:component-scan> 
    <!--配置QueryRunner对象-->
    <bean id="runner" class="org.apache.commons.dbutils.QueryRunner">
        <!--注入数据源-->
        <constructor-arg name="ds" ref="dataSource"></constructor-arg>
    </bean>
        <!--配置数据源c3p0-->
        <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
            <!--连接数据库必备信息-->
            <property name="driverClass" value="com.mysql.jdbc.Driver"></property>
            <property name="jdbcUrl" value="jdbc:mysql://localhost:3306/spring"></property>
            <property name="user" value="root"></property>
            <property name="password" value="1234"></property>
        </bean>
    
  6. 编写测试类


纯注解形式,将xml变为注解

  • 待改造的问题

    <context:component-scan base-package="com.itheima"></context:component-scan> 
     
    <!-- 配置 dbAssit -->  
    <bean id="dbAssit" class="com.itheima.dbassit.DBAssit"> 
      <property name="dataSource" ref="dataSource"></property>  </bean>   
     <!-- 配置数据源 -->  
    <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource"> 
      <property name="driverClass" value="com.mysql.jdbc.Driver"></property>   
        <property name="jdbcUrl" value="jdbc:mysql:///spring_day02"></property>   
        <property name="user" value="root"></property>   
        <property name="password" value="1234"></property> 
        </bean> 
    
  • 新注解

    • @Configuration (声明配置类)

      作用:  用于指定当前类是一个 spring 配置类
      获取容器时需要使用 AnnotationApplicationContext(有@Configuration 注解的类.class)。 
      属性:  value:用于指定配置类的字节码 
      
    • @ComponentScan(指定扫描的包)

      作用:  用于指定 spring 在初始化容器时要扫描的包。
      作用和在 spring 的 xml 配置文件中的: <context:component-scan base-package="com.itheima"/>一样。 
      属性:  basePackages/value:用于指定要扫描的包。
      
    • @Bean(将对象放入容器)

      作用:  该注解只能写在方法上,将方法返回值放入 spring 容器中。 
      属性:  name:给当前@Bean 注解方法创建的对象指定一个名称(即 bean 的 id)。
      
    • @Import(引入子配置类)

      作用:  用于父配置类导入子配置类,在引入其他配置类时,可以不用再写@Configuration 注解。写上也没问题。 
      属性:  value[]:用于指定其他配置类的字节码。
      
    • @PropertySource(引入配置文件)

      作用:     用于加载.properties 文件中的配置
      属性:  value[]:用于指定 properties 文件位置。如果是在类路径下,需要写上 classpath: 
      例如:@PropertySource ("classpath:jdbcConfig.properties"),位置在父配置类中
      
      public class JdbcConfig {  
          @Value("${jdbc.driver}")  
          private String driver;  
          @Value("${jdbc.url}")  
          private String url;  
          @Value("${jdbc.username}")  
          private String username;  
          @Value("${jdbc.password}")  
          private String password; 
       
       /** 
        * 创建一个数据源,并存入 spring 容器中  
        */  
          @Bean(name="dataSource") 
          public DataSource createDataSource() {   
              try {    
                  ComboPooledDataSource ds = new ComboPooledDataSource();                                       ds.setDriverClass(driver);    
                  ds.setJdbcUrl(url);    
                  ds.setUser(username);    
                  ds.setPassword(password);   
                  return ds;   } 
      

      注意:

      使用纯注解,测试的时候需要使用

      ApplicationContext ac = new AnnotationConfigApplicationContext(springConfiguration.class);

      来获取spring容器


Spring 整合 Junit(需要4.12版本)

  • 思路分析:junit 无法知晓我们是否使用了 spring 框架,更不用说帮我们创建 spring 容器了。不过我们替换掉junit的运行器。 这时,我们需要依靠 spring 框架,因为它提供了一个运行器,可以读取配置文件(或注解)来创建容器。我 们只需要告诉它配置文件在哪就行了。

    1. 导入依赖
    <dependency>
        <groupId>org.springframework</groupId>
      <artifactId>spring-test</artifactId>
        <version>5.0.2.RELEASE</version>
    </dependency>
    
    1. 使用@RunWith 注解替换原有运行器
      @RunWith(SpringJUnit4ClassRunner.class) 
    public class AccountServiceTest { } 
    
    1. 使用@ContextConfiguration 指定 spring 配置文件的位置
      @ContextConfiguration 注解:  
    locations 属性:用于指定xml配置文件的位置。如果是类路径下,需要用 classpath:表明  
      classes 属性:用于指定注解的类。
    例如:@ContextConfiguration(locations= {"classpath:bean.xml"})
    
    1. 使用@Autowired 给测试类中的变量注入数据
      @Autowired 
      private IAccountService as ; 
    

AOP(面向切面编程)

学习 spring 中的 AOP 要明确的事

a、开发阶段(我们做的)  
编写核心业务代码(开发主线):要求熟悉业务需求。  
把公用代码抽取出来,制作成通知。(开发阶段最后再做) 
在配置文件中,声明切入点与通知间的关系,即切面。 
b、运行阶段(Spring框架完成的)  
Spring 框架监控切入点方法的执行。一旦监控到切入点方法被运行,使用代理机制,动态创建目标对象的代理对象,根据通知类别,在代理对象的对应位置,将通知对应的功能织入,完成完整的代码逻辑运行。

AOP 相关术语

Joinpoint(连接点):   所谓连接点是指那些被拦截到的点。在 spring 中,这些点指的是方法,因为 spring 只支持方法类型的 连接点。 
Pointcut(切入点):   所谓切入点是指我们要对哪些 Joinpoint 进行拦截的定义。 
Advice(通知/增强):   所谓通知是指拦截到 Joinpoint 之后所要做的事情就是通知。   
                   通知的类型:前置通知,后置通知,异常通知,最终通知,环绕通知。 
Introduction(引介):   引介是一种特殊的通知在不修改类代码的前提下, Introduction 可以在运行期为类动态地添加一些方法或 Field。 
Target(目标对象):   代理的目标对象。 
Weaving(织入):   是指把增强应用到目标对象来创建新的代理对象的过程。   
                spring 采用动态代理织入,而 AspectJ 采用编译期织入和类装载期织入。 
Proxy(代理):   一个类被 AOP 织入增强后,就产生一个结果代理类。 
Aspect(切面):   是切入点和通知(引介)的结合。 

基于 XML 的 AOP 配置

  1. 导入依赖

    springaop5.0.2
    springaspects
    
  2. 创建 spring 的配置文件并导入约束

    aop的约束
    <?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>
    
  3. 配置 spring 的 ioc

        <!-- 配置 service --> 
    <bean id="accountService" class="com.itheima.service.impl.AccountServiceImpl">  </bean>
    
  4. 把通知类配置进容器中

    <!-- 配置通知 --> 
     <bean id="txManager" class="com.itheima.utils.TransactionManager">  </bean>
    
  5. 使用 aop:config 声明 aop 配置

    作用:用于声明开始 aop 的配置 
    <aop:config> <!-- 配置的代码都写在此处 -->  </aop:config> 
    
  6. 使用 aop:aspect 配置切面

    作用:   用于配置切面。  
    属性:   
        id:给切面提供一个唯一标识。   
        ref:引用配置好的通知类 bean 的 id。 
    <aop:aspect id="txAdvice" ref="txManager">   <!--配置通知的类型要写在此处--> </aop:aspect>
    
  7. 使用 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"/> 
    
    切入点表达式说明 :
        表达式语法:execution([修饰符] 返回值类型 包名.类名.方法名(参数)) 
        访问修饰符可以省略,
        全通配方式:                       *     *..  *  .  *  (..)
        使用..来表示当前包,及其子包 
        
        注:  通常情况下,我们都是对业务层的方法进行增强,所以切入点表达式都是切到业务层实现类。                  execution(* com.itheima.service.impl.*.*(..))    
    
  8. 使用 aop:xxx 配置对应的通知类型 (写在aop:aspect里)

     作用:   用于配置通知。指定增强的方法在切入点方法之前执行   
     属性:   method:用于指定通知类中的增强方法名称   ponitcut-ref:用于指定切入点的表达式的引用   
             poinitcut:用于指定切入点表达式,expression(public void                                     com.itheima.service.impl.AccountServiceImpl.transfer())
     执行时间点:   
        aop:before  前置通知,切入点方法执行之前执行 
        aop:after-returning 后置通知,切入点方法正常执行之后。它和异常通知只能有一个执行 
        aop:after-throwing 异常通知,切入点方法执行产生异常后执行。它和后置通知只能执行一个 
        aop:after  用于配置最终通知,无论切入点方法执行时是否有异常,它都会在其后面执行。 
        
     <aop:before method="beginTransaction" pointcut-ref="pt1"/> 
    
  • 环绕通知

    <!-- 配置环绕通知 -->   
    <aop:around method="transactionAround" pointcut-ref="pt1"/> 
    说明:   它是 spring 框架为我们提供的一种可以在代码中手动控制增强代码什么时候执行的方式。 
    注意:   通常情况下,环绕通知都是独立使用的 
        spring 框架为我们提供了一个接口:ProceedingJoinPoint,它可以作为环绕通知的方法参数。  
        在环绕通知执行时,spring 框架会为我们提供该接口的实现类对象,我们直接使用就行
    public Object transactionAround(ProceedingJoinPoint pjp) { 
         //定义返回值  
        Object rtValue = null;  
    try {   
        //获取方法执行所需的参数   
        Object[] args = pjp.getArgs(); 
          //前置通知:开启事务   
        beginTransaction(); 
          //执行方法  
        rtValue = pjp.proceed(args); 
          //后置通知:提交事务   
        commit();  
        }catch(Throwable e) {  
        //异常通知:回滚事务   
        rollback();   
        e.printStackTrace();  
        }finally { 
          //最终通知:释放资源   
        release();  
        }  return rtValue; 
    } 
    
  • 例子

    <!-- 声明一个业务类 -->
    <bean id="userManager" class="com.spring.service.impl.UserManagerServiceImpl">
        <property name="name" value="lixiaoxi"></property>
    </bean>  
    
    <!-- 声明通知类 -->
    <bean id="aspectBean" class="com.spring.aop.AopAspect" />
    
    <aop:config>
        <aop:aspect ref="aspectBean">
            <aop:pointcut id="pointcut" expression="execution(* com.spring.service.impl.UserManagerServiceImpl..*(..))"/>
    
            <aop:before method="doBefore" pointcut-ref="pointcut"/> 
            <aop:after-returning method="doAfterReturning" pointcut-ref="pointcut" returning="result"/>
            <aop:after method="doAfter" pointcut-ref="pointcut" /> 
            <aop:around method="doAround" pointcut-ref="pointcut"/> 
            <aop:after-throwing method="doAfterThrowing" pointcut-ref="pointcut" throwing="ex"/>
        </aop:aspect>
    </aop:config>
    

基于注解的 AOP 配置

三大步:

  • 将业务逻辑组件和切面类都加入到容器中,并告知spring容器哪个是切面类(@Aspect)

  • 在切面类上的每一个通知方法上标注上通知注解,告诉Spring何时何地运行(切入点表达式)

  • 开启基于注解的AOP模式

    1. 配置文件中导入 spring配置文件 的名称空间
    <?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" 
     xmlns:aop="http://www.springframework.org/schema/aop"  xmlns:context="http://www.springframework.org/schema/context"     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         http://www.springframework.org/schema/aop         http://www.springframework.org/schema/aop/spring-aop.xsd 
            http://www.springframework.org/schema/context         http://www.springframework.org/schema/context/spring-context.xsd">
    
    1. 把资源使用注解配置
    @Service("accountService") 
    public class AccountServiceImpl implements IAccountService { 
    
    1. 指定 spring 要扫描的包
    <!-- 告知 spring,在创建容器时要扫描的包 --> 
    <context:component-scan base-package="com.itheima"></context:component-scan>
    或
    @ComponentScan(指定扫描的包) 
    
    1. 把通知类也使用注解配置
    @Component("txManager") 
    
    1. 在通知类上使用@Aspect 注解声明为切面
    @Aspect//表明当前类是一个切面类
    
    1. 在增强的方法上使用注解配置通知
    @Before
    @AfterReturning(value="",returning="result"),方法参数可以加一个Object result,用于获取返回值
    @AfterThrowing(value="",throwing="exception"),方法参数可以加一个Exception exception,用于获取异常信息
    @After 
    
    @Before("execution(* com.itheima.service.impl..(..))") 
    
    方法的参数可以加JoinPoint,且必须在参数的第一位
    joinponit.getSignature().getName()可以获取到被切方法的名字
    joinponit.getArgs()可以获取被切方法的参数列表
    
    1. **在 spring 配置文件中开启 spring 对注解 AOP 的支持 **
    <aop:aspectj-autoproxy/>
      或纯注解方式
      @EnableAspectJAutoProxy
      public class SpringConfiguration { }
    
    1. 切入点表达式注解
      @Pointcut("execution(* com.itheima.service.impl..(..))") 
      private void pt1() {} 
    
      @Around("pt1()")//注意:千万别忘了写括号  
      public Object printLog() {}
    

    注意:

    进行测试的时候,业务逻辑方法对象应该是从容器中获取,如果是自己new出来,不会有aop增强的效果

    • 环绕通知注解配置
    @Around("execution(* com.itheima.service.impl.*.*(..))")  public Object transactionAround(ProceedingJoinPoint pjp) { 
      //定义返回值   
        Object rtValue = null;   
        try { 
       //获取方法执行所需的参数    
            Object[] args = pjp.getArgs(); 
       //前置通知:开启事务    
            beginTransaction(); 
       //执行方法    
            rtValue = pjp.proceed(args); 
       //后置通知:提交事务    
            commit();   
        }catch(Throwable e) { 
       //异常通知:回滚事务    
            rollback();    
            e.printStackTrace();   
        }finally {    
            //最终通知:释放资源    
            release();   
        }   
        return rtValue; 
    }
    

JDBCTemplate

JDBCTemplate和DBUtils对比:

  • 最重要的一点:jdbcTemplate的query方法是看不同query的返回值:query(查所有、查一个)、queryForObject(查一行一列),而参数都是new BeanProperty<>() DBUtils的query方法只有query,是看query中不同参数new BeanHandler的返回值:BeanHandler、 BeanListHandler、BeanObjectHandler
查一个
List<User> users = jdbcTemplate.query("select * from user where id = ? ",new BeanPropertyRowMapper<User>(User.class),1);
//判断list是否为空,空则返回null,不为空则返回该信息,因为是一个信息,所有get(0)是该信息
return users.isEmpty()?null:users.get(0);

使用方法

  • 导入依赖

     spring-jdbc-5.0.2.RELEASE.jar 
     spring-tx-5.0.2.RELEASE.jar
    
  • 配置数据源

    • C3P0

      <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource"> 
          <property name="driverClass" value="com.mysql.jdbc.Driver"></property>  
          <property name="jdbcUrl" value="jdbc:mysql:///spring_day02"></property> 
           <property name="user" value="root"></property> 
           <property name="password" value="1234"></property> 
      </bean>
      
    • 配置 spring 内置数据源

      <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource"> 
          <property name="driverClassName" value="com.mysql.jdbc.Driver"></property> 
          <property name="url" value="jdbc:mysql:///spring_day02"></property>  
          <property name="username" value="root"></property> 
          <property name="password" value="1234"></property> 
      </bean>
      
  • 将数据库连接的信息配置到属性文件中:

    引入外部的属性文件
    <context:property-placeholder location="classpath:jdbc.properties"/> 
    
  • JdbcTemplate 的增删改查操作

<!-- 配置一个数据库的操作模板:JdbcTemplate -->  
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">   
    <property name="dataSource" ref="dataSource"></property>  
</bean>
  • 第一种方式(所有情况都适用):在 dao 中定义 JdbcTemplate

    在 Dao类中定义 JdbcTemplate 的方式,适用于所有配置方式(xml和注解都可以)。 
    
    public class AccountDaoImpl implements IAccountDao {
    private JdbcTemplate jdbcTemplate;    
    public void setJdbcTemplate(JdbcTemplate jdbcTemplate) {   this.jdbcTemplate = jdbcTemplate;  }
    
    xml:
    <!-- 配置一个 dao -->  
    <bean id="accountDao" class="com.itheima.dao.impl.AccountDaoImpl"> 
          <!-- 注入 jdbcTemplate -->   
          <property name="jdbcTemplate" ref="jdbcTemplate"></property> 
    </bean>
    
  • 第二种方式(纯注解):在配置类上加JDBCTemplate组件

    //数据源
    @Bean
    public DataSource dateSource() throws Exception{
        ComboPoolDataSource dataSource = new ComboPoolDataSource();
        dataSource.setUser("root");
        dataSource.setPassword("123456");
        dataSource.setDriverClass("com.mysql.jdbc.Driver");
        dataSource.setJdbcUrl("jdbc:mysql://localhost:3306/test");
        return dataSource;
    }
    //JdbcTemplate
    @Bean
    public JdbcTemplate jdbcTemplate() throws Exception{
        jdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource());
        return jdbcTemplate;
    }
    
  • 第三种方式(注解不适用):让 dao 继承 JdbcDaoSupport

    让 Dao继承 JdbcDaoSupport 的方式,只能用于基于 XML 的方式,注解用不了。 
    
    public class AccountDaoImpl2 extends JdbcDaoSupport implements IAccountDao { 
     
     @Override  public Account findAccountById(Integer id) {   
         //getJdbcTemplate()方法是从父类上继承下来的。   
         List<Account> list = getJdbcTemplate().query("select * from account where id = ? ",new AccountRowMapper(),id); 
         
    xml:
    <!-- 配置 dao2 --> 
    <bean id="accountDao2" class="com.itheima.dao.impl.AccountDaoImpl2"> 
        <!-- 注入 dataSource -->  
        <property name="dataSource" ref="dataSource"></property> 
    </bean>
    

Spring中的事务控制

  • 写在最前:Spring 提供了分层设计业务层的事务处理解决方案,spring 的事务控制都是基于 AOP 的

  • Spring中事务控制的 API介绍:

  • PlatformTransactionManager:此接口是 spring 的事务管理器

  • 它的实现类:

    org.springframework.jdbc.datasource.DataSourceTransactionManager 
    使用 Spring JDBC 或 iBatis 进行持久化数据时使用 
    
    org.springframework.orm.hibernate5.HibernateTransactionManager  
    使用 Hibernate 版本进行持久化数据时使用
    

基于 XML 的声明式事务控制(配置方式)

  • 导入依赖

    spring-tx-5.0.2
    spring-jdbc-5.0.2
    
  • 创建 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"      xmlns:aop="http://www.springframework.org/schema/aop"      xmlns:tx="http://www.springframework.org/schema/tx"      xsi:schemaLocation="http://www.springframework.org/schema/beans         http://www.springframework.org/schema/beans/spring-beans.xsd 
           http://www.springframework.org/schema/tx         http://www.springframework.org/schema/tx/spring-tx.xsd           http://www.springframework.org/schema/aop  
              http://www.springframework.org/schema/aop/spring-aop.xsd"> 
    </beans>
    
  • 准备数据库表和实体类

  • 编写业务层接口和实现类

  • 编写 Dao 接口和实现类

  • 在配置文件中配置业务层和持久层

    <!-- 配置 service --> 
    <bean id="accountService" class="com.itheima.service.impl.AccountServiceImpl">  
        <property name="accountDao" ref="accountDao"></property> 
    </bean>   
    <!-- 配置 dao --> 
    <bean id="accountDao" class="com.itheima.dao.impl.AccountDaoImpl"> 
         <!-- 注入 dataSource 这里使用的是继承的方法,不需要配置JdbcTemplate,不适用于注解-->  
        <property name="dataSource" ref="dataSource"></property> 
    </bean>   
    <!-- 配置数据源 --> 
    <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">         <property name="driverClassName" value="com.mysql.jdbc.Driver"></property>  
        <property name="url" value="jdbc:mysql:///spring_day04"></property>  
        <property name="username" value="root"></property>  
        <property name="password" value="1234"></property> 
    </bean> 
    
  • 事务配置:配置事务管理器

    <!-- 配置一个事务管理器 --> 
    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> 
        <!-- 注入 DataSource -->  
        <property name="dataSource" ref="dataSource"></property> 
    </bean>
    
  • 配置事务的通知,引用事务管理器

    <!-- 事务的配置 
    transaction-manager:引用事务管理器bean的id
    --> 
    <tx:advice id="txAdvice" transaction-manager="transactionManager">
    
    </tx:advice>
    
  • 配置事务的属性

    <!--在 tx:advice 标签内部 配置事务的属性 -->
    <tx:attributes> 
    <!-- 指定方法名称:是业务核心方法   
    *read-only:是否是只读事务。默认 false(适用于增删改),不只读(适用于查询)。  
    isolation:指定事务的隔离级别。默认值是使用数据库的默认隔离级别。  
    *propagation:指定事务的传播行为,默认REQUIRED(创建事务,适用于增删改),SUPPORTS(适用于查询)。  
    timeout:指定超时时间。默认值为:-1。永不超时。  
    rollback-for:用于指定一个异常,当执行产生该异常时,事务回滚。产生其他异常,事务不回滚。 没有默认值,任何异常都回滚。  
    no-rollback-for:用于指定一个异常,当产生该异常时,事务不回滚,产生其他异常时,事务回 滚。没有默认值,任何异常都回滚。  -->  
    <!--name可以用*占位符-->
        <tx:method name="*" read-only="false" propagation="REQUIRED"/>  
        <tx:method name="find*" read-only="true" propagation="SUPPORTS"/> 
    </tx:attributes>
    
  • 配置 AOP 切入点表达式

    <!-- 配置 aop --> 
    <aop:config> 
         <!-- 配置切入点表达式 -->  
        <aop:pointcut expression="execution(* com.itheima.service.impl.*.*(..))" id="pt1"/> 
    </aop:config>
    
  • 配置切入点表达式和事务通知的对应关系

    <!-- 在 aop:config标签内部 
    advice-ref:事务配置的id
    pointcut-ref:切入点表达式的id
    --> 
    <aop:advisor advice-ref="txAdvice" pointcut-ref="pt1"/> 
    

基于注解的配置方式

三大步:

  • 开启事务管理功能@EnableTransactionManagement
  • 给方法标注上@Transactional ,表示当前方法是一个由事务接管的方法
  • 配置事务管理器来控制事务
  • 导入依赖

    aop
    aspectJ(解析切入点表达式)
    
  • 创建 spring 的配置文件导入约束并配置扫描的包 (纯注解请参考JDBCTemplate第二种方式配置数据源和jdbcTemplate)

  <?xml version="1.0" encoding="UTF-8"?> 
  <beans xmlns="http://www.springframework.org/schema/beans"     xmlns:aop="http://www.springframework.org/schema/aop"     xmlns:tx="http://www.springframework.org/schema/tx"     xmlns:context="http://www.springframework.org/schema/context"        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 
           http://www.springframework.org/schema/aop          http://www.springframework.org/schema/aop/spring-aop.xsd          http://www.springframework.org/schema/tx           http://www.springframework.org/schema/tx/spring-tx.xsd          http://www.springframework.org/schema/context         http://www.springframework.org/schema/context/spring-context.xsd"> 
       <!-- 配置 spring 创建容器时要扫描的包 -->  
      <context:component-scan base-package="com.itheima"></context:component-scan> 
  
       <!-- 配置 JdbcTemplate 采用非继承形式,适用于所有情况--> 
      <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate"> 
          <property name="dataSource" ref="dataSource"></property>  
      </bean> 
  
      <!-- 配置 spring 提供的内置数据源 -->  
      <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">         <property name="driverClassName" value="com.mysql.jdbc.Driver"></property>   
        <property name="url" value="jdbc:mysql://localhost:3306/spring_day02"></property>           <property name="username" value="root"></property>   
          <property name="password" value="1234"></property>  
    </bean>  
  </beans> 
  • 创建数据库表和实体类

  • 创建业务层接口和实现类并使用注解让 spring 管理

    @Service("accountService") 
    public class AccountServiceImpl implements IAccountService {  
      @Autowired  
        private IAccountDao accountDao; 
    //其余代码和基于 XML 的配置相同 
    } 
    
  • 创建 Dao 接口和实现类并使用注解让 spring 管理

    @Repository("accountDao") 
    public class AccountDaoImpl implements IAccountDao { 
       @Autowired 
         private JdbcTemplate jdbcTemplate;   
    //其余代码和基于 XML 的配置相同 
    }
    
  • 事务配置:配置事务管理器并注入数据源

     在配置类上写
     @Bean
    public PlatformTransactionManager transactionManager() throws Exception{
         return new DataSourceTransactionManager(dataSource());
    }
    
    • 在业务层使用@Transactional 注解配置事务通知

      ``` java
      //创建bean
      @Service(“accountService”)
      //配置事务通知,并配置事务属性
      @Transactional(readOnly=true,propagation=Propagation.SUPPORTS)
      public class AccountServiceImpl implements IAccountService {

      //转账
      @Override  
      @Transactional(readOnly=false,propagation=Propagation.REQUIRED) 
      public void transfer(String sourceName, String targeName, Float money) { 
      

      /* @Transactional

      该注解的属性和 xml 中的属性含义一致。
      该注解可以出现在接口上,类上和方法上。 
      出现接口上,表示该接口的所有实现类都有事务支持。 
      

      出现在类上,表示类中所有方法有事务支持
      出现在方法上,表示方法有事务支持。
      以上三个位置的优先级:方法>类>接口

    • /
      ```

    • 开启 spring 对注解事务的支持

      <!-- 开启 spring 对注解事务的支持 --> 
      <tx:annotation-driven transaction-manager="transactionManager"/> 
      
      或纯注解方式   @EnableTransactionManagement 
      
      @Configuration 
      @EnableTransactionManagement 
      public class SpringTxConfiguration { 
       //里面配置数据源,配置 JdbcTemplate,配置事务管理器。 
      } 
      

文章作者: 小苏
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 小苏 !
评论
  目录