Spring Framework

相关知识点

  1. 概念:spring是开源的轻量级框架

  2. 核心:aop:面向切面编程、ioc:控制反转

  3. spring是一站式框架,在javaee三层结构中,每一层都提供不同的解决技术

    1. web层:springMVC

    2. service层:spring的ioc

    3. dao层:spring的jdbcTemplate

IOC

  1. 全称:Inversion of Control (控制反转)

  2. DI:Dependency Injection 依赖注入:需要有 IOC 的环境,Spring 创建这个类的过程中,Spring 将类的依赖的属性设置进去.

  3. 理念:把对象的创建交给spring进行管理

  4. 画图分析ioc实现原理:

     

     

     

入门案例

  1. 导入核心jar包和支持日志输出的jar包

  2. 创建spring配置文件,配置创建类,官方建议命名:官方建议applicationContext.xml

  3. 在xml文件中引入schema约束

  4. 编写相关类

    1 public interface UserDao { 
    2     public void sayHello(); 
    3 }
    4 public class UserDaoImpl implements UserDao { 
    5     @Override 
    6     public void sayHello() {
    7         System.out.println("Hello Spring..."); 
    8     } 
    9 }
  5. 完成配置

    1 <!-- Spring 的入门案例================ --> 
    2 <bean id="userDao" class="cn.itcast.spring.demo1.UserDaoImpl"></bean>
  6. 编写测试代码

     

    1 @Test 
    2 // Spring 的方式: 
    3 public void demo(){ 
    4 // 创建 Spring 的工厂类: 
    5     ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext.xml"); 
    6     // 通过工厂解析 XML 获取 Bean 的实例. 
    7     UserDao userDao = (UserDao) ac.getBean("userDao"); 
    8     userDao.sayHello(); 
    9 }

     

ApplicationContext接口

//该接口的三个实现类
ClassPathXmlApplicationContext :加载类路径下 Spring 的配置文件.
FileSystemXmlApplicationContext :加载本地磁盘下 Spring 的配置文件
AnnotationConfigApplicationContext :当我们使用注解配置容器对象时,需要使用此类来创建 spring 容器。它用来读取注解。

基于XML的 IOC 配置

Spring 的配置文件的开发

//第一种:创建工厂的时候加载多个配置文件: 
ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext.xml","applicationContext2.xml");
//第二种:在一个配置文件中包含另一个配置文件:
<import resource="applicationContext2.xml"></import>

Spring生成bean的三种方式

  1. 有无参构造方法

    <!-- 方式一:无参的构造方法的实例化 --> 
    <bean id="bean1" class="cn.itcast.spring.demo3.Bean1"></bean>
    <!-- 方法里不存在无参的构造方法就会创造失败 -->
  2. 静态工厂实例化的方式

    //提供一个工厂类: 
    public class Bean2Factory {
      public static Bean2 getBean2(){ return new Bean2(); }
    }
           
    <!-- 方式二:xml静态工厂实例化 Bean -->
      <bean id="bean2" class="cn.itcast.spring.demo3.Bean2Factory" factory-method="getBean2"/>
  3. 实例工厂实例化的方式

    //提供 Bean3 的实例工厂: 
    public class Bean3Factory {
       public Bean3 getBean3(){ return new Bean3(); }
    }
    <!-- 方式三:实例工厂实例化 Bean -->
    <bean id="bean3Factory" class="cn.itcast.spring.demo3.Bean3Factory"></bean>
    <bean id="bean3" factory-bean="bean3Factory" factory-method="getBean3"></bean>

Bean标签常用属性

  1. id属性:起名称,id属性值名称任意命名

    1. id属性值,不能包含特殊符号

    2. 根据id值得到配置对象

  2. name属性:功能和id属性一样的,id属性值不能包含特殊符号,但是在name属性值里面可以包含特殊符号

  3. class属性:创建对象所在类的全路径

  4. scope属性:

    1. singleton:默认值,单例

      • 生命周期:从创建容器时,对象就被创建了,直到容器被销毁对象才被销毁

    2. prototype:多例

      • 每次访问对象时,都会重新创建对象实例。对象长时间不用就会被垃圾回收器回收了。

    3. request:创建对象把对象放到request域里面

    4. session:创建对象把对象放到session域里面

    5. globalSession:创建对象把对象放到globalSession里面

  5. init-method:指定类中的初始化方法名称。

  6. destroy-method:指定类中销毁方法名称。

Spring 的 Bean 的属性注入

 1     <!-- 第一种:构造方法的方式 --> 
 2 <bean id="car" class="cn.itcast.spring.demo4.Car"> 
 3     <constructor-arg name="name" value="保时捷"/> 
 4     <constructor-arg name="price" value="1000000"/> 
 5 </bean>
 6 
 7     <!-- 第二种:set 方法的方式 --> 
 8 <bean id="car2" class="cn.itcast.spring.demo4.Car2"> 
 9     <property name="name" value="奇瑞 QQ"/> 
10     <property name="price" value="40000"/> 
11 </bean>
12     <!-- 注入对象类型的属性 --> 
13         <!--
14             value:它能赋的值是基本数据类型和 String 类型
15             ref:它能赋的值是其他 bean 类型,也就是说,必须得是在配置文件中配置过的 bean
16         -->
17 <bean id="person" class="cn.itcast.spring.demo4.Person">
18     <property name="name" value="会希"/> 
19     <!-- ref 属性:引用另一个 bean 的 id 或 name --> 
20     <property name="car2" ref="car2"/> 
21 </bean>
22 
23     <!-- p 名称空间的属性注入的方式 --> 
24         <!-- Spring2.x 版本后提供的方式 -->
25         <!-- 
26             * 普通属性: p:属性名称=”” 
27             * 对象类型属性: p:属性名称-ref=””
28          -->
29 <bean id="car2" class="cn.itcast.spring.demo4.Car2" p:name="宝马7" p:price="12000"/> 
30 <bean id="person" class="cn.itcast.spring.demo4.Person" p:name="思聪" p:car-ref="car2"/>
31 
32     <!-- SpEL (Spring Expression Language) 的方式的属性注入 --> 
33         <!-- Spring3.x 版本后提供的方式 -->
34         <!-- 语法:#{ SpEL } -->
35 <bean id="car2" class="cn.itcast.spring.demo4.Car2"> 
36     <property name="name" value="#{'奔驰'}"/> 
37     <property name="price" value="#{800000}"/> 
38 </bean> 
39 <bean id="person" class="cn.itcast.spring.demo4.Person"> 
40     <property name="name" value="#{'冠希'}"/> 
41     <property name="car2" value="#{car2}"/> 
42 </bean>
43 <!-- Spring 的复杂类型的注入===================== --> 
44 <bean id="collectionBean" class="cn.itcast.spring.demo5.CollectionBean"> 
45     <!-- 数组类型的属性 --> 
46     <!-- 在注入集合数据时,只要结构相同,标签可以互换 -->
47     <property name="arrs"> 
48         <array>
49             <value>会希</value> 
50             <value>冠希</value> 
51             <value>天一</value> 
52         </array> 
53     </property> 
54     <!-- 注入 List 集合的数据 --> 
55     <property name="list"> 
56         <list>
57             <value>芙蓉</value> 
58             <value>如花</value> 
59             <value>凤姐</value> 
60         </list> 
61     </property>
62     <!-- 注入 set 集合的数据 --> 
63     <property name="set"> 
64         <set>
65             <value>AAA</value> 
66             <value>BBB</value> 
67             <value>CCC</value>
68 
69         </set>
70     </property>
71     <!-- 注入 Map 集合 --> 
72     <property name="map"> 
73         <map>
74             <entry key="aaa" value="111"/> 
75             <entry key="bbb" value="222"/> 
76             <entry key="testB"> 
77                 <value>bbb</value>
78             </entry> 
79         </map> 
80     </property> 
81     <!-- Properties 的注入 --> 
82     <property name="properties"> 
83         <props> 
84             <prop key="username">root</prop> 
85             <prop key="password">123</prop>
86         </props> 
87     </property> 
88 </bean>

基于注解的 IOC 配置

入门案例

  1. 在基于注解的配置中,我们还要多拷贝一个 aop 的 jar 包。

  2. 使用@Component (组件)注解配置管理的资源

    • 当我们使用注解注入时,set 方法不用写

  3. 创建 spring 的 xml 配置文件并开启对注解的支持

    <!-- 告知 spring 创建容器时要扫描的包 --> 
    <context:component-scan base-package="com.itheima"></context:component-scan>

常用注解

用于创建对象

相当于:<bean id="" class="">

  • @Component

    • 作用:把资源让 spring 来管理。相当于在 xml 中配置一个 bean。

    • 属性:

      • value:指定 bean 的 id。

        • 如果不指定 value 属性,默认 bean 的 id 是当前类的类名。首字母小写

  • @Controller @Service @Repository

    • 他们三个注解都是针对前一个的衍生注解,他们的作用及属性都是一模一样的。他们只不过是提供了更加明确的语义化。

    • @Controller:一般用于表现层的注解。

    • @Service:一般用于业务层的注解。

    • @Repository:一般用于持久层的注解。

用于注入数据的

相当于:<property name="" ref=""> <property name="" value="">

  • @Autowired

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

  • @Qualifier

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

    • 属性:value:指定 bean 的 id。

  • @Resource

    • 作用:直接按照 Bean 的 id 注入。它也只能注入其他 bean 类型,相当于前两个一起用

    • 属性:name:指定 bean 的 id。

  • @Value

    • 作用:注入基本数据类型和 String 类型数据的

    • 属性:value:用于指定值

    • 使用:用注解给spring容器里的对象的属性添加默认值@Value("${size:3}"

用于改变作用范围的

相当于:<bean id="" class="" scope="">

  • @Scope

    • 作用:指定 bean 的作用范围。

    • 属性:value:指定范围的值。

    • 取值:singleton prototype request session globalsession

和生命周期相关的

相当于:<bean id="" class="" init-method="" destroy-method="" />

  • @PostConstruct

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

  • @PreDestroy

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

总结

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

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

新注解说明

知识点

  1. @Configuration

    • 作用:用于指定当前类是一个 spring 配置类,当创建容器时会从该类上加载注解。获取容器时需要使用 AnnotationApplicationContext(有@Configuration 注解的类.class)。

    • 属性:value:用于指定配置类的字节码

  2. @ComponentScan

    • 作用:用于指定 spring 在初始化容器时要扫描的包。作用和在 spring 的 xml 配置文件中的: <context:component-scan base-package="com.itheima"/>是一样的。

    • 属性:basePackages:用于指定要扫描的包。和该注解中的 value 属性作用一样。

  3. @Bean

    • 作用:该注解只能写在方法上,表明使用此方法创建一个对象,并且放入 spring 容器。

    • 属性:name:给当前@Bean 注解方法创建的对象指定一个名称(即 bean 的 id)

  4. @PropertySource

    • 作用:用于加载.properties 文件中的配置。例如我们配置数据源时,可以把连接数据库的信息写到properties 配置文件中,就可以使用此注解指定 properties 配置文件的位置。

    • 属性:value[]:用于指定 properties 文件位置。如果是在类路径下,需要写上 classpath:

  5. @Import

    • 作用:用于导入其他配置类,在引入其他配置类时,可以不用再写@Configuration 注解。当然,写上也没问题。

    • 属性:value[]:用于指定其他配置类的字节码。

整合案例

1 jdbc.properties 文件:
2     jdbc.driver=com.mysql.jdbc.Driver
3     jdbc.url=jdbc:mysql://localhost:3306/day44_ee247_spring
4     jdbc.username=root
5     jdbc.password=1234
1 xml引入属性文件的两种方式
2 一种方式:
3  <!-- 引入外部属性文件: -->
4  <bean class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer"> 
5      <property name="location" value="classpath:jdbc.properties"/>
6 </bean>
7 另一种方式:
8 <context:property-placeholder location="classpath:jdbc.properties"/>
 1 @Configuration
 2 @PropertySource("classpath:jdbc.properties")
 3 public class JdbcConfig {
 4     
 5     @Value("${jdbc.driver}")
 6     private String driver;
 7     @Value("${jdbc.url}")
 8     private String url;
 9     @Value("${jdbc.username}")
10     private String username;
11     @Value("${jdbc.password}")
12     private String password;
13    
14     /**
15     * 创建一个数据源,并存入 spring 容器中
16     * @return
17     */
18     @Bean(name="dataSource")
19     public DataSource createDataSource() {
20         try {
21             ComboPooledDataSource ds = new ComboPooledDataSource(); 
22             ds.setDriverClass(driver);
23             ds.setJdbcUrl(url);            
24             ds.setUser(username);
25             ds.setPassword(password);
26             return ds;
27         } catch (Exception e) {
28             throw new RuntimeException(e);
29         } 
30     } 
31 }
1 //父配置文件
2 @Configuration
3 @ComponentScan(basePackages = "com.itheima.spring") 
4 @Import({JdbcConfig.class})
5 public class SpringConfiguration { 
6 }
1 //获取spring容器
2 ApplicationContext ac = new AnnotationConfigApplicationContext(SpringConfiguration.class);

 

Spring 整合 Junit

  1. 为什么不把测试类配到xml中

    • 在解释这个问题之前,先解除大家的疑虑,配到 XML 中能不能用呢?

    • 答案是肯定的,没问题,可以使用。

    • 那么为什么不采用配置到 xml 中的方式呢?

    • 这个原因是这样的:

    • 第一:当我们在 xml 中配置了一个 bean,spring 加载配置文件创建容器时,就会创建对象。

    • 第二:测试类只是我们在测试功能时使用,而在项目中它并不参与程序逻辑,也不会解决需求上的问题,所以创建完了,并没有使用。那么存在容器中就会造成资源的浪费。

    • 所以,基于以上两点,我们不应该把测试配置到 xml 文件中。

  2. 配置步骤

     1 /**
     2 * 测试类
     3 * @author 黑马程序员
     4 * @Company 
     5 * @Version 1.0
     6 */
     7 @RunWith(SpringJUnit4ClassRunner.class)
     8 //使用@RunWith 注解替换原有运行器
     9 
    10 @ContextConfiguration(locations= {"classpath:bean.xml"})
    11 //使用@ContextConfiguration 指定 spring 配置文件的位置
    12 //locations 属性:用于指定配置文件的位置。如果是类路径下,需要用 classpath:表明
    13 //classes 属性:用于指定注解的类。当不使用 xml 配置时,需要用此属性指定注解类的位置。
    14 public class AccountServiceTest {
    15     @Autowired
    16     //@Autowired 给测试类中的变量注入数据
    17     private IAccountService as ; 
    18 }

AOP

  1. AOP:全称是 Aspect Oriented Programming 即:面向切面编程。简单的说它就是把我们程序重复的代码抽取出来,在需要执行的时候,使用动态代理的技术,在不修改源码的基础上,对我们的已有方法进行增强。

经典案例

 1 public class AccountServiceImpl implements IAccountService {
 2     private IAccountDao accountDao = new AccountDaoImpl();
 3     @Override
 4     public void updateAccount(Account account) {
 5         try {
 6             TransactionManager.beginTransaction();
 7             accountDao.update(account);
 8             TransactionManager.commit();
 9         } catch (Exception e) {
10             TransactionManager.rollback();
11             e.printStackTrace();
12         }finally {
13             TransactionManager.release();
14         } 
15     }
16     @Override
17     public Account findAccountById(Integer accountId) {
18         Account account = null;
19         try {
20             TransactionManager.beginTransaction();
21             account = accountDao.findById(accountId);
22             TransactionManager.commit();
23             return account;
24         } catch (Exception e) {
25             TransactionManager.rollback();
26             e.printStackTrace();
27         }finally {
28             TransactionManager.release();
29         }
30         return null;
31     }
32     @Override
33     public void transfer(String sourceName, String targetName, Float money) {
34         try {
35             TransactionManager.beginTransaction();
36             Account source = accountDao.findByName(sourceName);
37             Account target = accountDao.findByName(targetName);
38             source.setMoney(source.getMoney()-money);
39             target.setMoney(target.getMoney()+money);
40             accountDao.update(source);
41             int i=1/0;
42             accountDao.update(target);
43             TransactionManager.commit();
44         } catch (Exception e) {
45             TransactionManager.rollback();
46             e.printStackTrace();
47         }finally {
48             TransactionManager.release();
49         } 
50     }
51 }
52 public class TransactionManager {
53     //定义一个 DBAssit
54     private static DBAssit dbAssit = new DBAssit(C3P0Utils.getDataSource(),true);
55     //开启事务
56     public static void beginTransaction() {
57         try {
58             dbAssit.getCurrentConnection().setAutoCommit(false);
59         } catch (SQLException e) {
60             e.printStackTrace();
61         } 
62     }
63     //提交事务
64     public static void commit() {
65         try {
66             dbAssit.getCurrentConnection().commit();
67         } catch (SQLException e) {
68             e.printStackTrace();
69         } 
70     }
71     //回滚事务
72    public static void rollback() {
73        try {
74            dbAssit.getCurrentConnection().rollback();
75        } catch (SQLException e) {
76            e.printStackTrace();
77        } 
78    }
79     //释放资源
80     public static void release() {
81         try {
82             dbAssit.releaseConnection();
83         } catch (Exception e) {
84             e.printStackTrace();
85         } 
86     }
87 } 

案例分析:通过对业务层改造,已经可以实现事务控制了,但是由于我们添加了事务控制,也产生了一个新的问题:业务层方法变得臃肿了,里面充斥着很多重复代码。并且业务层方法和事务控制方法耦合了

动态代理

  1. 特点

    • 字节码随用随创建,随用随加载。

    • 它与静态代理的区别也在于此。因为静态代理是字节码一上来就创建好,并完成加载。

    • 装饰者模式就是静态代理的一种体现

经典案例

 1 public interface IActor {
 2     public void basicAct(float money);
 3     public void dangerAct(float money);
 4 }
 5 
 6 public class Actor implements IActor {
 7     public void basicAct(float money) {
 8         System.out.println("拿到钱,开始基本的表演:" + money);
 9     }
10 
11     public void dangerAct(float money) {
12         System.out.println("拿到钱,开始危险的表演:" + money);
13     }
14 }

 

使用 JDK 官方的 Proxy 类创建代理对象

 1 public class Client {
 2     public static void main(String[] args) {
 3         //一个剧组找演员:
 4         final Actor actor = new Actor();//直接
 5         /**
 6          * 代理:
 7          * 间接。
 8          * 获取代理对象:
 9          * 要求:
10          * 被代理类最少实现一个接口
11          * 创建的方式
12          * Proxy.newProxyInstance(三个参数)
13          * 参数含义:
14          * ClassLoader:和被代理对象使用相同的类加载器。
15          * Interfaces:和被代理对象具有相同的行为。实现相同的接口。
16          * InvocationHandler:如何代理。
17          * 策略模式:使用场景是:
18          * 数据有了,目的明确。
19          * 如何达成目标,就是策略。
20          *
21          */
22         IActor proxyActor = (IActor) Proxy.newProxyInstance(
23                 actor.getClass().getClassLoader(),
24                 actor.getClass().getInterfaces(),
25                 new InvocationHandler() {
26                     /**
27                      * 执行被代理对象的任何方法,都会经过该方法。
28                      * 此方法有拦截的功能。
29                      *
30                      * 参数:
31                      * proxy:代理对象的引用。不一定每次都用得到
32                      * method:当前执行的方法对象
33                      * args:执行方法所需的参数
34                      * 返回值:
35                      * 当前执行方法的返回值
36                      */
37                     @Override
38                     public Object invoke(Object proxy, Method method, Object[] args)
39                             throws Throwable {
40                         String name = method.getName();
41                         Float money = (Float) args[0];
42                         Object rtValue = null;
43                         //每个经纪公司对不同演出收费不一样,此处开始判断
44                         if ("basicAct".equals(name)) {
45                             //基本演出,没有 2000 不演
46                             if (money > 2000) {
47                                 //看上去剧组是给了 8000,实际到演员手里只有 4000
48                                 //这就是我们没有修改原来 basicAct 方法源码,对方法进行了增强
49                                 rtValue = method.invoke(actor, money / 2);
50                             }
51                         }
52                         if ("dangerAct".equals(name)) {
53                             //危险演出,没有 5000 不演
54                             if (money > 5000) {
55                                 //看上去剧组是给了 50000,实际到演员手里只有 25000
56                                 //这就是我们没有修改原来 dangerAct 方法源码,对方法进行了增强
57                                 rtValue = method.invoke(actor, money / 2);
58                             }
59                         }
60                         return rtValue;
61                     }
62                 });
63         //没有经纪公司的时候,直接找演员。
64         // actor.basicAct(1000f);
65         // actor.dangerAct(5000f);
66         //剧组无法直接联系演员,而是由经纪公司找的演员
67         proxyActor.basicAct(8000f);
68         proxyActor.dangerAct(50000f);
69     }
70 }

使用 CGLib 的 Enhancer 类创建代理对象

 1 public class Client {
 2     public static void main(String[] args) {
 3         final Actor actor = new Actor();
 4         /**
 5          * 基于子类的动态代理
 6          * 要求:
 7          * 被代理对象不能是最终类
 8          * 用到的类:
 9          * Enhancer
10          * 用到的方法:
11          * create(Class, Callback)
12          * 方法的参数:
13          * Class:被代理对象的字节码
14          * Callback:如何代理
15          *
16          * @param args
17          */
18         Actor cglibActor = (Actor) Enhancer.create(actor.getClass(),
19                 new MethodInterceptor() {
20                     /**
21                      * 执行被代理对象的任何方法,都会经过该方法。在此方法内部就可以对被代理对象的任何
22                      方法进行增强。
23                      *
24                      * 参数:
25                      * 前三个和基于接口的动态代理是一样的。
26                      * MethodProxy:当前执行方法的代理对象。
27                      * 返回值:
28                      * 当前执行方法的返回值
29                      */
30                     @Override
31                     public Object intercept(Object proxy, Method method, Object[] args,
32                                             MethodProxy methodProxy) throws Throwable {
33                         String name = method.getName();
34                         Float money = (Float) args[0];
35                         Object rtValue = null;
36                         if ("basicAct".equals(name)) {
37                             //基本演出
38                             if (money > 2000) {
39                                 rtValue = method.invoke(actor, money / 2);
40                             }
41                         }
42                         if ("dangerAct".equals(name)) {
43                             //危险演出
44                             if (money > 5000) {
45                                 rtValue = method.invoke(actor, money / 2);
46                             }
47                         }
48                         return rtValue;
49                     }
50                 });
51         cglibActor.basicAct(10000);
52         cglibActor.dangerAct(100000);
53     }
54 }

 

AOP 相关术语

  • Joinpoint(连接点):所谓连接点是指那些被拦截到的点。在 spring 中,这些点指的是方法,因为 spring 只支持方法类型的连接点。

  • Pointcut切入点):所谓切入点是指我们要对哪些 Joinpoint 进行拦截的定义。

  • Advice(通知/增强):所谓通知是指拦截到 Joinpoint 之后所要做的事情就是通知。

    • 通知的类型:前置通知,后置通知,异常通知,最终通知,环绕通知。

  • Introduction(引介):引介是一种特殊的通知在不修改类代码的前提下, Introduction 可以在运行期为类动态地添加一些方法或 Field。

  • Target(目标对象):代理的目标对象。

  • Weaving(织入):是指把增强应用到目标对象来创建新的代理对象的过程。spring 采用动态代理织入,而 AspectJ 采用编译期织入和类装载期织入。

  • Proxy(代理):一个类被 AOP 织入增强后,就产生一个结果代理类。

  • Aspect(切面):是切入点和通知(引介)的结合。

基于xml的 AOP 配置

  1. 使用 aop:config 声明 aop 配置

    <aop:config>
        <!-- 配置的代码都写在此处 -->
    </aop:config>
  2. 使用 aop:aspect 配置切面

    • 属性:

      • id:给切面提供一个唯一标识。

      • ref:引用配置好的通知类 bean 的 id

    <aop:aspect id="txAdvice" ref="txManager">
        <!--配置通知的类型要写在此处-->
    </aop:aspect>
  3. 使用 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"/>
  4. 使用 aop:xxx 配置对应的通知类型

    • aop:before

      • 作用:用于配置前置通知。指定增强的方法在切入点方法之前执行

      • 属性:

        • method:用于指定通知类中的增强方法名称

        • ponitcut-ref:用于指定切入点的表达式的引用

        • poinitcut:用于指定切入点表达式

      • 执行时间点:切入点方法执行之前执行

    • aop:after-returning

      • 作用:用于配置后置通知

      • 属性:

        • method:指定通知中方法的名称。

        • pointcut-ref:指定切入点表达式的引用

        • pointct:定义切入点表达式

      • 执行时间点:切入点方法正常执行之后。它和异常通知只能有一个执行

    • aop:after-throwing

      • 作用:用于配置异常通知

      • 属性:

        • method:指定通知中方法的名称。

        • pointcut-ref:指定切入点表达式的引用

        • pointct:定义切入点表达式

      • 执行时间点:切入点方法执行产生异常后执行。它和后置通知只能执行一个

    • aop:after

      • 作用:用于配置最终通知

      • 属性:

        • method:指定通知中方法的名称。

        • pointct:定义切入点表达式

        • pointcut-ref:指定切入点表达式的引用

      • 执行时间点:无论切入点方法执行时是否有异常,它都会在其后面执行。

      <aop:before method="beginTransaction" pointcut-ref="pt1"/>
      <aop:after-returning method="commit" pointcut-ref="pt1"/>
      <aop:after-throwing method="rollback" pointcut-ref="pt1"/>
      <aop:after method="release" pointcut-ref="pt1"/>

切入点表达式说明

execution:匹配方法的执行(常用)
execution(表达式)
1.表达式语法:execution([修饰符] 返回值类型 包名.类名.方法名(参数))
2.写法说明:
3.全匹配方式:
public void com.itheima.service.impl.AccountServiceImpl.saveAccount(com.itheima.domain.Account)
4.访问修饰符可以省略
void com.itheima.service.impl.AccountServiceImpl.saveAccount(com.itheima.domain.Account)
5.返回值可以使用*号,表示任意返回值
* com.itheima.service.impl.AccountServiceImpl.saveAccount(com.itheima.domain.Account)
6.包名可以使用*号,表示任意包,但是有几级包,需要写几个*
* *.*.*.*.AccountServiceImpl.saveAccount(com.itheima.domain.Account)
7.使用..来表示当前包,及其子包
* com..AccountServiceImpl.saveAccount(com.itheima.domain.Account)
8.类名可以使用*号,表示任意类
* com..*.saveAccount(com.itheima.domain.Account)
9.方法名可以使用*号,表示任意方法
* com..*.*( com.itheima.domain.Account)
10.参数列表可以使用*,表示参数可以是任意数据类型,但是必须有参数
* com..*.*(*)
11.参数列表可以使用..表示有无参数均可,有参数可以是任意类型
* com..*.*(..)
12.全通配方式:
* *..*.*(..)
13.注:
通常情况下,我们都是对业务层的方法进行增强,所以切入点表达式都是切到业务层实现类。
execution(* com.itheima.service.impl.*.*(..))

环绕通知

 1 配置方式:
 2 <aop:config>
 3     <aop:pointcut expression="execution(* com.itheima.service.impl.*.*(..))"id="pt1"/>
 4     <aop:aspect id="txAdvice"ref="txManager">
 5     <!--配置环绕通知-->
 6         <aop:around method="transactionAround"pointcut-ref="pt1"/>
 7     </aop:aspect>
 8 </aop:config>
 9 /*aop:around:
10 1.作用:用于配置环绕通知
11 2.属性:
12     method:指定通知中方法的名称。
13     pointct:定义切入点表达式
14     pointcut-ref:指定切入点表达式的引用
15 3.说明:它是 spring 框架为我们提供的一种可以在代码中手动控制增强代码什么时候执行的方式。
16 4.注意:通常情况下,环绕通知都是独立使用的
17 */
18 /**
19  * 环绕通知
20  * @param pjp
21  * spring 框架为我们提供了一个接口:ProceedingJoinPoint,它可以作为环绕通知的方法参数。
22  * 在环绕通知执行时,spring 框架会为我们提供该接口的实现类对象,我们直接使用就行。
23  * @return
24  */
25 public Object transactionAround(ProceedingJoinPoint pjp) {
26         //定义返回值
27         Object rtValue = null;
28         try {
29             //获取方法执行所需的参数
30             Object[] args = pjp.getArgs();
31             //前置通知:开启事务
32             beginTransaction();
33             //执行方法 明确调用业务层方法(切入点方法)
34             rtValue = pjp.proceed(args);
35             //后置通知:提交事务
36             commit();
37         }catch(Throwable e) { //使用Throwable范围更广
38             //异常通知:回滚事务
39             rollback();
40             e.printStackTrace();
41         }finally {
42             //最终通知:释放资源
43             release();
44         }
45         return rtValue; 
46 }

 

基于注解的 AOP 配置

  1. 常用注解

    1. @Aspect //表明当前类是一个切面类

    2. @Before("execution(* com.itheima.service.impl..(..))")

    3. @AfterReturning //把当前方法看成是后置通知。

    4. @AfterThrowing //把当前方法看成是异常通知。

    5. @After //把当前方法看成是最终通知。

    6. @Around //把当前方法看成是环绕通知。

  2. 在 spring 配置文件中开启 spring 对注解 AOP 的支持

    • <aop:aspectj-autoproxy/>

  3. 配置切入点注解 @Pointcut

    @Pointcut("execution(* com.itheima.service.impl.*.*(..))")
    private void pt1() {}
    
    常用注解的value值就可以改成如下
    @Around("pt1()")//注意:千万别忘了写括号
    public Object transactionAround(ProceedingJoinPoint pjp) {}
  4. 不使用 XML 的配置方式

    @Configuration
    @ComponentScan(basePackages="com.itheima")
    @EnableAspectJAutoProxy
    public class SpringConfiguration {
    }

#

Spring 中的JdbcTemplate

JdbcTemplate 概述

它是 spring 框架中提供的一个对象,是对原始 Jdbc API 对象的简单封装。spring 框架为我们提供了很多的操作模板类。

  • 操作关系型数据的:

    • JdbcTemplate

    • HibernateTemplate

  • 操作 nosql 数据库的:

    • RedisTemplate

  • 操作消息队列的:

    • JmsTemplate

我们今天的主角在 spring-jdbc-5.0.2.RELEASE.jar 中,我们在导包的时候,除了要导入这个 jar 包 外,还需要导入一个 spring-tx-5.0.2.RELEASE.jar(它是和事务相关的)。

Spring 中的事务控制

Spring 中事务控制的 API 介绍

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

    public interface PlatformTransactionManager {
        //根据事务定义TransactionDefinition,获取事务
        TransactionStatus getTransaction(TransactionDefinition definition);
        //提交事务
        void commit(TransactionStatus status);
        //回滚事务
        void rollback(TransactionStatus status);
    }
    • 真正管理事务的对象

      • org.springframework.jdbc.datasource.DataSourceTransactionManager 使用 Spring JDBC 或 iBatis 进行持久化数据时使用

      • org.springframework.orm.hibernate5.HibernateTransactionManager 使用 Hibernate 版本进行持久化数据时使用

  2. TransactionDefinition:它是事务的定义信息对象

    //获取事务对象名称
    - String getName()
    //获取事务隔离级
    - int getlsolationLevel()
    //获取事务传播行为
    - int getPropagationBehavior()
    //获取事务超时时间
    - int getTimeout()
    //获取事务是否只读 只读型事务,执行查询时候也会开启事务
    - boolean isReadOnly()
    1. 事务的隔离级别

      事务隔离级反映事务提交并发访问时的处理态度:
      - ISOLATION_DEFAULT
      	- 默认级别,归属下列某一种
      - ISOLATION READ_UNCOMMITTED
      	- 可以读取未提交数据
      - ISOLATION READ_COMMITTED
      	- 只能读取已提交数据,解决脏读问题(Oracle默认级别)
      - ISOLATION_REPEATABLE_READ
      	- 是否读取其他事务提交修改后的数据,解决不可重复读问题(MySQL默认级别)
      - ISOLATION_SERIALIZABLE
      	- 是否读取其他事务提交添加后的数据,解决幻影读问题
    2. 事务的传播行为

      REQUIRED:如果当前没有事务,就新建一个事务,如果已经存在一个事务中,加入到这个事务中。一般的选择(默认值)
      SUPPORTS:支持当前事务,如果当前没有事务,就以非事务方式执行(没有事务)
      MANDATORY:使用当前的事务,如果当前没有事务,就抛出异常
      REQUERS_NEW:新建事务,如果当前在事务中,把当前事务挂起。
      NOT_SUPPORTED:以非事务方式执行操作,如果当前存在事务,就把当前事务挂起
      NEVER:以非事务方式运行,如果当前存在事务,抛出异常
      NESTED:如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则执行 REQUIRED 类似的操作。
  3. TransactionStatus:此接口提供的是事务具体的运行状态

    TransactionStatus接口描述了某个时间点上事务对象的状态信息,包含有6个具体的操作
    -刷新事务
    	-void flush()
    -获取是否是否存在存储点
    	-boolean hasSavepoint()
    -获取事务是否完成
    	-boolean isCompleted()
    获取事务是否为新的事务
    	-boolean isNewTransaction()
    -获取事务是否回滚
    	-boolean isRollbackOnly()
    -设置事务回滚
    	-void setRollbackOnly()

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

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

<!-- 配置事务的通知引用事务管理器 --> 
<tx:advice id="txAdvice" transaction-manager="transactionManager">
	<!--在 tx:advice 标签内部 配置事务的属性 --> 
    <tx:attributes>
        <!-- 指定方法名称:是业务核心方法
        read-only:是否是只读事务。默认 false,不只读。
        isolation:指定事务的隔离级别。默认值是使用数据库的默认隔离级别。
        propagation:指定事务的传播行为。
        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>
</tx:advice>

<!-- 配置 aop --> 
<aop:config>
	<!-- 配置切入点表达式 --> 
    <aop:pointcut expression="execution(* com.itheima.service.impl.*.*(..))" id="pt1"/>
    <!-- 在 aop:config 标签内部:建立事务的通知和切入点表达式的关系 --> 
    <aop:advisor advice-ref="txAdvice" pointcut-ref="pt1"/>
</aop:config>

基于注解的配置方式

第一步:配置事务管理器并注入数据源
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> 
    <property name="dataSource" ref="dataSource"></property>
</bean>
第二步:在业务层使用@Transactional 注解
例如:
@Transactional(readOnly=false,propagation=Propagation.REQUIRED)
该注解的属性和 xml 中的属性含义一致。该注解可以出现在接口上,类上和方法上。
出现接口上,表示该接口的所有实现类都有事务支持。
出现在类上,表示类中所有方法有事务支持
出现在方法上,表示方法有事务支持。
以上三个位置的优先级:方法>类>接口

第三步:在配置文件中开启 spring 对注解事务的支持
<tx:annotation-driven transaction-manager="transactionManager"/>

第三步:用注解开启 spring 对注解事务的支持
@Configuration
@EnableTransactionManagement
public class SpringTxConfiguration {
//里面配置数据源,配置 JdbcTemplate,配置事务管理器。
}

 

posted @ 2020-08-19 19:36  庄嘉豪  阅读(268)  评论(2编辑  收藏  举报