(手码四万余字)专门为大学生入门的Spring全面详解

Spring

1、简介

1.1 简介

Spring : 春天 ————给软件行业带来了春天
2002年,Rod Jahnson首次推出了Spring框架雏形interface21框架。
2004年3月24日,Spring框架以interface21框架为基础,经过重新设计,发布了1.0正式版。
Spring理念 : 使现有技术更加实用 . 本身就是一个大杂烩 , 整合现有的框架技术

Spring是一个轻量级的Java 开发框架,最根本的使命就是:解决企业级应用开发的复杂性,即简化Java开发。

两个核心特性:依赖注入(DI)和面向切面编程(AOP)。

核心:IoC容器和AOP模块。通过IoC容器管理 POJO对象以及它们之间的耦合关系,通过AOP以动态非侵入的方式增强服务。

1.2 组成

(手码四万余字)专门为大学生入门的Spring全面详解_第1张图片

Spring 框架是一个分层架构,由 7 个定义良好的模块组成。Spring 模块构建在核心容器之上,核心容器定义了创建、配置和管理 bean 的方式 。

(手码四万余字)专门为大学生入门的Spring全面详解_第2张图片
组成 Spring 框架的每个模块(或组件)都可以单独存在,或者与其他一个或多个模块联合实现。每个模块的功能如下:

  • 核心容器: 核心容器提供 Spring 框架的基本功能。核心容器的主要组件是 BeanFactory,它是工厂模式的实现。BeanFactory 使用控制反转(IOC) 模式将应用程序的配置和依赖性规范与实际的应用程序代码分开。
  • Spring 上下文: Spring 上下文是一个配置文件,向 Spring 框架提供上下文信息。Spring 上下文包括企业服务,例如 JNDI、EJB、电子邮件、国际化、校验和调度功能。
  • Spring AOP: 通过配置管理特性,Spring AOP 模块直接将面向切面的编程功能 , 集成到了 Spring 框架中。所以,可以很容易地使 Spring 框架管理任何支持 AOP的对象。Spring AOP 模块为基于 Spring 的应用程序中的对象提供了事务管理服务。通过使用 Spring AOP,不用依赖组件,就可以将声明性事务管理集成到应用程序中。
  • Spring DAO: JDBC DAO 抽象层提供了有意义的异常层次结构,可用该结构来管理异常处理和不同数据库供应商抛出的错误消息。异常层次结构简化了错误处理,并且极大地降低了需要编写的异常代码数量(例如打开和关闭连接)。Spring DAO 的面向 JDBC 的异常遵从通用的 DAO 异常层次结构。
  • Spring ORM: Spring 框架插入了若干个 ORM 框架,从而提供了 ORM 的对象关系工具,其中包括 JDO、Hibernate 和 iBatis SQL Map。所有这些都遵从 Spring 的通用事务和 DAO 异常层次结构。
  • Spring Web 模块: Web 上下文模块建立在应用程序上下文模块之上,为基于 Web 的应用程序提供了上下文。所以,Spring 框架支持与 Jakarta Struts 的集成。Web 模块还简化了处理多部分请求以及将请求参数绑定到域对象的工作。
  • Spring MVC 框架: MVC 框架是一个全功能的构建 Web 应用程序的 MVC 实现。通过策略接口,MVC 框架变成为高度可配置的,MVC 容纳了大量视图技术,其中包括 JSP、Velocity、Tiles、iText 和 POI。

1.3 扩展

Spring Boot与Spring Cloud

  • Spring Boot 是 Spring 的一套快速配置脚手架,可以基于Spring Boot 快速开发单个微服务;
  • Spring Cloud是基于Spring Boot实现的;
  • Spring Boot专注于快速、方便集成的单个微服务个体,Spring Cloud关注全局的服务治理框架;
  • Spring Boot使用了约束优于配置的理念,很多集成方案已经帮你选择好了,能不配置就不配置 , Spring Cloud很大的一部分是基于Spring Boot来实现,Spring Boot可以离开Spring Cloud独立使用开发项目,但是Spring Cloud离不开Spring Boot,属于依赖的关系。
  • SpringBoot在SpringClound中起到了承上启下的作用,如果你要学习SpringCloud必须要学习SpringBoot。

2、Spring IoC

2.1 Spring IoC阐述

控制反转是一个比较抽象的概念,我们可以举个例子来说明:在实际生活中,人们要用到一样东西时,人们的基本想法就是找到东西,比如想喝橙汁,在没有饮品店的日子里,最直观的想法就是买果汁机、橙子,准备水。这里就像是自己“主动”创造的过程;然而到了现在,想喝橙汁的想法一出现,第一个想法就是找到饮品店的联系方式,通过电话等渠道描述你的需要,然会过一会就会有人送上橙汁。这里橙汁是由饮品店创造的。
有了上面的例子,我们就可以明白 控制反转的概念:控制反转是一种通过描述(在Java中可以是XML或者注释)并通过第三方去产生或获取特定对象的方式。
在Spring中实现控制反转的是IoC容器,其实现方法是依赖注入。Spring会提供IoC容器来管理对应的资源。

2.2 Spring IoC容器的设计和初始化浅谈

Spring IoC容器可以容纳我们所开发的各种Bean,并且可以从中获取各种发布在Spring IoC容器里的Bean,并且通过描述可以得到它。

Spring IoC容器的设计
Spring IoC容器的设计主要是基于BeanFactory和ApplicationContext两个接口,其中ApplicationContext是BeanFactory的子接口之一,换句话说BeanFactory是Spring IoC容器所定义的最底层接口。而ApplicationContext是其高级接口之一,并且对BeanFactory功能做了许多有用的扩展,所以在绝大部分的工作场景下,都会使用ApplicationContext作为Spring IoC容器。

Spring IoC容器的初始化和依赖注入
Bean的定义和初始化在Spring IoC容器中是两大步骤,它是先定义,然后初始化和依赖注入的。
Bean的定义分为三步:

  1. Resource定位,这步是Spring IoC容器根据开发者的配置,进行资源定位,在Spring的开发中,通过XML或者注解是十分常见的方式,定位的内容由开发者所提供。
  2. BeanDefinition的载入,这个时候只是将Resource定位到的信息,保存到Bean定义(BeanDefinition)中,此时并不会创建Bean的实例。
  3. BeanDefinition的注册,这个过程就是将BeanDefinition的信息发布到Spring IoC容器中,注意,此时仍旧没有对应的Bean实例的创建。

做完这三步,Bean就在Spring IoC容器被定义了,而没有被初始化,更没有完成依赖注入,也就是没有注入其配置的资源给Bean,那么他还不能完全使用。对于初始化和依赖注入,Spring Bean还有一个配置选项——lazy-init,其含义就是是否初始化Spring Bean,在没有任何配置的情况下,它的默认值为default,实际值就是false,也就是Spring IoC容器默认会自动初始化Bean。如果将其设置为true,那么只有当我们使用Spring IoC容器的getBean方法获取它时,它才会进行Bean的初始化,完成依赖注入。

2.3 IoC创建对象方式

2.3.1 通过无参构造方法来创建

话不多说,用代码来说明

1、User.java

public class User {

   private String name;

   public User() {
       System.out.println("user无参构造方法");
  }

   public void setName(String name) {
       this.name = name;
  }

   public void show(){
       System.out.println("name="+ name );
  }

}

2、beans.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"
      xsi:schemaLocation="http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans.xsd">

   <bean id="user" class="com.learn.spring.pojo.User">
       <property name="name" value="hjw"/>
   </bean>

</beans>

3、测试类

@Test
public void test(){
   ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
   //在执行getBean的时候, user已经创建好了 , 通过无参构造
   User user = (User) context.getBean("user");
   //调用对象的方法 .
   user.show();
}

结果可以发现,在调用show方法之前,User对象已经通过无参构造初始化了!

2.3.2 通过有参构造方法来创建

1、User.java

public class UserT {

   private String name;

   public UserT(String name) {
       this.name = name;
  }

   public void setName(String name) {
       this.name = name;
  }

   public void show(){
       System.out.println("name="+ name );
  }

}

2、beans.xml 有三种方式编写

<!-- 第一种根据index参数下标设置 -->
<bean id="user" class="com.learn.spring.pojo.User">
   <!-- index指构造方法 , 下标从0开始 -->
   <constructor-arg index="0" value="hjw"/>
</bean>
<!-- 第二种根据参数名字设置 -->
<bean id="user" class="com.learn.spring.pojo.User">
   <!-- name指参数名 -->
   <constructor-arg name="name" value="hjw"/>
</bean>
<!-- 第三种根据参数类型设置 -->
<bean id="user" class="com.learn.spring.pojo.User">
   <constructor-arg type="java.lang.String" value="hjw"/>
</bean>

3、测试类

@Test
public void testT(){
   ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
   User user = (User) context.getBean("user");
   user.show();
}

结论:在配置文件加载的时候。其中管理的对象都已经初始化了!

2.4 Bean

2.4.1 Bean的生命周期

Spring IoC容器的本质目的就是为了管理Bean,对于Bean而言,我们就要了解它的生命周期。
(手码四万余字)专门为大学生入门的Spring全面详解_第3张图片
从上图可以看到,Spring IoC容器对Bean的管理还是比较复杂的,Spring IoC容器在执行了初始化和依赖注入后,会执行一定的步骤来完成初始化,通过这些步骤我们就可以自定义初始化,而在Spring IoC容器正常关闭的时候,他也会执行一定的步骤来关闭容器,释放资源。

下面我用一个实现类来测试一下他的生命周期。

测试文件框架
(手码四万余字)专门为大学生入门的Spring全面详解_第4张图片
BeanPostProcessorImpl.java

package Juice.main.bean;

import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;

public class BeanPostProcessorImpl implements BeanPostProcessor {

    @Override
    public Object postProcessBeforeInitialization(Object bean,String beanName) throws BeansException{
        System.out.println("【"+bean.getClass().getSimpleName()+"】对象"+beanName+"开始实例化");
        return bean;
    }

    @Override
    public Object postProcessAfterInitialization(Object bean,String beanName) throws BeansException{
        System.out.println("【"+bean.getClass().getSimpleName()+"】对象"+beanName+"实例化完成");
        return bean;
    }

}

JuiceMaker.java

package Juice.main.pojo;

import org.springframework.beans.BeansException;
import org.springframework.beans.factory.*;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;

public class JuiceMaker
        implements BeanNameAware, BeanFactoryAware, ApplicationContextAware, InitializingBean, DisposableBean {
    private String beverageShop = null;
    private Source source = null;

    public String getBeverageShop() {
        return beverageShop;
    }

    public void setBeverageShop(String beverageShop) {
        this.beverageShop = beverageShop;
    }

    public Source getSource() {
        return source;
    }

    public void setSource(Source source) {
        this.source = source;
    }

    public void init(){
        System.out.println("【"+this.getClass().getSimpleName()+"】执行自定义初始化方法");
    }

    public void myDestory(){
        System.out.println("【"+this.getClass().getSimpleName()+"】执行自定义销毁方法");
    }

    public String makeJuice(){
        String Juice="这是一杯由"+beverageShop+"饮品店,提供的"+source.getSize()+source.getSugar()+source.getFruit();
        return Juice;
    }

    @Override
    public void setBeanName(String arg0){
        System.out.println("【"+this.getClass().getSimpleName()+"】调用BeanNameAware接口的setBeanName方法");
    }

    @Override
    public void setBeanFactory(BeanFactory arg0) throws BeansException{
        System.out.println("【"+this.getClass().getSimpleName()+"】调用BeanFactoryAware接口的setBeanFactory方法");
    }

    @Override
    public void setApplicationContext(ApplicationContext arg0) throws BeansException{
        System.out.println("【"+this.getClass().getSimpleName()+"】调用ApplicationContextAware接口的setApplicationContext方法");
    }

    @Override
    public void afterPropertiesSet() throws Exception{
        System.out.println("【"+this.getClass().getSimpleName()+"】调用InitializingBean接口的afterPropertiesSet方法");
    }

    @Override
    public void destroy() throws Exception{
        System.out.println("调用接口DisposableBean的destory方法");
    }
}

Source.java

package Juice.main.pojo;

public class Source {
    private String fruit;   //类型
    private String sugar;   //糖分描述
    private Integer size;   //大小杯

    public String getFruit() {
        return fruit;
    }

    public void setFruit(String fruit) {
        this.fruit = fruit;
    }

    public String getSugar() {
        return sugar;
    }

    public void setSugar(String sugar) {
        this.sugar = sugar;
    }

    public Integer getSize() {
        return size;
    }

    public void setSize(Integer size) {
        this.size = size;
    }
}

spring-cfg.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"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.0.xsd">
    <bean id="beanPostProcessor"
          class="Juice.main.bean.BeanPostProcessorImpl"></bean>

    <bean id="source" class="Juice.main.pojo.Source">
        <property name="fruit" value="橙汁"></property>
        <property name="size" value="2"></property>
        <property name="sugar" value="少糖"></property>
    </bean>

    <bean id="juiceMaker" class="Juice.main.pojo.JuiceMaker"
        init-method="init" destroy-method="myDestory">
        <property name="beverageShop" value="贡茶"></property>
        <property name="source" ref="source"></property>
    </bean>

</beans>

test.java

package Juice.test;

import Juice.main.pojo.JuiceMaker;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class test {
    public static void main(String[] args){
        ClassPathXmlApplicationContext ctx=new ClassPathXmlApplicationContext("spring-cfg.xml");
        JuiceMaker juiceMaker=(JuiceMaker)ctx.getBean("juiceMaker");
        System.out.println(juiceMaker.makeJuice());
        ctx.close();
    }
}

运行结果:
(手码四万余字)专门为大学生入门的Spring全面详解_第5张图片

2.4.2 Bean的装配方式

一般说来,Spring装配Bean有两种方式:

  • 通过XML配置装配Bean
  • 通过注解配置装配Bean

通过XML配置装配Bean

使用XML装配Bean需要定义对应的XML,这里需要引入对应的XML模式(XSD)文件,这些文件会定义配置Spring Bean的一些元素,一个简单的模板如下:

<?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>

装配简单值

先来一个最简单的装配,XML代码如下:

    <bean id="source" class="Juice.main.pojo.Source">
        <property name="fruit" value="橙汁"></property>
        <property name="size" value="2"></property>
        <property name="sugar" value="少糖"></property>
    </bean>

简单的解释一下:

  • id属性是Spring找到的这个Bean的编号,不过id不是一个必需的属性,如果没有声明它,那么Spring将会采用 “全限定名#{number}” 的格式生产编号。如果只声明了这样的类,而没有声明id,那么SPring为其生产的编号就是“com.ssm.learn.pojo.Role#0”。
  • class显然就是一个类的全限定名。
  • property元素是定义类的属性,其中name属性定义的是属性名称,而value是其值。

装配集合

有时候要做一些复制的装配工作,比如Set、Map、Array和Properties等。为了很好的介绍,我们举一个例子来说明一下。首先我们新建一个Bean,代码如下:

package Juice.main.pojo;

import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Set;

public class complex {
    private Long id;
    private List<String> list;
    private Map<String,String> map;
    private Properties props;
    private Set<String> set;
    private String[] array;

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public List<String> getList() {
        return list;
    }

    public void setList(List<String> list) {
        this.list = list;
    }

    public Map<String, String> getMap() {
        return map;
    }

    public void setMap(Map<String, String> map) {
        this.map = map;
    }

    public Properties getProps() {
        return props;
    }

    public void setProps(Properties props) {
        this.props = props;
    }

    public Set<String> getSet() {
        return set;
    }

    public void setSet(Set<String> set) {
        this.set = set;
    }

    public String[] getArray() {
        return array;
    }

    public void setArray(String[] array) {
        this.array = array;
    }
}

为此可以这样装配这些属性。

<bean id="complex" class="Juice.main.pojo.complex">
        <property name="id" value="1" />

        <property name="list">
            <list>
                <value>value-list-1</value>
                <value>value-list-2</value>
                <value>value-list-3</value>
            </list>
        </property>

        <property name="map">
            <map>
                <entry key="key1" value="value-key-1" />
                <entry key="key2" value="value-key-2" />
                <entry key="key3" value="value-key-3" />
            </map>
        </property>

        <property name="props">
            <props>
                <prop key="prop1">value-prop-1</prop>
                <prop key="prop1">value-prop-2</prop>
                <prop key="prop1">value-prop-3</prop>
            </props>
        </property>

        <property name="set">
            <set>
                <value>value-set-1</value>
                <value>value-set-2</value>
                <value>value-set-3</value>
            </set>
        </property>

        <property name="array">
            <array>
                <value>value-array-1</value>
                <value>value-array-2</value>
                <value>value-array-3</value>
            </array>
        </property>
    </bean>

当然这里的装配主要集中在比较简单的String类型上,其主要的目的是告诉大家如何装配简易的数据到集合中。
从上面可以看到对字符串的各个集合的装载,但是有些时候可以需要更为复杂的装载,比如一个list可以是一个系列类的对象,又如一个Map集合类,键可以是一个类对象,而值也是一个类对象。为此,我们再来举一个简单的例子。

public class Role {
    private Long id;
    private String roleName;
    private String note;
}

public class User {
    private Long id;
    private String userName;
    private String note;
}


public class UserRoleAdd {
    private Long id;
    private List<Role> list;
    private Map<Role,User> map;
    private Set<Role> set;
}

这里可以看到,对于List、Map和Set等集合类使用的是类对象,不过不用担心,Spring IoC容器提供了对应的配置方法,代码如下:

<bean id="role1" class="Juice.main.pojo.Role">
        <property name="id" value="1" />
        <property name="roleName" value="role_name_1" />
        <property name="note" value="role_note_1" />
    </bean>

    <bean id="role2" class="Juice.main.pojo.Role">
        <property name="id" value="2" />
        <property name="roleName" value="role_name_2" />
        <property name="note" value="role_note_2" />
    </bean>

    <bean id="user1" class="Juice.main.pojo.User">
        <property name="id" value="1" />
        <property name="userName" value="user_name_1" />
        <property name="note" value="user_note_1" />
    </bean>

    <bean id="user2" class="Juice.main.pojo.User">
        <property name="id" value="2" />
        <property name="userName" value="user_name_2" />
        <property name="note" value="user_note_2" />
    </bean>

    <bean id="userRoleAdd" class="Juice.main.pojo.UserRoleAdd">
        <property name="id" value="1" />
        <property name="list">
            <list>
                <ref bean="role1" />
                <ref bean="role2" />
            </list>
        </property>
        <property name="map">
            <map>
                <entry key-ref="role1" value-ref="user1" />
                <entry key-ref="role2" value-ref="user2" />
            </map>
        </property>
        <property name="set">
            <set>
                <ref bean="role1" />
                <ref bean="role2" />
            </set>
        </property>
    </bean>

这里先定义了两个角色Bean(role1和role2)和两个用户(user1和user2),它们和之前的定义并没有什么不同,只是后面的定义稍微不一样。

命名空间装配

除了上述的配置之外,Spring还提供了对应的命名空间的定义,只是在使用命名空间的时候要先引入对应的命名空间和XML模式。比如我们用这个模式来定义userRoleAdd类实例。

<?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:c="http://www.springframework.org/schema/c"
       xmlns:p="http://www.springframework.org/schema/p"
       xmlns:util="http://www.springframework.org/schema/util"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util.xsd">
    <bean id="role1" class="Juice.main.pojo.Role"
          c:_0="1" c:_1="role_name_1" c:_2="role_note_1" />

    <bean id="role2" class="Juice.main.pojo.Role"
          p:id="2" p:roleName="role_name_2" p:note="role_note_2" />

    <bean id="user1" class="Juice.main.pojo.User"
          p:id="1" p:userName="user_name_1" p:note="user_note_1" />

    <bean id="user2" class="Juice.main.pojo.User"
          p:id="2" p:userName="user_name_2" p:note="user_note_2" />

    <util:list id="list">
        <ref bean="role1" />
        <ref bean="role2" />
    </util:list>

    <util:map id="map">
        <entry key-ref="role1" value-ref="user1" />
        <entry key-ref="role2" value-ref="user2" />
    </util:map>

    <util:set id="set">
        <ref bean="role1" />
        <ref bean="role2" />
    </util:set>

    <bean id="userRoleAdd" class="Juice.main.pojo.UserRoleAdd"
          p:id="1" p:list-ref="list" p:map-ref="map" p:set-ref="set" />
</beans>

通过注解配置装配Bean

在大多时候,我们会考虑使用注解(annotation)的方式去装配Bean。

1、使用@Component装配Bean

首先定义一下POJO,代码如下:

@Component(value = "role")
public class Role {
    @Value("1")
    private Long id;
    @Value("role_name_1")
    private String roleName;
    @Value("role_note_1")
    private String note;
}

解释一下上面的注解:

  • 注解@Component代表Spring IoC会把这个类扫描生成Bean实例,而其中的value属性代表这个类在Spring中的id,这相当于XML方式定义的Bean的id,也可以简写成@Component(“role”)。
  • 注解@Value代表的是值的注入。

现在有了这个类,但是还不能进行测试,因为Spring IoC并不知道需要去哪里扫描对象,这个时候可以使用一个Java Config 来告诉它,代码如下:

@ComponentScan
public class PojoConfig {

}

这个类十分简单,几乎没有什么逻辑

  • 包名要和上面类的包名保持一致
  • @ComponentScan代表进行扫描,默认是扫描当前包的路径,POJO的包名和它保持一致才能扫描,否则是没有的。

这样之后我们就可以进行测试:

package Juice.test;

import Juice.main.pojo.PojoConfig;
import Juice.main.pojo.Role;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

public class AnnotationMain {
    public static void main(String[] args){
        ApplicationContext context=new AnnotationConfigApplicationContext(PojoConfig.class);
        Role role=context.getBean(Role.class);
        System.out.println(role.getId());
    }
}

@ComponentScan存在着两个配置项:第一个是basePackages,它可以配置一个Java包的数组,Spring会根据它的配置扫描对应的包和子包,将配置好的Bean装配起来;第二个是basePackageClasses,它可以配置多个类,Spring会根据配置的类所在的包,为包和子包进行扫描装配对于配置的Bean。

2、自动装配——@Autowired

@Autowired主要是注解注入对象,关于这个问题,在注解中略微有点复杂,在大部分情况下建议使用自动装配,因为这样可以减少配置的复杂度。
下面我们来测试自动装配。

package Juice.main.service;

import Juice.main.pojo.Role;

public interface RoleService {
    public void printRoleInfo(Role role);
}

这个接口采用了Spring推荐的接口方式,这样可以更为灵活,因为这样可以将定义和实现分离,接下来是其实现类,代码如下:

package Juice.main.service.impl;

import Juice.main.pojo.Role;
import Juice.main.service.RoleService2;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

@Component("RoleService2")
public class RoleServiceImpl2 implements RoleService2 {
    @Autowired
    private Role role=null;

    public Role getRole() {
        return role;
    }

    public void setRole(Role role) {
        this.role = role;
    }

    @Override
    public void printRoleInfo() {
        System.out.println("id="+role.getId());
        System.out.println("roleName="+role.getRoleName());
        System.out.println("note="+role.getNote());
    }
}

这里的@Autowired注解,表示在Spring IoC定位所有的Bean后,这个字段需要按类型注入,这样IoC容器就会寻找资源,然后将其注入。

@Autowired出来可以配置在属性之外,还允许方法配置,常见的Bean的setting方法也可以使用它来完成注入。
在大部分情况下,我还是建议使用@Autowired注解,这是Spring IoC自动装配完成的,是的配置大幅度减少,满足约定优于配置的原则,增强程序的健壮性。

3、@Primary和@Qualifier

@Autowired注解可以完成一些自动装配的功能,并且方法十分简单,但是有时候这样的方式并不能使用。因为该注解采用的是按类型来注入对象,但是Java中接口可以有多个实现类,这样就会造成通过类型获取Bean的不唯一,从而导致Spring IoC类似于按类型的方式无法获取得到唯一的实例化类。
为了解决这一方法,才推出了@Primary和@Qualifier这两个注解。

注解@Primary
注解@Primary代表首要的,当Spring IoC通过一个接口或者抽象类注入对象时,注解@Primary会告诉Spring IoC容器,请优先使用该类注入。
但是无论如何这个接口只能解决首要性问题,而不能解决选择性的问题,简而言之,他不能选择使用接口具体的实现类去注入。

注解@Qualifier
注解@Qualifier就好比是采用按名称查找的方法。

@Autiwired
@Qualifier("roleService")
private RoleService roleService=null;

4、使用@Bean装配Bean
对于Java而言,大部分的开发都需要引入第三方的包,而且往往并没有这些包的源码,这时候将无法为这些包的类加入@Component注解,让它们变为开发环境的Bean。
这个时候Spring给予了一个注解@Bean,它可以注解到方法之上,并且将方法返回的对象作为Spring的Bean,存放在IoC容器中。

@Bean(name="dataSource")
public DataSource getDataSource(){
	...
}

这样就能装配一个Bean,当Spring IoC容器扫描它的时候,就会为其生成对应的Bean。这里还配置了Bean的name为dataSource,这就意味着Spring生成该Bean的时候就会使用dataSource作为其BeanName。

2.4.3 Spring表达式(Spring EL)

Spring还提供了更灵活的注入方式——Spring表达式。Spring EL拥有很多功能:

  • 使用Bean的id来引用Bean。
  • 调用指定对象的方法和访问对象的属性。
  • 进行运算。
  • 提供正则表达式进行比配。
  • 集合配置。

Bean的属性和方法

使用注解的方式需要用到注解@Value,在属性文件的读取中使用的是“$”,而在Spring EL中则使用“#”。下面我们以角色类为例进行分析,代码如下:


@Component(value = "role")
public class Role {
    @Value("#{1}")
    private Long id;
    @Value("#{role_name_1}")
    private String roleName;
    @Value("#{role_note_1}")
    private String note;
}

这样就可以定义一个BeanName为role的角色类,同时给予它所有的属性赋值,这个时候可以通过另外一个Bean去引用它的属性或者调用它的方法,比如先建一个类——ELBean类,代码如下:

@Component("elBean")
public class ELBean {
    //通过beanName获取bean,然后注入
    @Value("#{role}")
    private Role role;
    
    //获取bean的属性id
    @Value("#{role.id}")
    private Long id;
    
    //调用bean的getNote方法,获取角色名称
    @Value("#{role.getNote().toString()}")
    private String note;

}

使用类的静态常量和方法

有时候我们可能希望使用一些静态方法和常量,比如圆周率Π,而在Java中就是Math类的PI常量,如果需要注入它,代码如下:

@Value("#{T(Math).PI}")
private double pi;

同样的,有时候需要使用Math类的静态方法去生产随机数,这个时候就需要使用它的random方法,代码如下:

@Value("#{T(Math).random()}")
private double random;

这样就可以通过调用类的静态方法加载对应的数据了。

Spring EL运算
Spring EL还可以进行运算,比如在ELBean上增加一个数字num,其值默认为要求是角色编号(id)+1,那么我们可以这么写:

@Value("#{role.id+1}")
private int num;

有时候“+”运算符也可以运用在字符串的连接上,代码如下:

@Value("#{role.roleName+role.note}")
private String str;

这样就能够得到一个角色名称和备注连接的字符串。
比较两个值是否相等,还有其他更多的比较,比如大于、小于等。

@Value("#{role.id==1}")
private boolean equalNum;

@Value("#{role.note eq 'note_1'}")
private boolean equalString;

@Value("#{role.id>2}")
private boolean greater;

在Java中,也许你会怀旧三目运算,我们也可以去实现这样的功能

@Value("#{role.id>1 ? 5:1}")
private int max;

@Value("#{role.note? : 'hello'}")
private String defaultString;
2.4.4 Bean的作用域

(手码四万余字)专门为大学生入门的Spring全面详解_第6张图片
几种作用域中,request、session作用域仅在基于web的应用中使用(不必关心你所采用的是什么web应用框架),只能用在基于web的Spring ApplicationContext环境。

Singleton(单例模式)
当一个bean的作用域为Singleton,那么Spring IoC容器中只会存在一个共享的bean实例,并且所有对bean的请求,只要id与该bean定义相匹配,则只会返回bean的同一实例。Singleton是单例类型,就是在创建起容器时就同时自动创建了一个bean的对象,不管你是否使用,他都存在了,每次获取到的对象都是同一个对象。注意,Singleton作用域是Spring中的缺省作用域。要在XML中将bean定义成singleton,可以这样配置:

 <bean id="ServiceImpl" class="com.learn.spring.service.ServiceImpl" scope="singleton">

Prototype(原型模式)
当一个bean的作用域为Prototype,表示一个bean定义对应多个对象实例。Prototype作用域的bean会导致在每次对该bean请求(将其注入到另一个bean中,或者以程序的方式调用容器的getBean()方法)时都会创建一个新的bean实例。Prototype是原型类型,它在我们创建容器的时候并没有实例化,而是当我们获取bean的时候才会去创建一个对象,而且我们每次获取到的对象都不是同一个对象。根据经验,对有状态的bean应该使用prototype作用域,而对无状态的bean则应该使用singleton作用域。在XML中将bean定义成prototype,可以这样配置:

 <bean id="account" class="com.learn.spring.DefaultAccount" scope="prototype"/>  
  或者
 <bean id="account" class="com.learn.spring.DefaultAccount" singleton="false"/>

3、Java的反射机制

Java反射技术应用广泛,它能够配置:类的全限定名、方法和参数。完成对象的初始化以及发射某些方法。在Java中,反射是通过包java.lang.reflect.*来实现的。
今天我主要讲解对象构建(包括没有参数的和有参数的构造方法)和方法的发射调用。

1、通过反射构造方法
在Java中允许通过发射配置信息构建对象,下面我先写一个简单的ReflectServiceImpl类,代码如下:

public class ReflectServiceImpl {
	public void sayHello(String name) {
		System.err.println("Hello! "+name);
	}
}

然后通过发射的方法去构建它,代码如下:

//通过反射构造ReflectServiceImpl类的对象
public ReflectServiceImpl getInstance() {
		ReflectServiceImpl object=null;
		try {
			object=(ReflectServiceImpl)
					Class.forName("java发射.src.ReflectServiceImpl").newInstance();
		}
		catch (Exception e) {
			// TODO: handle exception
			e.printStackTrace();
		}
		return object;
	}

上面的代码就是生成一个对象,然后将其返回。
object=(ReflectServiceImpl)Class.forName(“java发射.src.ReflectServiceImpl”).newInstance();这段代码的目的就是给类加载器注册了一个类ReflectServiceImpl的全限定名,然后通过newInstance方法初始化了一个类对象。

但是又有了新的问题,如果这个类的构造方法中都至少存在一个参数,如何去反射构建它。在java中,只要稍微改变一下就可以了。下面我把上面的ReflectServiceImpl类进行简单的修改,代码如下:

public class ReflectServiceImpl2 {
	private String name;
	
	public ReflectServiceImpl2(String name) {
		this.name=name;
	}
	
	public void sayHello(String name) {
		System.err.println("Hello! "+name);
	}
}

这里实现了含一个参数的构造方法,这时将不能用之前的办法将其反射生成对象,而也要相应的做一些修改,代码如下:

//通过反射构造ReflectServiceImpl2类的对象
	public ReflectServiceImpl2 getInstance2() {
		ReflectServiceImpl2 object=null;
		try {
			object=(ReflectServiceImpl2)Class.forName("java发射.src.ReflectServiceImpl2")
						.getConstructor(String.class).newInstance("monkey");
		}
		catch (Exception e) {
			// TODO: handle exception
			e.printStackTrace();
		}
		return object;
	}

先通过forName加载到类的加载器。然后通过getConstructor方法,它的参数可以是多个的,这里定义为String.class,意为有且只有一个参数类型为String的构建方法。通过这个方法可以对重名方法进行排除,此时再用newInstance方法生成对象,只是newInstance方法也多了一个参数(monkey)。实际上就等于

object=new ReflectServiceImpl2("monkey");

反射的优点:只要配置就可以生成对象,可以解除程序的耦合度,比较灵活。
反射的缺点:运行比较慢。

2、反射方法
在使用反射方法前要获取方法对象,得到了方法才能够去反射。

//反射ReflectServiceImpl类的sayHello方法
	public Object reflectMethod() {
		Object returnObj=null;
		ReflectServiceImpl target=new ReflectServiceImpl();
		try {
			Method method=ReflectServiceImpl.class.getMethod("sayHello", String.class);
			returnObj=method.invoke(target, "monkey");
		}
		catch (Exception e) {
			// TODO: handle exception
			e.printStackTrace();
		}
		return returnObj;
	}

我们来看一下try里面的内容,当有具体的对象target,而不知道具体是哪一个类时,也可以使用如下代码:

Method method=target.getClass().getMethod("sayHello", String.class);

其中第一个参数是方法的名称,第二个参数是参数类型,其实是一个列表,多个参数可以继续编写多个类型,这样便能够获取反射的方法对象。反射方法是运用returnObj=method.invoke(target, “monkey”);代码完成的,第一个参数为target,就是确定用哪个对象调用方法,而monkey就是参数,这行就等同于

target.sayHello("monkey");

3、实例

public Object reflect() {
		ReflectServiceImpl object=null;
		try {
			object=(ReflectServiceImpl)
				Class.forName("java发射.src.ReflectServiceImpl").newInstance();
			Method method=ReflectServiceImpl.class.getMethod("sayHello", String.class);
			method.invoke(object, "monkey");
		}catch (Exception e) {
			// TODO: handle exception
			e.printStackTrace();
		}
		return object;
	}

这样就能反射对象和方法,测试结果如下:

Hello!monkey

4、Java的动态代理

动态代理的意义在于生成一个占位(又称代理对象),来代理真实对象,从而控制真实对象的访问。
有人可能不知道什么叫做代理模式?
那么我就用一个十分简单的场景来介绍一下。假设你的公司是一家软件设备,你是一位软件工程师。客户带着需求去找公司显然不会直接和你交谈,而是去找你的商务谈,此时客户会认为商务代表公司。
那么客户就是调用者,商务就是代理对象,软件工程师就是真实对象。
显然客户是通过商务去访问软件工程师的,那么商务的意义在于什么呢?
商务可以进行谈判。商务也有可能在开发软件之前谈判失败,此时商务就会根据公司规则去结束和客户的合作关系,这些都不用软件工程师来处理。因此,代理的作用就是,在真实对象访问前或者之后加入对应的逻辑,或者根据其他规则控制是否使用真实对象。
由此我们可以知道代理必须分为两个步骤:

  • 代理对象和真实对象建立代理关系
  • 实现代理对象的代理逻辑方法

今天我主要来讲一讲JDK的动态代理。
JDK动态代理是java.lang.reflect.*包提供的方式,它必须接助一个接口才能产生代理对象。
所以先定义接口,代码如下:

public interface HelloWorld {
	public void sayHelloWorld();
}

然后提供实现类HelloWorldImpl来实现接口,代码如下:

public class HelloWorldImpl implements HelloWorld{
	@Override
	public void sayHelloWorld() {
		System.out.println("Hello World");
	}
}

这是最简单的Java接口和实现类的关系,此时可以开始动态代理了。按照我们之前的分析,先要建立起代理对象和真实对象的关系,然后实现代理逻辑。
在JDK动态代理中,要实现代理逻辑类必须去实现java.lang.reflect.InvocationHandler接口,他里面定义了一个invoke()方法,并提供接口数组用于下挂代理对象,代码如下:

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

public class JdkProxyExample implements InvocationHandler{
	//真实对象
	private Object target=null;
	
	/*
	 * 建立代理对象和真实对象的代理关系,并返回代理对象
	 * @param target 真实对象
	 * @return  代理对象
	 */
	public Object bind(Object target) {
		this.target=target;
		Object proxy=Proxy.newProxyInstance(target.getClass().getClassLoader(),
				target.getClass().getInterfaces(),this);
		return proxy;
	}

	/*
	 * 代理方法逻辑
	 * @param proxy 代理对象
	 * @param method 当前调度方法
	 * @param args 当前方法参数
	 * @return 代理结果返回
	 */
	@Override
	public Object invoke(Object proxy,Method method,Object[] args) throws Throwable{
		System.out.println("进入代理方法");
		System.out.println("在调度真实对象之前的服务");
		Object obj=method.invoke(target, args);
		System.out.println("在调度真实对象之后的服务");
		return obj;
	}
}


第一步,建立代理对象和真实对象的关系。
这里是使用了bind方法去完成的。方法里面首先用类的属性target保存了真实对象,然后通过如下语句建立并生成代理对象。

Proxy.newProxyInstance(target.getClass().getClassLoader(),
				target.getClass().getInterfaces(),this);

其中newProxyInstance方法包含3个参数:

  • 第一个是类加载器,我们采用了target本身的类加载器。
  • 第二个是把生成的动态代理对象挂在哪些接口下,这个写法就是放在target实现的接口下。
  • 第三个是定义实现方法逻辑的代理类,this表示当前对象,它必须实现InvocationHandler接口的invoke方法,它就是代理逻辑方法的现实方法。

第二步,实现代理逻辑方法。
invoke方法可以实现代理逻辑,invoke方法的3个参数的含义如下。
proxy:代理对象,就是bind方法生成的对象。
method:当前调度的方法。
args:调度方法的参数。

当我们使用了代理对象调度方法后,他就会进入到invoke方法里面。

Object obj=method.invoke(target, args);

这行代码相当于调度真实对象的方法,这里是通过反射实现。

最后我们再来编写一个测试JDK动态代理,代码如下:

public class testJdkProxy {
	JdkProxyExample jdk=new JdkProxyExample();
	HelloWorldImpl hwImpl=new HelloWorldImpl();
	//绑定关系,代理对象和真实对象之间的关系,因为挂在HelloWorld下,
	//所以声明代理对象HelloWorld hWorld
	HelloWorld hWorld=(HelloWorld)jdk.bind(hwImpl);
	//此时HelloWorld对象已经是代理对象,他会进入代理的逻辑方法invoke里
	hWorld.sayHelloWorld();
}

测试结果如下:

进入代理逻辑方法
在调度真实对象之前的服务
Hello World
在调度真实对象之后的服务

5、Spring AOP

5.1 什么是AOP

AOP(Aspect Oriented Programming)意为:面向切面编程,通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术。AOP是OOP的延续,是软件开发中的一个热点,也是Spring框架中的一个重要内容,是函数式编程的一种衍生范型。利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。

5.2 AOP的一些术语

1、切面(Aspect)

切面就是在一个怎样的环境中工作。它可以定义后面需要介绍的各类通知、切点和引入等内容,然后SpringAOP会将其定义的内容织入到约定的流程中,在动态代理中可以把它理解成一个拦截器。

2、通知(Advice)

通知是切面开启后,切面的方法。他根据在代理对象真实方法调用前、后的顺序和逻辑区分。

  • 前置通知(before):在动态代理反射原有对象方法或者执行环绕通知前执行的通知功能。
  • 后置通知(after):在动态代理反射原有对象方法或者执行环绕通知后执行的通知功能。无论是否抛出异常,他都会被执行。
  • 返回通知(afterReturning):在动态代理反射原有对象方法或者执行环绕通知后正常返回(无异常)执行的通知功能。
  • 异常通知(afterThrowing):在动态代理反射原有对象方法或者执行环绕通知产生异常后执行的通知功能。
  • 环绕通知(around):在动态代理中,他可以取代当前被拦截对象的方法,提供回调原有被拦截对象的方法。

3、引入(introduction)

引入允许我们在现有的类里添加自定义的类和方法。

4、切点(Pointcut)

这是一个告诉Spring AOP在什么时候启动拦截并织入对应的流程中,因为并不是所有的开发都需要启动AOP的,他往往通过正则表达式进行限定。

5、连接点(join point)

连接点对应的是具体需要拦截的东西,比如通过切点的正则表达式去判断那些方法是连接点,从而织入对应的通知。

6、织入(Wearing)

织入是一个生成代理对象并将切面内容放入到流程中的过程。实际代理的方法分为静态代理和动态代理。静态代理是在编译class文件时生成的代码逻辑,但是在Spring中并不使用这样的方式,所以我们暂时不讨论。一种是通过ClassLoader也就是在类加载的时候生成的代码逻辑,但是它在应用程序代码运行之前就生成了对应的逻辑。还有一种是运行期,动态生成代码的方式,这是SpringAOP所采用的方式,Spring是以JDK和CGLIB动态代理来生成代理对象的。

(手码四万余字)专门为大学生入门的Spring全面详解_第7张图片

5.3 使用Spring实现AOP

5.3.1 通过 Spring API 实现

首先编写我们的业务接口和实现类

public interface UserService {

   public void add();

   public void delete();

   public void update();

   public void search();

}
public class UserServiceImpl implements UserService{

   @Override
   public void add() {
       System.out.println("增加用户");
  }

   @Override
   public void delete() {
       System.out.println("删除用户");
  }

   @Override
   public void update() {
       System.out.println("更新用户");
  }

   @Override
   public void search() {
       System.out.println("查询用户");
  }
}

然后去写我们的增强类 , 我们编写两个 , 一个前置增强 一个后置增强

public class Log implements MethodBeforeAdvice {

   //method : 要执行的目标对象的方法
   //objects : 被调用的方法的参数
   //Object : 目标对象
   @Override
   public void before(Method method, Object[] objects, Object o) throws Throwable {
       System.out.println( o.getClass().getName() + "的" + method.getName() + "方法被执行了");
  }
}
public class AfterLog implements AfterReturningAdvice {
   //returnValue 返回值
   //method被调用的方法
   //args 被调用的方法的对象的参数
   //target 被调用的目标对象
   @Override
   public void afterReturning(Object returnValue, Method method, Object[] args,Object target) throws Throwable {
       System.out.println("执行了" + target.getClass().getName()
       +"的"+method.getName()+"方法,"
       +"返回值:"+returnValue);
  }
}

最后去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">

   <!--注册bean-->
   <bean id="userService" class="com.learn.spring.service.UserServiceImpl"/>
   <bean id="log" class="com.learn.spring.log.Log"/>
   <bean id="afterLog" class="com.learn.spring.log.AfterLog"/>

   <!--aop的配置-->
   <aop:config>
       <!--切入点 expression:表达式匹配要执行的方法-->
       <aop:pointcut id="pointcut" expression="execution(* com.learn.spring.service.UserServiceImpl.*(..))"/>
       <!--执行环绕; advice-ref执行方法 . pointcut-ref切入点-->
       <aop:advisor advice-ref="log" pointcut-ref="pointcut"/>
       <aop:advisor advice-ref="afterLog" pointcut-ref="pointcut"/>
   </aop:config>

</beans>

测试

public class MyTest {
   @Test
   public void test(){
       ApplicationContext context = newClassPathXmlApplicationContext("beans.xml");
       UserService userService = (UserService) context.getBean("userService");
       userService.search();
  }
}

Aop的重要性 : 很重要 . 一定要理解其中的思路 , 主要是思想的理解这一块 .

Spring的Aop就是将公共的业务 (日志 , 安全等) 和领域业务结合起来 , 当执行领域业务时 , 将会把公共业务加进来 . 实现公共业务的重复利用 . 领域业务更纯粹 , 程序猿专注领域业务 , 其本质还是动态代理 。

5.3.2 自定义类来实现Aop

目标业务类不变依旧是userServiceImpl

第一步 : 写我们自己的一个切入类

public class DiyPointcut {

   public void before(){
       System.out.println("---------方法执行前---------");
  }
   public void after(){
       System.out.println("---------方法执行后---------");
  }
   
}

去spring中配置

<!--第二种方式自定义实现-->
<!--注册bean-->
<bean id="diy" class="com.kuang.config.DiyPointcut"/>

<!--aop的配置-->
<aop:config>
   <!--第二种方式:使用AOP的标签实现-->
   <aop:aspect ref="diy">
       <aop:pointcut id="diyPonitcut" expression="execution(* com.learn.spring.service.UserServiceImpl.*(..))"/>
       <aop:before pointcut-ref="diyPonitcut" method="before"/>
       <aop:after pointcut-ref="diyPonitcut" method="after"/>
   </aop:aspect>
</aop:config>

测试:

public class MyTest {
   @Test
   public void test(){
       ApplicationContext context = newClassPathXmlApplicationContext("beans.xml");
       UserService userService = (UserService) context.getBean("userService");
       userService.add();
  }
}

5.3.3 使用@AspectJ注解开发SpringAOP

现在使用@AspectJ注解的方式已经成为了主流。所以我用一个简单的例子来了解一下他的整个过程。

选择连接点
Spring是方法级别的AOP框架,我们主要是以某个类的某个方法作为连接点,用动态代理的理论来说,就是要拦截哪个方法织入对应AOP通知。
首先我们建一个接口:

package aop.service;

import game.bigLiZi.game.pojo.Role;

public interface RoleService {
    public void printRole(Role role);
}

这个接口很简单,接下来提供一个实现类:

package aop.service.impl;

import aop.service.RoleService;
import game.bigLiZi.game.pojo.Role;
import org.springframework.stereotype.Component;

@Component
public class RoleServiceImpl implements RoleService {
    @Override
    public void printRole(Role role) {
        System.out.println("{ id:"+role.getId()+"roleName:"+role.getRoleName()+
                                "note:"+role.getNote()+"}");
    }
}

这个时候如果把printRole作为AOP的连接点,那么用动态代理的语言就是要为类RoleServiceImpl生成代理对象,然后拦截printRole方法,于是可以产生各种AOP通知方法。

创建切面
选择好了连接点就可以创建切面了,在Spring中只要使用@Aspect注解一个类,那么Spring IoC容器就会认为这是一个切面了。

package aop.aspect;

import org.aspectj.lang.annotation.*;

@Aspect
public class RoleAspect {

    @Before("execution(* aop.service.impl.RoleServiceImpl.printRole(..))")
    public void before(){
        System.out.println("before..........");
    }

    @After("execution(* aop.service.impl.RoleServiceImpl.printRole(..))")
    public void after(){
        System.out.println("after..........");
    }

    @AfterReturning("execution(* aop.service.impl.RoleServiceImpl.printRole(..))")
    public void afterReturning(){
        System.out.println("afterReturning..........");
    }

    @AfterThrowing("execution(* aop.service.impl.RoleServiceImpl.printRole(..))")
    public void afterThrowing(){
        System.out.println("afterThrowing..........");
    }
}

这里我们就要说一说AspectJ注解了:

  • @Before:在被代理对象的方法前调用,前置通知。
  • @Around:将被代理对象的方法封装起来,并用环绕通知取代它。 它将覆盖原有方法,但是允许你通过反射调用原有的方法。
  • @After:在被代理对象的方法后调用, 后置通知。
  • @AfterReturning:在被代理对象的方法正常返回后调用,返回通知,要求被代理对象的方法执行过程中没有发生异常。
  • @AfterThrowing:在被代理对象的方法抛出异常后调用,异常通知,要求被代理对象的方法执行过程中返回异常。

上面那段代码的注解使用了对应的正则表达式,这些正则表达式是切点的问题,也就是要告诉Spring AOP,需要拦截什么对象的什么方法。

定义切点
上面代码在注解中定义了execution的正则表达式,Spring是通过这个正则表达式判断是否需要拦截你的方法,这个表达式是:

execution(* aop.service.impl.RoleServiceImpl.printRole(..))

对这个表达式分析一下:

  • execution:代表执行方法的时候会触发
  • *:代表任意返回类型的方法
  • aop.service.impl.RoleServiceImpl:代表类的全限定名
  • printRole:被拦截方法名称
  • (…):任意的参数

上面的表达式还有些简单,AspectJ的指示器还有很多,下面我列举几个:

  • arg():限制连接点匹配参数为指定类型的方法
  • execution:用于匹配连接点的执行方法
  • this():限制连接点匹配AOP代理的Bean,引用为指定类型的类
  • target:限制连接点匹配被代理对象为指定的类型
  • within():限制连接点匹配的包

此外,上面的正则表达式需要重复书写多次,比较麻烦,这里我们可以使用@Pointcut来避免这个麻烦。代码如下:

package aop.aspect;

import org.aspectj.lang.annotation.*;

public class anotherAspect {
    @Pointcut("execution(* aop.service.impl.RoleServiceImpl.printRole(..))")
    public void print(){
        
    }
    
    @Before("print()")
    public void before(){
        System.out.println("before..........");
    }

    @After("print()")
    public void after(){
        System.out.println("after..........");
    }

    @AfterReturning("print()")
    public void afterReturning(){
        System.out.println("afterReturning..........");
    }

    @AfterThrowing("print()")
    public void afterThrowing(){
        System.out.println("afterThrowing..........");
    }
}

测试AOP
连接点、切面以及切点都有了,这个时候就可以编写测试代码来测试AOP的内容。
首先要对Spring的Bean进行配置,采用Java配置,代码如下:

package aop.config;

import aop.aspect.RoleAspect;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;

@Configuration
@EnableAspectJAutoProxy
@ComponentScan("aop")
public class AopConfig {

    @Bean
    public RoleAspect getRoleAspect(){
        return new RoleAspect();
    }
}

测试AOP流程

package aop.main;

import aop.config.AopConfig;
import aop.service.RoleService;
import game.bigLiZi.game.pojo.Role;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

public class Main {
    public static void main(String[] args){
        ApplicationContext ctx=new AnnotationConfigApplicationContext(AopConfig.class);
        RoleService roleService=(RoleService)ctx.getBean(RoleService.class);
        Role role=new Role();
        role.setId(1L);
        role.setRoleName("role_name_1");
        role.setNote("role_note_1");
        roleService.printRole(role);
        System.out.println("########################");
        role=null;
        roleService.printRole(role);
    }
}

在第二次打印之前,将role设置为null,这样是为了测试异常返回通知。

before..........
{ id:1roleName:role_name_1note:role_note_1}
after..........
afterReturning..........
########################
before..........
after..........
afterThrowing..........
Exception in thread "main" java.lang.NullPointerException

6、一个简单的MyBatis-Spring项目

前提:Mybatis-Spring项目不是Spring框架的子项目,所以我们需要自己下载jar包。
具体下载方法:mybatis-spring.1.3.1.jar

配置Mybatis-Spring项目需要以下几步:

  1. 配置数据库
  2. 配置SqlSessionFactory,也可以选择配置SqlSessionTemplate,在同时配置SqlSessionFactory和SqlSessionTemplate的情况下,优先采用SqlSessionTemplate。
  3. 配置Mapper,可以配置单个Mapper,也可以通过扫描的方法生成Mapper,比较灵活。
  4. 事务管理。

下面我按照顺序编写代码。
1、配置数据库

    <!--数据库连接池 -->
    <bean id="dataSource" class="org.springframework.jdbc.datasource.SimpleDriverDataSource">
        <property name="username" value="root" />
        <property name="password" value="123456" />
        <property name="driverClass" value="com.mysql.jdbc.Driver" />
        <property name="url" value="jdbc:mysql://localhost:3306/ssm" />
    </bean>

2、配置SqlSessionFactory

SqlSessionFactory是产生SqlSession的基础,因此配置SqlSessionFactory十分关键。在MyBatis-Spring项目中提供了SqlSessionFactoryBean去支持SqlSessionFactory的配置。由于使用了第三方的包,一般而言,我们倾向于XML的配置,代码如下:

<!--配置SqlSessionFactory -->
    <bean id="SqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
        <property name="dataSource" ref="dataSource" />
        <property name="configLocation" value="classpath:sqlMapConfig.xml" />
    </bean>

这里配置了SqlSessionFactoryBean,但是只配置了数据源,然后引入一个MyBatis配置文件。下面我们来看看配置文件的代码:

<?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" />
        <!-- 允许JDBC支持生成的键。需要适当的驱动。如果设置为true,则这个设置强制生成的键被使用-->
        <setting name="useGeneratedKeys" value="true" />
        <!-- 配置默认的执行器 :SIMPLE执行器没有什么特别之处。REUSE执行器重用预处理语句,BATCH执行器重用语句和批量更新-->
        <setting name="defaultExecutorType" value="REUSE" />
        <!--全局启用或禁用延迟加载。当禁用时,所有关联对象都会即时加载 -->
        <setting name="lazyLoadingEnabled" value="true" />
        <!-- 设置超时时间,它决定驱动等待一个数据库相应的时间-->
        <setting name="defaultStatementTimeout" value="25000" />
    </settings>

    <!-- 别名设置-->
    <typeAliases>
        <typeAlias alias="role" type="firstMybatisSpringActivity.pojo.Role" />
    </typeAliases>

    <!-- 指定映射器路径-->
    <mappers>
        <mapper resource="firstMybatisSpringActivity/mapper/RoleMapper.xml" />
    </mappers>
</configuration>

这里通过mapper标签引入了映射器RoleMapper.xml。RoleMapper.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="firstMybatisSpringActivity.mapper.RoleMapper">

    <insert id="insertRole" useGeneratedKeys="true" keyProperty="id">
        insert into role(role_name,note) values (#{roleName},#{note})
    </insert>

    <delete id="deleteRole" parameterType="long">
        delete from role where id=#{id}
    </delete>

    <select id="getRole" parameterType="long" resultType="role">
        select id,role_name as roleName,note from role where id=#{id}
    </select>

    <update id="updateRole" parameterType="role">
        update role
        set role_name=#{roleName},
            note=#{note}
        where id=#{id}
    </update>
</mapper>
    

这里定义了一个命名空间——firstMybatisSpringActivity.mapper.RoleMapper,并且提供了对角色的增删改查方法。按照Mybatis的规则定义了一个接口RoleMapper.java,这样才能调用它。代码如下:

package firstMybatisSpringActivity.mapper;

import firstMybatisSpringActivity.pojo.Role;
import org.apache.ibatis.annotations.Param;

public interface RoleMapper {
    public int insertRole(Role role);
    public int deleteRole(@Param("id") Long id);
    public Role getRole(@Param("id") Long id);
    public int updateRole(Role role);
}

到这里就完成了关于MyBatis框架的主要代码,但是RoleMapper是一个接口,而不是一个类,它不能产生实例,所以我们要用另外的方法去配置它。

MyBatis-Spring提供了一个MapperFactoryBean类作为中介,我们可以配置它来实现我们想要的Mapper。现在我们来配置RoleMapper的映射器对象。代码如下:

<bean id="roleMapper" class="org.mybatis.spring.mapper.MapperFactoryBean">
        <!-- RoleMapper接口将被扫描为Mapper -->
        <property name="mapperInterface" value="firstMybatisSpringActivity.mapper.RoleMapper" />
        <property name="SqlSessionFactory" ref="SqlSessionFactory" />
    </bean>

这里我们可以看到MapperFactoryBean存在3个属性可以配置,分别是mapperInterface、SqlSessionFactory和sqlSessionTemplate,其中:

  • mapperInterface是映射器的接口。
  • 如果同时配置SqlSessionFactory和sqlSessionTemplate,那么他就会启用sqlSessionTemplate,而SqlSessionFactory作废。

这里笔者插一手sqlSessionTemplate组件的介绍。
严格来说,SqlSessionTemplate并不是一个必须配置的组件,但是他也有一定的价值。首先他是线程安全的类,也就是确保每一个线程使用的SqlSession唯一且不互相冲突。其次,它提供了一系列的功能,比如增删改查等常用功能,不过在此之前需要先配置它,代码如下:

	<!-- 配置SqlSessionTemplate-->
    <bean id="sqlSessionTemplate" class="org.mybatis.spring.SqlSessionTemplate">
        <constructor-arg ref="SqlSessionFactory" />
    </bean>

最后我们用代码来测试一下:

package firstMybatisSpringActivity.main;



import firstMybatisSpringActivity.mapper.RoleMapper;
import firstMybatisSpringActivity.pojo.Role;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class main {
    public static void main(String[] args){
        //ctx为Spring IoC容器
        ApplicationContext ctx=new ClassPathXmlApplicationContext("spring-config.xml");
        RoleMapper roleMapper=ctx.getBean(RoleMapper.class);
        Role role=new Role();
        role.setRoleName("role_name_mapper");
        role.setNote("note_mapper");
        roleMapper.insertRole(role);
        Long id=role.getId();
        roleMapper.getRole(id);
        role.setNote("note_mapper_update");
        roleMapper.updateRole(role);
        roleMapper.deleteRole(id);
    }
}

运行结果:

DEBUG 2020-04-19 21:26:22,739 org.springframework.context.support.AbstractApplicationContext: Refreshing org.springframework.context.support.ClassPathXmlApplicationContext@782830e
DEBUG 2020-04-19 21:26:23,044 org.springframework.beans.factory.xml.XmlBeanDefinitionReader: Loaded 5 bean definitions from class path resource [spring-config.xml]
DEBUG 2020-04-19 21:26:23,122 org.springframework.beans.factory.support.DefaultSingletonBeanRegistry: Creating shared instance of singleton bean 'org.mybatis.spring.mapper.MapperScannerConfigurer#0'
 WARN 2020-04-19 21:26:23,258 org.mybatis.spring.mapper.ClassPathMapperScanner: No MyBatis mapper was found in '[firstMybatisSpringActivity.mapper]' package. Please check your configuration.
DEBUG 2020-04-19 21:26:23,299 org.springframework.beans.factory.support.DefaultSingletonBeanRegistry: Creating shared instance of singleton bean 'org.springframework.context.annotation.internalConfigurationAnnotationProcessor'
DEBUG 2020-04-19 21:26:23,320 org.springframework.beans.factory.support.DefaultSingletonBeanRegistry: Creating shared instance of singleton bean 'org.springframework.context.event.internalEventListenerProcessor'
DEBUG 2020-04-19 21:26:23,322 org.springframework.beans.factory.support.DefaultSingletonBeanRegistry: Creating shared instance of singleton bean 'org.springframework.context.event.internalEventListenerFactory'
DEBUG 2020-04-19 21:26:23,324 org.springframework.beans.factory.support.DefaultSingletonBeanRegistry: Creating shared instance of singleton bean 'org.springframework.context.annotation.internalAutowiredAnnotationProcessor'
DEBUG 2020-04-19 21:26:23,326 org.springframework.beans.factory.support.DefaultSingletonBeanRegistry: Creating shared instance of singleton bean 'org.springframework.context.annotation.internalCommonAnnotationProcessor'
DEBUG 2020-04-19 21:26:23,346 org.springframework.beans.factory.support.DefaultSingletonBeanRegistry: Creating shared instance of singleton bean 'dataSource'
DEBUG 2020-04-19 21:26:23,414 org.springframework.beans.factory.support.DefaultSingletonBeanRegistry: Creating shared instance of singleton bean 'SqlSessionFactory'
DEBUG 2020-04-19 21:26:23,436 org.apache.ibatis.logging.LogFactory: Logging initialized using 'class org.apache.ibatis.logging.slf4j.Slf4jImpl' adapter.
DEBUG 2020-04-19 21:26:23,653 org.mybatis.spring.SqlSessionFactoryBean: Parsed configuration file: 'class path resource [sqlMapConfig.xml]'
DEBUG 2020-04-19 21:26:23,654 org.mybatis.spring.SqlSessionFactoryBean: Property 'mapperLocations' was not specified or no matching resources found
DEBUG 2020-04-19 21:26:23,657 org.springframework.beans.factory.support.DefaultSingletonBeanRegistry: Creating shared instance of singleton bean 'sqlSessionTemplate'
DEBUG 2020-04-19 21:26:23,694 org.springframework.beans.factory.support.DefaultSingletonBeanRegistry: Creating shared instance of singleton bean 'roleMapper'
DEBUG 2020-04-19 21:26:23,741 org.mybatis.spring.SqlSessionUtils: Creating a new SqlSession
DEBUG 2020-04-19 21:26:23,752 org.mybatis.spring.SqlSessionUtils: SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@7e6f74c] was not registered for synchronization because synchronization is not active
DEBUG 2020-04-19 21:26:23,759 org.springframework.jdbc.datasource.DataSourceUtils: Fetching JDBC Connection from DataSource
DEBUG 2020-04-19 21:26:23,760 org.springframework.jdbc.datasource.SimpleDriverDataSource: Creating new JDBC Driver Connection to [jdbc:mysql://localhost:3306/ssm]
DEBUG 2020-04-19 21:26:24,227 org.mybatis.spring.transaction.SpringManagedTransaction: JDBC Connection [com.mysql.jdbc.JDBC4Connection@683dbc2c] will not be managed by Spring
DEBUG 2020-04-19 21:26:24,240 org.apache.ibatis.logging.jdbc.BaseJdbcLogger: ==>  Preparing: insert into role(role_name,note) values (?,?) 
DEBUG 2020-04-19 21:26:24,279 org.apache.ibatis.logging.jdbc.BaseJdbcLogger: ==> Parameters: role_name_mapper(String), note_mapper(String)
DEBUG 2020-04-19 21:26:24,302 org.apache.ibatis.logging.jdbc.BaseJdbcLogger: <==    Updates: 1
DEBUG 2020-04-19 21:26:24,304 org.mybatis.spring.SqlSessionUtils: Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@7e6f74c]
DEBUG 2020-04-19 21:26:24,309 org.mybatis.spring.SqlSessionUtils: Creating a new SqlSession
DEBUG 2020-04-19 21:26:24,309 org.mybatis.spring.SqlSessionUtils: SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@65b3f4a4] was not registered for synchronization because synchronization is not active
DEBUG 2020-04-19 21:26:24,311 org.springframework.jdbc.datasource.DataSourceUtils: Fetching JDBC Connection from DataSource
DEBUG 2020-04-19 21:26:24,311 org.springframework.jdbc.datasource.SimpleDriverDataSource: Creating new JDBC Driver Connection to [jdbc:mysql://localhost:3306/ssm]
DEBUG 2020-04-19 21:26:24,327 org.mybatis.spring.transaction.SpringManagedTransaction: JDBC Connection [com.mysql.jdbc.JDBC4Connection@28975c28] will not be managed by Spring
DEBUG 2020-04-19 21:26:24,327 org.apache.ibatis.logging.jdbc.BaseJdbcLogger: ==>  Preparing: select id,role_name as roleName,note from role where id=? 
DEBUG 2020-04-19 21:26:24,328 org.apache.ibatis.logging.jdbc.BaseJdbcLogger: ==> Parameters: 5(Long)
DEBUG 2020-04-19 21:26:24,349 org.apache.ibatis.logging.jdbc.BaseJdbcLogger: <==      Total: 1
DEBUG 2020-04-19 21:26:24,350 org.mybatis.spring.SqlSessionUtils: Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@65b3f4a4]
DEBUG 2020-04-19 21:26:24,351 org.mybatis.spring.SqlSessionUtils: Creating a new SqlSession
DEBUG 2020-04-19 21:26:24,351 org.mybatis.spring.SqlSessionUtils: SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@55f616cf] was not registered for synchronization because synchronization is not active
DEBUG 2020-04-19 21:26:24,351 org.springframework.jdbc.datasource.DataSourceUtils: Fetching JDBC Connection from DataSource
DEBUG 2020-04-19 21:26:24,351 org.springframework.jdbc.datasource.SimpleDriverDataSource: Creating new JDBC Driver Connection to [jdbc:mysql://localhost:3306/ssm]
DEBUG 2020-04-19 21:26:24,360 org.mybatis.spring.transaction.SpringManagedTransaction: JDBC Connection [com.mysql.jdbc.JDBC4Connection@46fa7c39] will not be managed by Spring
DEBUG 2020-04-19 21:26:24,361 org.apache.ibatis.logging.jdbc.BaseJdbcLogger: ==>  Preparing: update role set role_name=?, note=? where id=? 
DEBUG 2020-04-19 21:26:24,361 org.apache.ibatis.logging.jdbc.BaseJdbcLogger: ==> Parameters: role_name_mapper(String), note_mapper_update(String), 5(Long)
DEBUG 2020-04-19 21:26:24,364 org.apache.ibatis.logging.jdbc.BaseJdbcLogger: <==    Updates: 1
DEBUG 2020-04-19 21:26:24,364 org.mybatis.spring.SqlSessionUtils: Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@55f616cf]
DEBUG 2020-04-19 21:26:24,365 org.mybatis.spring.SqlSessionUtils: Creating a new SqlSession
DEBUG 2020-04-19 21:26:24,365 org.mybatis.spring.SqlSessionUtils: SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@4a668b6e] was not registered for synchronization because synchronization is not active
DEBUG 2020-04-19 21:26:24,365 org.springframework.jdbc.datasource.DataSourceUtils: Fetching JDBC Connection from DataSource
DEBUG 2020-04-19 21:26:24,365 org.springframework.jdbc.datasource.SimpleDriverDataSource: Creating new JDBC Driver Connection to [jdbc:mysql://localhost:3306/ssm]
DEBUG 2020-04-19 21:26:24,377 org.mybatis.spring.transaction.SpringManagedTransaction: JDBC Connection [com.mysql.jdbc.JDBC4Connection@f381794] will not be managed by Spring
DEBUG 2020-04-19 21:26:24,377 org.apache.ibatis.logging.jdbc.BaseJdbcLogger: ==>  Preparing: delete from role where id=? 
DEBUG 2020-04-19 21:26:24,378 org.apache.ibatis.logging.jdbc.BaseJdbcLogger: ==> Parameters: 5(Long)
DEBUG 2020-04-19 21:26:24,380 org.apache.ibatis.logging.jdbc.BaseJdbcLogger: <==    Updates: 1
DEBUG 2020-04-19 21:26:24,381 org.mybatis.spring.SqlSessionUtils: Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@4a668b6e]

7、在Spring+Mybatis组合中使用事务的简单实例

实例目录图:
(手码四万余字)专门为大学生入门的Spring全面详解_第8张图片
chapter13Spring-cfg.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:p="http://www.springframework.org/schema/p"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xmlns:tx="http://www.springframework.org/schema/tx"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
                            http://www.springframework.org/schema/beans/spring-beans.xsd
                            http://www.springframework.org/schema/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
                            https://www.springframework.org/schema/context/spring-context.xsd">

    <!-- 启用扫描机制,并制定扫描对应的包 -->
    <context:annotation-config />
    <context:component-scan base-package="Chapter13.*" />

    <!-- 数据库连接池 -->
    <bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource">
        <property name="driverClassName" value="com.mysql.jdbc.Driver" />
        <property name="url" value="jdbc:mysql://localhost:3306/ssm" />
        <property name="username" value="root" />
        <property name="password" value="hjw19990825" />
        <property name="maxActive" value="255" />
        <property name="maxIdle" value="5" />
        <property name="maxWait" value="10000" />
    </bean>


    <!-- 集成MyBatis -->
    <bean id="SqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
        <property name="dataSource" ref="dataSource" />
        <!--指定Mybatis-config.xml -->
        <property name="configLocation" value="classpath:/Chapter13/mybatis/mybatis-config.xml" />
    </bean>

    <!-- 事务管理器配置数据源事务 -->
    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource" />
    </bean>

    <!--使用注解定义事务 -->
    <tx:annotation-driven transaction-manager="transactionManager" />

    <!-- 采用自动扫描方式创建mapper bean -->
    <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
        <property name="basePackage" value="Chapter13" />
        <property name="SqlSessionFactory" ref="SqlSessionFactory" />
        <property name="annotationClass" value="org.springframework.stereotype.Repository" />
    </bean>

</beans>

Role.java

package Chapter13.pojo;

public class Role {
    private Long id;
    private String roleName;
    private String note;

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getRoleName() {
        return roleName;
    }

    public void setRoleName(String roleName) {
        this.roleName = roleName;
    }

    public String getNote() {
        return note;
    }

    public void setNote(String note) {
        this.note = note;
    }

    public Role(){}
}

搭建MyBatis的映射文件,建立SQL和POJO的关系。

RoleMapper.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="Chapter13.mapper.RoleMapper">
        <insert id="insertRole" parameterType="Chapter13.pojo.Role">
            insert into role(role_name,note) values (#{roleName},#{note})
        </insert>
    </mapper> 

配置一个接口去使用。
RoleMapper.java

package Chapter13.mapper;

import Chapter13.pojo.Role;
import org.springframework.stereotype.Repository;

@Repository
public interface RoleMapper {
    public int insertRole(Role role);
}

mybatis-config.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>
    <mappers>
        <mapper resource="Chapter13/sqlMapper/RoleMapper.xml" />
    </mappers>
</configuration>

这样MyBatis部分的内容就配置完成了,接着配置一些服务类(Service)。对于服务类而言,在开发的过程中一般都坚持“接口+实现类”的规则,这有利于实现类的变化。这里我定义了两个接口。

RoleService.java

package Chapter13.service;

import Chapter13.pojo.Role;

public interface RoleService {
    public int insertRole(Role role);
}

RoleServiceList.java

package Chapter13.service;

import Chapter13.pojo.Role;

import java.util.List;

public interface RoleListService {
    public int insertRoleList(List<Role> roleList);
}

然后写两个实现类。

RoleServiceImpl.java

package Chapter13.service.impl;

import Chapter13.mapper.RoleMapper;
import Chapter13.pojo.Role;
import Chapter13.service.RoleService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Isolation;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

@Service
public class RoleServiceImpl implements RoleService {
    @Autowired
    private RoleMapper roleMapper=null;

    @Override
    @Transactional(propagation = Propagation.REQUIRES_NEW,isolation = Isolation.READ_COMMITTED)
    public int insertRole(Role role) {
        return roleMapper.insertRole(role);
    }
}

RoleListServiceImpl.java

package Chapter13.service.impl;

import Chapter13.pojo.Role;
import Chapter13.service.RoleListService;
import Chapter13.service.RoleService;
import org.apache.log4j.Logger;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Isolation;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

import java.util.List;

@Service
public class RoleListServiceImpl implements RoleListService {
    @Autowired
    private RoleService roleService=null;
    Logger log=Logger.getLogger(RoleListServiceImpl.class);

    @Override
    @Transactional(propagation = Propagation.REQUIRED,isolation = Isolation.READ_COMMITTED)
    public int insertRoleList(List<Role> roleList) {
        int count=0;
        for(Role role:roleList){
            try{
                count=roleService.insertRole(role)+1;
            }catch (Exception ex){
                log.info(ex);
            }
        }
        return count;
    }
}

最后我们在编写一个测试类来测试这些事务。

Chapter13Main.java

package Chapter13.main;

import Chapter13.pojo.Role;
import Chapter13.service.RoleListService;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

import java.util.ArrayList;
import java.util.List;

public class Chapter13Main {
    public static void main(String[] args){
        ApplicationContext ctx=new ClassPathXmlApplicationContext("chapter13Spring-cfg.xml");
        RoleListService roleListService=ctx.getBean(RoleListService.class);
        List<Role> roleList=new ArrayList<Role>();
        for(int i=1;i<=2;i++){
            Role role=new Role();
            role.setRoleName("role_name_"+i);
            role.setNote("role_note_"+i);
            roleList.add(role);
        }
        int count=roleListService.insertRoleList(roleList);
        System.out.println(count);
    }
}

运行结果:

......
DEBUG 2020-04-22 18:52:30,548 org.mybatis.spring.mapper.ClassPathMapperScanner: Creating MapperFactoryBean with name 'roleMapper' and 'Chapter13.mapper.RoleMapper' mapperInterface
DEBUG 2020-04-22 18:52:30,558 org.springframework.beans.factory.support.DefaultSingletonBeanRegistry: Creating shared instance of singleton bean 'org.springframework.context.event.internalEventListenerProcessor'
DEBUG 2020-04-22 18:52:30,565 org.springframework.beans.factory.support.DefaultSingletonBeanRegistry: Creating shared instance of singleton bean 'org.springframework.context.event.internalEventListenerFactory'
DEBUG 2020-04-22 18:52:30,572 org.springframework.beans.factory.support.DefaultSingletonBeanRegistry: Creating shared instance of singleton bean 'org.springframework.transaction.config.internalTransactionalEventListenerFactory'
DEBUG 2020-04-22 18:52:30,579 org.springframework.beans.factory.support.DefaultSingletonBeanRegistry: Creating shared instance of singleton bean 'org.springframework.context.annotation.internalAutowiredAnnotationProcessor'
DEBUG 2020-04-22 18:52:30,595 org.springframework.beans.factory.support.DefaultSingletonBeanRegistry: Creating shared instance of singleton bean 'org.springframework.context.annotation.internalCommonAnnotationProcessor'
DEBUG 2020-04-22 18:52:30,601 org.springframework.beans.factory.support.DefaultSingletonBeanRegistry: Creating shared instance of singleton bean 'org.springframework.aop.config.internalAutoProxyCreator'
DEBUG 2020-04-22 18:52:30,658 org.springframework.beans.factory.support.DefaultSingletonBeanRegistry: Creating shared instance of singleton bean 'roleListServiceImpl'
DEBUG 2020-04-22 18:52:30,730 org.springframework.beans.factory.support.DefaultSingletonBeanRegistry: Creating shared instance of singleton bean 'roleServiceImpl'
DEBUG 2020-04-22 18:52:30,732 org.springframework.beans.factory.support.DefaultSingletonBeanRegistry: Creating shared instance of singleton bean 'roleMapper'
DEBUG 2020-04-22 18:52:30,782 org.springframework.beans.factory.support.DefaultSingletonBeanRegistry: Creating shared instance of singleton bean 'org.springframework.transaction.config.internalTransactionAdvisor'
DEBUG 2020-04-22 18:52:30,788 org.springframework.beans.factory.support.DefaultSingletonBeanRegistry: Creating shared instance of singleton bean 'org.springframework.transaction.annotation.AnnotationTransactionAttributeSource#0'
DEBUG 2020-04-22 18:52:30,835 org.springframework.beans.factory.support.DefaultSingletonBeanRegistry: Creating shared instance of singleton bean 'transactionManager'
DEBUG 2020-04-22 18:52:30,869 org.springframework.beans.factory.support.DefaultSingletonBeanRegistry: Creating shared instance of singleton bean 'org.springframework.transaction.interceptor.TransactionInterceptor#0'
DEBUG 2020-04-22 18:52:30,954 org.springframework.transaction.support.AbstractPlatformTransactionManager: Creating new transaction with name [Chapter13.service.impl.RoleListServiceImpl.insertRoleList]: PROPAGATION_REQUIRED,ISOLATION_READ_COMMITTED
DEBUG 2020-04-22 18:52:31,782 org.springframework.jdbc.datasource.DataSourceTransactionManager: Acquired Connection [org.apache.commons.dbcp.PoolableConnection@28261e8e] for JDBC transaction
DEBUG 2020-04-22 18:52:31,787 org.springframework.jdbc.datasource.DataSourceUtils: Changing isolation level of JDBC Connection [org.apache.commons.dbcp.PoolableConnection@28261e8e] to 2
DEBUG 2020-04-22 18:52:31,789 org.springframework.jdbc.datasource.DataSourceTransactionManager: Switching JDBC Connection [org.apache.commons.dbcp.PoolableConnection@28261e8e] to manual commit
DEBUG 2020-04-22 18:52:31,791 org.springframework.transaction.support.AbstractPlatformTransactionManager: Suspending current transaction, creating new transaction with name [Chapter13.service.impl.RoleServiceImpl.insertRole]
DEBUG 2020-04-22 18:52:31,803 org.springframework.jdbc.datasource.DataSourceTransactionManager: Acquired Connection [org.apache.commons.dbcp.PoolableConnection@353352b6] for JDBC transaction
DEBUG 2020-04-22 18:52:31,803 org.springframework.jdbc.datasource.DataSourceUtils: Changing isolation level of JDBC Connection [org.apache.commons.dbcp.PoolableConnection@353352b6] to 2
DEBUG 2020-04-22 18:52:31,804 org.springframework.jdbc.datasource.DataSourceTransactionManager: Switching JDBC Connection [org.apache.commons.dbcp.PoolableConnection@353352b6] to manual commit
DEBUG 2020-04-22 18:52:31,811 org.mybatis.spring.SqlSessionUtils: Creating a new SqlSession
DEBUG 2020-04-22 18:52:31,818 org.mybatis.spring.SqlSessionUtils: Registering transaction synchronization for SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@76494737]
DEBUG 2020-04-22 18:52:31,828 org.mybatis.spring.transaction.SpringManagedTransaction: JDBC Connection [org.apache.commons.dbcp.PoolableConnection@353352b6] will be managed by Spring
DEBUG 2020-04-22 18:52:31,837 org.apache.ibatis.logging.jdbc.BaseJdbcLogger: ==>  Preparing: insert into role(role_name,note) values (?,?) 
DEBUG 2020-04-22 18:52:31,888 org.apache.ibatis.logging.jdbc.BaseJdbcLogger: ==> Parameters: role_name_1(String), role_note_1(String)
DEBUG 2020-04-22 18:52:31,892 org.apache.ibatis.logging.jdbc.BaseJdbcLogger: <==    Updates: 1
DEBUG 2020-04-22 18:52:31,893 org.mybatis.spring.SqlSessionUtils: Releasing transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@76494737]
DEBUG 2020-04-22 18:52:31,893 org.mybatis.spring.SqlSessionUtils$SqlSessionSynchronization: Transaction synchronization committing SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@76494737]
DEBUG 2020-04-22 18:52:31,894 org.mybatis.spring.SqlSessionUtils$SqlSessionSynchronization: Transaction synchronization deregistering SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@76494737]
DEBUG 2020-04-22 18:52:31,894 org.mybatis.spring.SqlSessionUtils$SqlSessionSynchronization: Transaction synchronization closing SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@76494737]
DEBUG 2020-04-22 18:52:31,894 org.springframework.transaction.support.AbstractPlatformTransactionManager: Initiating transaction commit
DEBUG 2020-04-22 18:52:31,894 org.springframework.jdbc.datasource.DataSourceTransactionManager: Committing JDBC transaction on Connection [org.apache.commons.dbcp.PoolableConnection@353352b6]
DEBUG 2020-04-22 18:52:31,897 org.springframework.jdbc.datasource.DataSourceUtils: Resetting isolation level of JDBC Connection [org.apache.commons.dbcp.PoolableConnection@353352b6] to 4
DEBUG 2020-04-22 18:52:31,898 org.springframework.jdbc.datasource.DataSourceTransactionManager: Releasing JDBC Connection [org.apache.commons.dbcp.PoolableConnection@353352b6] after transaction
DEBUG 2020-04-22 18:52:31,899 org.springframework.transaction.support.AbstractPlatformTransactionManager: Resuming suspended transaction after completion of inner transaction
DEBUG 2020-04-22 18:52:31,899 org.springframework.transaction.support.AbstractPlatformTransactionManager: Suspending current transaction, creating new transaction with name [Chapter13.service.impl.RoleServiceImpl.insertRole]
DEBUG 2020-04-22 18:52:31,900 org.springframework.jdbc.datasource.DataSourceTransactionManager: Acquired Connection [org.apache.commons.dbcp.PoolableConnection@353352b6] for JDBC transaction
DEBUG 2020-04-22 18:52:31,900 org.springframework.jdbc.datasource.DataSourceUtils: Changing isolation level of JDBC Connection [org.apache.commons.dbcp.PoolableConnection@353352b6] to 2
DEBUG 2020-04-22 18:52:31,900 org.springframework.jdbc.datasource.DataSourceTransactionManager: Switching JDBC Connection [org.apache.commons.dbcp.PoolableConnection@353352b6] to manual commit
DEBUG 2020-04-22 18:52:31,901 org.mybatis.spring.SqlSessionUtils: Creating a new SqlSession
DEBUG 2020-04-22 18:52:31,901 org.mybatis.spring.SqlSessionUtils: Registering transaction synchronization for SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@710636b0]
DEBUG 2020-04-22 18:52:31,901 org.mybatis.spring.transaction.SpringManagedTransaction: JDBC Connection [org.apache.commons.dbcp.PoolableConnection@353352b6] will be managed by Spring
DEBUG 2020-04-22 18:52:31,902 org.apache.ibatis.logging.jdbc.BaseJdbcLogger: ==>  Preparing: insert into role(role_name,note) values (?,?) 
DEBUG 2020-04-22 18:52:31,902 org.apache.ibatis.logging.jdbc.BaseJdbcLogger: ==> Parameters: role_name_2(String), role_note_2(String)
DEBUG 2020-04-22 18:52:31,903 org.apache.ibatis.logging.jdbc.BaseJdbcLogger: <==    Updates: 1
DEBUG 2020-04-22 18:52:31,903 org.mybatis.spring.SqlSessionUtils: Releasing transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@710636b0]
DEBUG 2020-04-22 18:52:31,903 org.mybatis.spring.SqlSessionUtils$SqlSessionSynchronization: Transaction synchronization committing SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@710636b0]
DEBUG 2020-04-22 18:52:31,904 org.mybatis.spring.SqlSessionUtils$SqlSessionSynchronization: Transaction synchronization deregistering SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@710636b0]
DEBUG 2020-04-22 18:52:31,904 org.mybatis.spring.SqlSessionUtils$SqlSessionSynchronization: Transaction synchronization closing SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@710636b0]
DEBUG 2020-04-22 18:52:31,904 org.springframework.transaction.support.AbstractPlatformTransactionManager: Initiating transaction commit
DEBUG 2020-04-22 18:52:31,904 org.springframework.jdbc.datasource.DataSourceTransactionManager: Committing JDBC transaction on Connection [org.apache.commons.dbcp.PoolableConnection@353352b6]
DEBUG 2020-04-22 18:52:31,906 org.springframework.jdbc.datasource.DataSourceUtils: Resetting isolation level of JDBC Connection [org.apache.commons.dbcp.PoolableConnection@353352b6] to 4
DEBUG 2020-04-22 18:52:31,906 org.springframework.jdbc.datasource.DataSourceTransactionManager: Releasing JDBC Connection [org.apache.commons.dbcp.PoolableConnection@353352b6] after transaction
DEBUG 2020-04-22 18:52:31,907 org.springframework.transaction.support.AbstractPlatformTransactionManager: Resuming suspended transaction after completion of inner transaction
DEBUG 2020-04-22 18:52:31,907 org.springframework.transaction.support.AbstractPlatformTransactionManager: Initiating transaction commit
DEBUG 2020-04-22 18:52:31,907 org.springframework.jdbc.datasource.DataSourceTransactionManager: Committing JDBC transaction on Connection [org.apache.commons.dbcp.PoolableConnection@28261e8e]
DEBUG 2020-04-22 18:52:31,908 org.springframework.jdbc.datasource.DataSourceUtils: Resetting isolation level of JDBC Connection [org.apache.commons.dbcp.PoolableConnection@28261e8e] to 4
DEBUG 2020-04-22 18:52:31,908 org.springframework.jdbc.datasource.DataSourceTransactionManager: Releasing JDBC Connection [org.apache.commons.dbcp.PoolableConnection@28261e8e] after transaction
2

Process finished with exit code 0

你可能感兴趣的:(java基础,spring,java,编程语言)