Spring使用手册

本文最后更新于:2021年1月8日 下午

先对Spring有个大概的认识…

一、Spring概述

1)介绍

  • Spring:春天 —-> 简化开发流程,为软件行业带来了春天
  • 2002年,首次推出了Spring框架的雏形:interface21框架
  • Spring框架以interface21框架为基础,经过重新设计并不断丰富内涵,于2004年3月24日,发不了1.0正式版
  • Rod Johnson,Spring FrameWork 创始人,著名作者,悉尼大学的博士,音乐学???!!
  • Spring理念:使现有的技术更加容易使用,本身是一个大杂烩,整合了现有的技术框架

  • 两套技术框架有:

    • SSH(基本不用了):Struct2 + Spring + Hibernate
    • SSM:SpringMvc + Spring + Mybatis
  • 官网:https://spring.io/

2)优点

  • Spring是一个开源的免费的框架(容器)
  • Spring是一个轻量级的、非入侵式(不会对原先代码产生影响)的框架!
  • 控制反转(IOC)、面向切面编程(AOP)
  • 支持事务的处理,对框架整合支持较好

一句话总结,即 Spring是一个轻量级的控制反转(IOC)和面向切面编程(AOP)的框架

<!-- https://mvnrepository.com/artifact/org.springframework/spring-webmvc -->
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-webmvc</artifactId>
    <version>5.2.0.RELEASE</version>
</dependency>

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-jdbc</artifactId>
    <version>5.2.0.RELEASE</version>
</dependency>

3)组成

4)拓展

现代化的Java开发,基本就是基于Spring的开发了

  • SpringBoot
    • 一个快速开发的脚手架,可以快速开发单个微服务
    • 约定大于配置
  • Spring Cloud
    • SpringCloud是基于SpringBoot开发的
  • 先学习Spring及Spring MVC,再学习SpringBoot

二、IOC理论推导

  • UserDao接口、UserDaoImpl实现类
  • UserService接口、UserService实现类

在我们之前的业务中,用户的需求可能会影响我们原先的代码,我们需要根据用户的需求去修改源代码!如果程序代码量非常大,修改一次的成本代价将十分昂贵!

我们改为使用一个Set接口进行动态注入

// UserServiceImpl.java
private UserDao userDao;
public void setUserDao(UserDao userDao) { 
   this.userDao = userDao;  // 利用set进行动态值的注入
}


// Mytest.java
userService.setUserDao(new UserDaoImpl());
//userService.setUserDao(new UserDaoMysqlImpl());
userService.getUser();
  • 之前程序是主动创建对象,如private UserDao userDao = new UserDaoMysqlImpl()
  • 现在是被动接收对象,即控制反转了!

IOC原型:通过控制反转思想,从本质上解决了因需求大量修改源代码的问题,系统耦合性降低,可以更加专注的在业务实现上。

控制反转IOC(Inversion of Control)是一种 『基于XML或注解,通过第三方去生产或获取特定对象 』的方式。

Spring通过IoC容器实现控制反转,其实现方法是 依赖注入(Dependency Injection,DI)。

没有IoC的程序中,我们采用面向对象编程,对象的创建与对象间的依赖关系完全硬编码在程序中,对象的创建由程序自己控制。控制反转后,将对象的创建转移给第三方来实现。

Spring容器在初始化时:

  • 先读取配置文件,
  • 根据配置文件,创建对象,并存入容器中,
  • 程序使用时,再从Ioc容器中取出需要的对象。

所谓的IoC,可以理解为:对象由Spring来创建、管理和装配!

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd">
        <!-- 使用Spring来创建对象,这些对象在Spring中被称为bean(组件)
          类型 变量名/对象名 = new 类型();
          class hello = new Hello();

          id = 变量名
          class = new 的对象
          property 相当于 给对象中的属性设置一个值

		 value: 具体的值,基本数据类型
		 ref:引用Spring容器中创建好的对象

          控制:谁来控制对象的创建。传统程序中,对象由程序本身控制创建;而使用Spring后,对象由Spring来创建
          反转:程序本身不再创建对象,而是被动地接收对象
          依赖注入:通过set方法进行注入
        -->

    <bean id="hello" class="com.gaowl.pojo.Hello">  <!-- 该类中需设置了以下变量的set方法,否则报错 -->
        <property name="str" value="Spring"/>
    </bean>
    
    <bean id="mysqlImpl" class="com.gaowl.dao.UserDaoMysqlImpl"> 
    </bean>
    <bean id="daoImpl" class="com.gaowl.dao.UserDaoImpl"> 
    </bean>

    <bean id="UserServiceImpl" class="com.gaowl.service.UserServiceImpl">
        <property name="userDao" ref="mysqlImpl" />
    </bean>

</beans>

测试示例:

import com.gaowl.pojo.Hello;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class MyTest {
    public static void main(String[] args) {
        // 解析xml文件,获取Spring的上下文对象
        ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml"); // 文件名需对应

        // 我们的对象 现在全部在 Spring中了,如要使用,直接取出即可
        Hello hello = (Hello) context.getBean("hello");
        System.out.println(hello.toString());
    }
}

三、IOC创建对象的方式

  • 使用带参构造创建对象

    <bean id="user" class="com.gaowl.pojo.User">
       <property name="name" value="gaowl_up"/>
    </bean>
  • 采用无参构造创建对象:

    • 下标赋值
    • 通过类型创建(不建议使用)
    • 直接通过参数名设置
    <bean id="user" class="com.gaowl.pojo.User">
        <constructor-arg index="0" value="日拱一卒"/>
        <constructor-arg type="java.lang.String" value="gaowl"/>
        <constructor-arg name="name" value="gaowl"/>
    </bean>

总结:在配置文件加载时,容器中管理的对象就已经初始化了

四、Spring配置说明

1)alias

<!-- 设置别名,如果添加了别名,便可以通过别名来获取该对象了。更推荐使用name来设置别名-->
<alias name="user" alias="user_1"/>

2)bean

  • id:bean的唯一标识符,对象名
  • class:bean对象的全限定名(全类名)
  • name:别名,而且 name可以起多个别名
<bean id="userT" class="com.gaowl.pojo.UserT" name="user2 u2,u3;u4">
   <property name="name" value="gaowl_up"/>
</bean> 

3)import

常用于团队开发使用,将多个配置文件导入合并为一个。

  • 假设某项目需要张三、李四、王五一个开发,每个人负责不同的类,不同的类注册在不同的bean中,这时我们可以利用import将所有人的bean.xml合并为一个总的
<!-- applicationContext.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">
    
    <import resource="beans.xml"/>
    <import resource="beans2.xml"/>
    <import resource="beans3.xml"/>

</beans>

五、依赖注入

依赖注入,dependency injection,即 由容器来设置和装配bean对象所依赖的资源

1)构造器注入

通过xml文件中<constructor-arg index="0" value="日拱一卒"/>方式注入,具体参考第三节

2)通过set方式注入

要求被注入的属性, 必须有set方法

  • student类

    package com.gaowl.pojo;
    
    import java.util.*;
    
    public class Student {
        private String name;
        private Address address;
        private String[] books;
        private List<String> hobbies;
        private Set<String> games;
        private String wife;
        private Properties info;
        private Map<String,String> card;
    
    	// set、get、toString方法等
    }
  • 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">
    
        <bean id="address" class="com.gaowl.pojo.Address">
            <property name="address" value="重庆"/>
        </bean>
    
        <bean id="student" class="com.gaowl.pojo.Student">
            <!-- 1.普通值注入,value -->
            <property name="name" value="gaowl"/>
    
            <!-- 2.引用类型/bean注入,ref -->
            <property name="address" ref="address"/>  <!-- 引用上面已声明的bean -->
    
            <!-- 3.数组注入 -->
            <property name="books">
                <array>
                    <value>红楼梦</value>
                    <value>西游记</value>
                </array>
            </property>
    
            <!-- 4.list注入 -->
            <property name="hobbies">
                <list>
                    <value>羽毛球</value>
                    <value>骑行</value>
                </list>
            </property>
    
            <!-- 5.set注入 -->
            <property name="games">
                <set>
                    <value>LOL</value>
                    <value>GTA5</value>
                </set>
            </property>
    
            <!-- 6.map注入 -->
            <property name="card">
                <map>
                    <entry key="中国邮政" value="123456789"/>
                    <entry key="工商银行" value="987654321"/>
                </map>
            </property>
    
            <!-- 7.null注入,String类型初始值也为null -->
            <property name="wife">
                <null/>
            </property>
    
            <!-- 8.property注入 -->
            <property name="info">
                <props>
                    <prop key="学号">6191922009</prop>
                    <prop key="性别"></prop>
                    <prop key="爱好"></prop>
                </props>
            </property>
    
        </bean>
    
    </beans>

3)其他方式注入

  • P命名空间注入:properties

    导入约束 : xmlns:p="http://www.springframework.org/schema/p"
     
     <!--P(属性: properties)命名空间 , 属性依然要设置set方法-->
     <bean id="user" class="com.gaowl.pojo.User" p:name="gaowl" p:age="24"/>
  • C命名空间注入:constructor

    导入约束 : xmlns:c="http://www.springframework.org/schema/c"
      
    <!-- C(构造: Constructor)命名空间 , 属性依然要设置set方法,还要有带参构造方法 -->
    <bean id="user" class="com.gaowl.pojo.User" c:name="gaowl" c:age="25"/>

4)Bean的作用域

  • singleton:默认值,单例模式,bean以单例的方式存在。

  • prototype:原型模式,每次容器从容器中调用bean时,都会返回一个新的实例,即new xxxbean()

    <bean id="ServiceImpl" class="cn.csdn.service.ServiceImpl" scope="singleton">
    <bean id="ServiceImpl" class="cn.csdn.service.ServiceImpl" scope="prototype">
  • request:每次HTTP请求都会创建一个新的bean

  • session:同一个HTTP session共享一个bean,不同Session使用不同bean。

    • request和session作用域仅在基于Web的应用中使用,只能用在web的Spring ApplicationContext环境中

六、Bean的自动装配(重要)

Spring会在上下文中自动寻找,并自动给bean装配属性

1)byName和byType

  • byName:自动在容器上下文中寻找 和自己对象 set方法后面的值对应的bean id
    • 需要保证所有bean的id值唯一,并且这个bean需要和自动注入的属性的set方法的值一致!
  • byType:自动在容器上下文中寻找,和自己对象 属性类型相同的bean。因此,此时id是可以省略的
    • 需要保证所有bean的class唯一,并且这个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">

    <bean id="cat" class="com.gaowl.pojo.Cat"/>
    <bean id="dog" class="com.gaowl.pojo.Dog"/>

    <bean id="person" class="com.gaowl.pojo.Person">
        <property name="name" value="gaowl"/>
        <property name="cat" ref="cat"/>
        <property name="dog" ref="dog"/>
    </bean>
</beans>


<?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="cat" class="com.gaowl.pojo.Cat"/>
    <bean id="dog" class="com.gaowl.pojo.Dog"/>

    <!-- byName:自动在容器上下文中寻找 和自己对象set方法后面的值对应的bean id;此处为搜索 cat和dog;-->
    <bean id="person" class="com.gaowl.pojo.Person" autowire="byName">
        <property name="name" value="gaowl"/>
    </bean>
    
    <!-- byType:自动在容器上下文中寻找,和自己对象属性类型相同的bean。因此,此时id是可以省略的,此处搜索类型 Cat类型 和 Dog类型    -->
    <bean id="person" class="com.gaowl.pojo.Person" autowire="byType">
        <property name="name" value="gaowl"/>
    </bean>   
</beans>

2)注解自动装配(更实用)

JdK1.5之后的版本,Spring2.5之后的版本

要使用注解,需先导入约束,并配置注解的支持

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context
        https://www.springframework.org/schema/context/spring-context.xsd">

    <context:annotation-config/>
    <bean id="cat" class="com.gaowl.pojo.Cat"/>
    <bean id="dog" class="com.gaowl.pojo.Dog"/>
    <bean id="person" class="com.gaowl.pojo.Person"/>

</beans>
  • 🔎 @Autowired

    • 直接在属性上使用即可,也可以在set方式上使用!

    • 可以省略Set方法,前提是自动装配的属性在IOC容器中存在,且可以通过byType找到

    • 多个时,采用@Qualifier(value=“xxx”)来协作使用,指定一个唯一的bean

      @Autowired
      private Cat cat;
      
      @Autowired
      @Qualifier(value="dog222")
      private Dog dog;
  • 🔎 @Resource

    • 先通过名称去匹配唯一的bean,找不到再通过type去寻找唯一的bean,依然找不到会报错

    • 多个时,通过name属性来指定唯一bean

      @Resource
      private Cat cat;
      
      @Resource(name="dog222")
      private Dog dog;
  • 🔎 @Autowired@Resource的区别:

    • 都是自动装配的,都可以放在属性字段上
    • @Autowired通过byType的方式实现,且对象必须存在
    • @Resource默认通过byName的方式实现,如果找不到名字,再通过byType进行寻找

七、使用注解开发(重要)

在spring4之后,使用注解开发时,需保证aop的包已被导入;之后再在applicationContext.xml文件中导入约束并配置注解的支持

1)Bean的实现

  • 配置扫描哪些包下的注释:

    <!-- 扫描指定路径下的注解 -->
    <context:component-scan base-package="com.gaowl"/>
  • 在指定包下编写类,增加注解

    // 等价于在beans.xml文件中的 <bean id="user" class="com.gao.pojo.User"/>
    @Component
    public class User {
    //    public String name = "gaowl";
    
        // 相当于 <property name="name" value="gaowl"/>
        @Value("gaowl")
        public String name;
    }

2)属性注入

  • 直接在变量名称上方添加@Value(“具体值”),无需set方法
  • 如果提供了set方法,也可以在set方法上添加@Value(“具体值”)。。没必要啊

3)@Component的衍生注解

  • @Controller:web层
  • @Service:service层
  • @Repository:dao层

    • 以上均为将某个类注册到容器中,装配bean
  • @Scope("prototype")指定作用域

    • 单例模式:singleton,仅有一个实例存在
    • 原型模式:prototype,每次调用bean时均会返回一个新的实例

4)小结

XML与注解比较

  • XML可以适用任何场景 ,结构清晰,维护方便
  • 注解不是自己提供的类使用不了,开发简单方便

xml与注解整合开发

  • xml管理Bean
  • 注解只负责完成属性的注入
  • 使用过程中, 可以不用扫描,但必须开启注解的支持
    • <context:annotation-config/> 的作用:
      • 进行注解驱动注册,从而使注解生效
      • 用于激活那些已经在spring容器里注册过的bean上面的注解,也就是显示的向Spring注册
      • 如果不扫描包,就需要手动配置bean
      • 如果不加注解驱动,则注入的值为null!

八、基于Java类进行配置

非常常用的一种方式

  • 实体类
package com.gaowl.pojo;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

// 将该类注册到容器中
@Component
public class User {
    private String name;

    public String getName() {
        return name;
    }

    @Value("gaowl")   // 注入数值
    public void setName(String name) {
        this.name = name;
    }

    @Override
    public String toString() {
        return "User{" +
                "name='" + name + '\'' +
                '}';
    }
}
  • 配置类
// 声明为配置类,并注册到容器中
@Configuration
@ComponentScan("com.gaowl.pojo")
@Import(GaowlConfig.class)   // 将多个配置文件合并为一个
public class GaowlConfig2 {

    // 将该方法注册为bean,相当于之前写xml文件的一个<bean>标签
    // 这个方法的名字,相当于bean标签的id属性;
    //         返回值,相当于bean标签的class属性
    @Bean
    public User getUser(){
        return new User();
    }
    
}
  • 测试类
public class MyTest {
    public static void main(String[] args) {
        
        // 完全使用配置类时,通过 AnnotationConfig上下文 来获取容器,之后再通过 配置类的class对象 加载
        ApplicationContext context = new AnnotationConfigApplicationContext(GaowlConfig.class);
        User user = context.getBean("getUser", User.class);  // 和config中的bean对应
        System.out.println(user.getName());
        
    }
}

九、代理模式(重要)

代理模式的分类:

  • 静态代理
  • 动态代理

1)静态代理

角色分析:

  • 抽象角色:一般使用接口或者抽象类来解决,比如出租房子这个操作
  • 真实角色:被代理的角色,比如房东
  • 代理角色:代理真实角色,且代理真实角色后,一般会做一些附属操作,比如房子中介
  • 客户:访问代理对象的人,比如想租房子的人,其直接跟房产中介谈就好,不用接触房东

代码步骤:

  • 接口(出租房子操作)

    public interface Rent {
        public void rent();
    }
  • 真实角色(房东)

    public class Host implements Rent{
        @Override
        public void rent() {
            System.out.println("房东要出租房子");
        }
    }
  • 代理角色(房中介)

    public class Proxy implements Rent{
    
        private Host host;
    
        public Proxy() {
        }
    
        public Proxy(Host host) {
            this.host = host;
        }
    
        @Override
        public void rent(){
            seeHouse();     
            host.rent();    // 通过有参构造器拿到host对象
            
            signContract();
            fare();
        }
    
        // 看房
        public void seeHouse(){
            System.out.println("中介正在带你看房...");
        }
    
        // 签合同
        public void signContract(){
            System.out.println("签署合同...");
        }
    
        // 收中介费
        public void fare(){
            System.out.println("收取中介费用...");
        }
    }
  • 客户端访问代理角色(租户)

    package com.gaowl.demo01;
    
    public class Client {
        public static void main(String[] args) {
            // 房东要出租房子
            Host host = new Host();
            //  host.rent();
    
            // 代理:中介帮房东出租房子,且附属有一些其他操作
            Proxy proxy = new Proxy(host);
    
            // 不用面对房东,直接找中介租房即可
            proxy.rent();
    
        }
    }

静态代理的好处:

  • 使真实角色的操作更加纯粹,不用去关注一些公共事务
  • 代理角色来负责公共事务,实现了业务的分工
  • 公共事务发生扩展时,方便集中管理

静态代理的缺点:

  • 一个真实角色就要有一个代理角色(???),代码量翻倍

一言以蔽之,代理的作用为:在不改变原有业务的基础上,增加一些新的功能

代码见:spring-08-proxy-demo2

2)动态代理

  • 也是四个角色:抽象角色、真实角色、代理角色、用户
  • 动态代理的代理类是动态生成的,无需自己手动编写
    • 基于接口的动态代理:JDK动态代理
    • 基于类的动态代理:cglib
    • Java字节码实现:javasist

📘 InvocationHandler

由代理实例的 调用处理程序实现的 接口(java.lang.reflect下,别导错了)

📘 Proxy类

提供创建动态代理类和实例的静态方法

package com.gaowl.demo04;

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

public class ProxyInvocationHandler implements InvocationHandler {

    // 被代理的接口
    private Object target;
	// 设置set方法
    public void setTarget(Object target) {
        this.target = target;
    }

    // 生成代理类
    public Object getProxy(){
        return Proxy.newProxyInstance(this.getClass().getClassLoader(), target.getClass().getInterfaces(), this);
    }

    // 处理代理实例,并返回结果
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        log(method.getName());
        Object result = method.invoke(target, args);   // 反射
        return result;
    }

    public void log(String msg){
        System.out.println("执行了" + msg + "方法");
    }

}

测试程序

package com.gaowl.demo04;

import com.gaowl.demo02.UserService;
import com.gaowl.demo02.UserServiceImpl;

public class Client {
    public static void main(String[] args) {
        // 真实角色
        UserServiceImpl userService = new UserServiceImpl();
        // 代理角色
        ProxyInvocationHandler pih = new ProxyInvocationHandler();
	    // 设置要代理的对象
        pih.setTarget(userService);  
        // 动态生成代理类
        UserService proxy = (UserService) pih.getProxy(); 

        // 通过代理类执行相关方法
        proxy.add();
    }
}

动态代理的好处

  • 使真实角色的操作更加纯粹,不用去关注一些公共事务
  • 代理角色来负责公共事务,实现了业务的分工
  • 公共事务发生扩展时,方便集中管理(以上三条和静态代理相同)
  • 一个动态代理类代理的是一个接口,一般对应一类业务
  • 一个动态代理类可以代理多个类,只要是实现了同一个接口即可

十、AOP

1)什么是AOP

AOP(Aspect Oriented Programming),面向切面编程,通过预编译方式和运行期动态代理实现程序功能的统一维护。

利用AOP可以对业务逻辑的各个部分进行分离,从而降低业务逻辑各个部分间的耦合性,提高程序的可用性,从而提高开发效率

2)AOP在Spring中的应用

==提供声明式事务;允许用户自定义切面==

以下名词需要了解下:

  • 横切关注点:跨越应用程序多个模块的方法或功能。即是,与我们业务逻辑无关的,但是我们需要关注的部分,就是横切关注点。如日志 , 安全 , 缓存 , 事务等等 ….
  • 切面(ASPECT):横切关注点 被模块化 的特殊对象。即,它是一个类
  • 通知(Advice):切面必须要完成的工作。即,它是类中的一个方法
  • 目标(Target):被通知对象。一般为接口或者某个方法
  • 代理(Proxy):向目标对象应用通知之后创建的对象。代理类
  • 切入点(PointCut):切面通知 执行的 “地点”的定义。
  • 连接点(JointPoint):与切入点匹配的执行点。

SpringAOP中,通过Advice定义横切逻辑,共有5种类型的Advice:

通知类型 连接点 实现接口
前置通知 方法前面 org.springframework.aop.MethodBeforeAdvice
后置通知 方法后面 org.springframework.aop.AfterReturningAdvice
环绕通知 方法前后 org.springframework.aop.MethodInterceptor
异常抛出通知 方法抛出异常 org.springframework.aop.ThrowsAdvice
引介通知 类中增加新的方法属性 org.springframework.aop.IntroductionInterceptor

即 Aop 在 不改变原有代码的情况下 , 去增加新的功能 .

3)使用Spring实现AOP

使用AOP织入,需要先导入aspectjweaver依赖包!

<!-- https://mvnrepository.com/artifact/org.aspectj/aspectjweaver -->
<dependency>
   <groupId>org.aspectj</groupId>
   <artifactId>aspectjweaver</artifactId>
   <version>1.9.4</version>
</dependency>

A.通过 Spring API 实现

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


/**
 * returnValue:方法的返回值
 * thod:要执行的目标对象的方法
 * args:被调用的方法的参数
 * target:目标对象
 */
public class AfterLog implements AfterReturningAdvice {
    @Override
    public void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable {
        System.out.println("执行了" + target.getClass().getName() + "的" + method.getName() + "方法,其返回值为" + returnValue);
    }
}
<?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.gaowl.service.UserServiceImpl"/>
    <bean id="afterLog" class="com.gaowl.springapi.AfterLog"/>
    <bean id="beforeLog" class="com.gaowl.springapi.BeforeLog"/>

    <!--aop的配置方法1:使用springapi -->
    <aop:config>
        <!-- 切入点,此处expression匹配要执行的方法 -->
        <aop:pointcut id="pointcut" expression="execution(* com.gaowl.service.UserServiceImpl.*(..))"/>    
        <aop:advisor advice-ref="beforeLog" pointcut-ref="pointcut"/>    <!-- 执行环绕;advice-ref为 执行 上面已声明的方法 -->
        <aop:advisor advice-ref="afterLog" pointcut-ref="pointcut"/>
    </aop:config>
</beans>

B.自定义类实现AOP

  • 自定义切入类
public class DiyPointCut {
    public void before(){
        System.out.println("---------方法执行前---------");
    }
    public void after(){
        System.out.println("---------方法执行后---------");
    }
}
  • 在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"
       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.gaowl.service.UserServiceImpl"/>
    <bean id="diy" class="com.gaowl.diy.DiyPointCut"/>

    <!-- aop的配置方法二:自定义类 -->
    <!-- 当执行UserServiceImpl中所有方法时,先执行前置before方法,再执行具体的方法,最后还要执行后置after方法 -->
    <aop:config>
        <aop:aspect ref="diy">      <!-- 自定义切面 -->
            <aop:pointcut id="diyPointcut" expression="execution(* com.gaowl.service.UserServiceImpl.*(..))"/>   <!--切入点-->
            <aop:before method="before" pointcut-ref="diyPointcut" />   <!--通知,method为 自定义类中的方法-->
            <aop:after method="after" pointcut-ref="diyPointcut" />
        </aop:aspect>
    </aop:config>

</beans>

C.通过注解实现AOP

  • 创建一个通过注解实现的增强类
@Aspect
public class AnnotationPointcut {
    @Before("execution(* com.gaowl.service.UserServiceImpl.*(..))")
    public void before(){
        System.out.println("---------方法执行前---------");
    }

    @After("execution(* com.gaowl.service.UserServiceImpl.*(..))")
    public void after(){
        System.out.println("---------方法执行后---------");
    }

    @Around("execution(* com.gaowl.service.UserServiceImpl.*(..))")
    public void around(ProceedingJoinPoint jp) throws Throwable {
        System.out.println("环绕前");
        System.out.println("签名:"+jp.getSignature());
        //执行目标方法proceed
        Object proceed = jp.proceed();
        System.out.println("环绕后");
        System.out.println(proceed);
    }

}
  • 在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"
       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.gaowl.service.UserServiceImpl"/>
    <bean id="diy" class="com.gaowl.diy.DiyPointCut"/>

    <!--aop的配置方法三:通过注解实现-->
    <bean id="annotationPointcut" class="com.gaowl.diy.AnnotationPointcut"/>
    <aop:aspectj-autoproxy/>   <!-- 该声明自动为spring容器中那些配置@aspectj切面的bean创建代理,织入切面 -->

</beans>

十一、整合Mybatis

1)基本步骤

  • 在pom.xml中导入相关jar包

    • junit、mybatis、mysql数据库、spring相关的、aop织入、mybatis-spring
    <?xml version="1.0" encoding="UTF-8"?>
    <project xmlns="http://maven.apache.org/POM/4.0.0"
             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
        <parent>
            <artifactId>spring-study</artifactId>
            <groupId>com.gaowl</groupId>
            <version>1.0-SNAPSHOT</version>
        </parent>
        <modelVersion>4.0.0</modelVersion>
    
        <artifactId>spring-10-mybatis</artifactId>
    
        <build>
            <resources>
                <resource>
                    <directory>src/main/resources</directory>
                    <includes>
                        <include>**/*.properties</include>
                        <include>**/*.xml</include>
                    </includes>
                </resource>
                <resource>
                    <directory>src/main/java</directory>
                    <includes>
                        <include>**/*.properties</include>
                        <include>**/*.xml</include>
                    </includes>
                    <filtering>true</filtering>
                </resource>
            </resources>
        </build>
    
        <dependencies>
            <dependency>
                <groupId>junit</groupId>
                <artifactId>junit</artifactId>
                <version>4.12</version>
                <scope>test</scope>
            </dependency>
    
            <dependency>
                <groupId>mysql</groupId>
                <artifactId>mysql-connector-java</artifactId>
                <version>5.1.47</version>
            </dependency>
    
            <dependency>
                <groupId>org.mybatis</groupId>
                <artifactId>mybatis</artifactId>
                <version>3.5.2</version>
            </dependency>
    
            <dependency>
                <groupId>org.springframework</groupId>
                <artifactId>spring-jdbc</artifactId>
                <version>5.2.0.RELEASE</version>
            </dependency>
    
            <dependency>
                <groupId>org.mybatis</groupId>
                <artifactId>mybatis-spring</artifactId>
                <version>2.0.6</version>
            </dependency>
    
            <dependency>
                <groupId>org.projectlombok</groupId>
                <artifactId>lombok</artifactId>
                <version>1.18.16</version>
            </dependency>
        </dependencies>
    
    </project>
  • 创建实体类、接口、Mapper实现

    package com.gaowl.pojo;
    
    import lombok.AllArgsConstructor;
    import lombok.Data;
    import lombok.NoArgsConstructor;
    
    @Data
    @AllArgsConstructor
    @NoArgsConstructor
    public class User {
        private int id;
        private String name;
        private String pwd;
    }
    package com.gaowl.dao;
    
    import com.gaowl.pojo.User;
    import java.util.List;
    
    public interface UserMapper {
        public List<User> getUsers();
    }
    package com.gaowl.dao;
    
    import com.gaowl.pojo.User;
    import org.mybatis.spring.SqlSessionTemplate;
    import java.util.List;
    
    public class UserMapperImpl implements UserMapper{
    
        private SqlSessionTemplate sqlSession;   // 通过spring-dao.xml传入
    
        public void setSqlSession(SqlSessionTemplate sqlSession) {
            this.sqlSession = sqlSession;
        }
    
        // 之前是在测试类中获取sqlSession并调用相关方法
        @Override
        public List<User> getUsers() {
            UserMapper mapper = sqlSession.getMapper(UserMapper.class);
            return mapper.getUsers();
        }
    }
    <?xml version="1.0" encoding="UTF-8" ?>
    <!DOCTYPE mapper
            PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
            "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
    <mapper namespace="com.gaowl.dao.UserMapper">
        <select id="getUsers"  resultType="User">
            select * from mybatis.user
        </select>
    </mapper>
  • 编写Spring的配置文件

    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="http://www.springframework.org/schema/beans"
           xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
           xmlns:aop="http://www.springframework.org/schema/aop"
           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">
    
        <!-- DataSource:使用Spring的数据源替换Mybatis的配置  c3p0 dbcp  druid
             此外,我们使用Spring提供的JDBC -->
        <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
            <property name="driverClassName" value="com.mysql.jdbc.Driver"/>
            <property name="url" value="jdbc:mysql://localhost:3306/mybatis?useSSL=false&amp;useUnicode=true&amp;characterEncoding=utf8"/>
            <property name="username" value="root"/>
            <property name="password" value="root"/>
        </bean>
    
        
        <!-- 【重点】sqlSessionFactory,并制定和上面datasource的连接 -->
        <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
            <property name="dataSource" ref="dataSource"/>
    
            <!-- 绑定Mybatis配置文件 -->
            <property name="configLocation" value="classpath:mybatis-config.xml"/>
            <property name="mapperLocations" value="classpath:com/gaowl/dao/UserMapper.xml"/>
        </bean>
    
        
        <!-- 【重点】使用SqlSessionTemplate来创建我们要使用的SqlSession,
    		此处采用sqlSessionFactory作为构造方法的参数创建SqlSessionTemplate对象。 -->
        <bean id="sqlSession" class="org.mybatis.spring.SqlSessionTemplate">
            <constructor-arg index="0" ref="sqlSessionFactory"/>
        </bean>
    
    </beans>
    <?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">
    
        <!-- 引入子配置文件 -->
        <import resource="spring-dao.xml"/>
    
        <!-- 将实现类注入到Spring中 -->
        <bean id="UserMapper" class="com.gaowl.dao.UserMapperImpl">
            <property name="sqlSession" ref="sqlSession"/>
        </bean>
    </beans>
  • 测试

    import com.gaowl.dao.UserMapper;
    import com.gaowl.pojo.User;
    import org.junit.Test;
    import org.springframework.context.ApplicationContext;
    import org.springframework.context.support.ClassPathXmlApplicationContext;
    
    import java.util.List;
    
    public class MyTest {
    
        @Test
        public void test(){
            ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
            // 动态代理代理的是接口,而不是类,因此此处为 UserService 而不是 UserServiceImpl
            UserMapper userMapper = context.getBean("UserMapper", UserMapper.class);
    
            List<User> users = userMapper.getUsers();
            for (User user : users) {
                System.out.println(user);
            }
        }
    }

2)遇到的问题

java.lang.NoSuchMethodError: org.springframework.beans.factory.config.BeanDefinition.getResolvableType()Lorg/springframework/core/ResolvableType;

	at org.springframework.context.event.AbstractApplicationEventMulticaster.supportsEvent(AbstractApplicationEventMulticaster.java:311)
	at org.springframework.context.event.AbstractApplicationEventMulticaster.retrieveApplicationListeners(AbstractApplicationEventMulticaster.java:243)
  • 检查maven中的spring-jdbc和spring-webmvc是否版本一致。此处使用5.2.0.RELEASE版本

3)使用SqlSession

在 MyBatis 中,你可以使用 SqlSessionFactory 来创建 SqlSession。 一旦你获得一个 session 之后,你可以使用它来执行映射了的语句,提交或回滚连接,最后,当不再需要它的时候,你可以关闭 session。 使用 MyBatis-Spring 之后,你不再需要直接使用 SqlSessionFactory 了,因为你的 bean 可以被注入一个线程安全的 SqlSession,它能基于 Spring 的事务配置来自动提交、回滚、关闭 session。

  • SqlSessionTemplate

SqlSessionTemplate 是 MyBatis-Spring 的核心。作为 SqlSession 的一个实现,这意味着可以使用它无缝代替你代码中已经在使用的 SqlSessionSqlSessionTemplate 是线程安全的,可以被多个 DAO 或映射器所共享使用。

具体见11.1基本步骤

<!-- 【重点】使用SqlSessionTemplate来创建我们要使用的SqlSession,
    此处采用sqlSessionFactory作为构造方法的参数创建SqlSessionTemplate对象。 -->
<bean id="sqlSession" class="org.mybatis.spring.SqlSessionTemplate">
    <constructor-arg index="0" ref="sqlSessionFactory"/>
</bean>
  • SqlSessionDaoSupport

SqlSessionDaoSupport 是一个抽象的支持类,用来为你提供 SqlSession。调用 getSqlSession() 方法你会得到一个 SqlSessionTemplate,之后可以用于执行 SQL 方法。

package com.gaowl.dao;

import com.gaowl.pojo.User;
import java.util.List;
import org.mybatis.spring.support.SqlSessionDaoSupport;

public class UserMapperImpl2 extends SqlSessionDaoSupport implements UserMapper{

    @Override
    public List<User> getUsers() {
        return getSqlSession().getMapper(UserMapper.class).getUsers();
    }
}
<bean id="UserMapper2" class="com.gaowl.dao.UserMapperImpl2">
    <!--此处传入sqlSessionFactory给父类,相当于原先SqlSessionTemplate创建,现在SqlSessionDaoSupport创建,
    省略了实现类中的set方法之类的,也没方便多少,看着用吧-->
    <property name="sqlSessionFactory" ref="sqlSessionFactory"/>
 </bean>

十二、事务

1)事务简介

要么都成功,要么都失败。有效地确保了完整性和一致性(非常重要!!)。

事务的ACID原则:

  • 原子性、一致性、隔离性(多个业务操作同一个资源时相互隔离)、持久性

2)spring中的事务管理

  • 声明式事务(不改变原有的代码)

    MyBatis-Spring 借助了 Spring 中的 DataSourceTransactionManager 来实现事务管理,在spring-dao.xml中配置如下:

    <!-- 配置声明式事务 -->
    <!-- 为事务管理器指定的 DataSource 必须和用来创建 SqlSessionFactoryBean 的是同一个数据源,否则事务管理器就无法工作了。 -->
    <bean id="transaction" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <constructor-arg ref="dataSource"/>
    </bean>
    
    
    
    <!-- 结合aop实现事务的织入 -->
    <!-- 配置事务通知 -->
    <tx:advice id="txAdvice" transaction-manager="transaction">
        <!-- 指定要配置事务的方法,事务的传播特性(总共7种) -->
        <tx:attributes>
            <tx:method name="add" propagation="REQUIRED"/>
            <tx:method name="delete" propagation="REQUIRED"/>
            <tx:method name="update" propagation="REQUIRED"/>
            <tx:method name="query" read-only="true"/>
            <tx:method name="*" propagation="REQUIRED"/>
        </tx:attributes>
    </tx:advice>
    
    <!-- 配置事务切入 -->
    <aop:config>
        <aop:pointcut id="txPointCut" expression="execution(* com.gaowl.dao.*.*(..))"/>
        <aop:advisor advice-ref="txAdvice" pointcut-ref="txPointCut"/>     <!-- 该包下的所有方法均配置事务-->
    </aop:config>
  • 编程式事务(需要改变代码,不建议使用)

本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!