Spring

The grass seeks her crowd in the earth.
The tree seeks his solitude of the sky.

绿草求她地上的伴侣。                       
树木求他天空的寂寞。 

Spring

Spring框架是由于软件开发的复杂性而创建的。Spring使用的是基本的JavaBean来完成以前只可能由EJB完成的事情。然而,Spring的用途不仅仅限于服务器端的开发。从简单性、可测试性和松耦合性角度而言,绝大部分Java应用都可以从Spring中受益。

Spring是一个轻量级控制反转(IOC)哥面向切面(AOP)的容器框架

点击跳转Spring官方地址

点击跳转Spring官方下载地址

点击跳转Spring官方GitHub地址

配置

使用maven配置spring-webmvc spring-jdbc

点击跳转Spring-Maven配置

优点

  1. Spring是一个开源的免费的框架(容器)
  2. Spring是一个轻量级的、非入侵式框架
  3. AOP编程的支持
  4. IOC编程的支持
  5. 支持事务的处理、对框架整合的支持

缺点

配置十分繁琐

组成

IOC理论

IOC是Inversion of Control的缩写,即为控制反转

IOC 容器具有依赖注入功能的容器,它可以创建对象,IOC 容器负责实例化、定位、配置应用程序中的对象及建立这些对象间的依赖。通常new一个实例,控制权由程序员控制,而”控制反转”是指new实例工作不由程序员来做而是交给Spring容器来做。

  1. 耦合的对象
  1. 解耦的过程

  2. 理想的系统

    在实现A的时候,根本无须再去考虑B、C和D了,对象之间的依赖关系已经降低到了最低程度

控制反转是一种通过获得描述(XML或注解)并通过第三方去生产或获取的特定对象的方式,在Spring中实现控制反转的是IOC容器,其实现方法是依赖注入dependency injection(DI)

程序说明

实体类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
package com.bobo.pojo;
public class Hello {
private String str;
public String getStr() {
return str;
}
public void setStr(String str) {
this.str = str;
}
@Override
public String toString() {
return "Hello{" +
"str='" + str + '\'' +
'}';
}
}
IOC-XML配置

applicationContext.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<?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
https://www.springframework.org/schema/beans/spring-beans.xsd">

<bean id="..." class="...">
<!-- collaborators and configuration for this bean go here -->
</bean>
<bean id="..." class="...">
<!-- collaborators and configuration for this bean go here -->
</bean>
<!-- more bean definitions go here -->
</beans>

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<?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
https://www.springframework.org/schema/beans/spring-beans.xsd">
<!--使用spring来创建对象,在Spring这些都称为Bean
类型 变量名 = new 类型;
bean = 对象 new Hello();
id = 变量名
class = new 的对象;
property 相当于给对象中的属性设置一个值
-->
<bean id="hello" class="com.bobo.pojo.Hello">
<!-- collaborators and configuration for this bean go here -->
<property name="str" value="Spring"></property>
</bean>
<!-- more bean definitions go here -->
</beans>
实例化容器

提供给ApplicationContext构造函数的位置路径是资源字符串,允许容器从各种外部资源(如本地文件系统、Java CLASSPATH等)加载配置元数据。

获取Spring的上下文对象

1
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");

1
2
3
4
5
6
7
8
9
10
11
import com.bobo.pojo.Hello;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class MyTest {
public static void main(String[] args) {
//获取Spring的上下文对象
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
//我们的对象现在都在Spring中管理了,我们要使用,直接去里面取出来
Hello hello =(Hello)context.getBean("hello");
System.out.println(hello.toString());
}
}
  • hello对象是由Spring创建的,hello对象的属性是由Spring设置的

Bean作用域

当在 Spring 中定义一个 bean 时,你必须声明该 bean 的作用域的选项。

作用域 描述
singleton 在spring IoC容器仅存在一个Bean实例,Bean以单例方式存在,默认值
prototype 每次从容器中调用Bean时,都返回一个新的实例,即每次调用getBean()时,相当于执行newXxxBean()
request 每次HTTP请求都会创建一个新的Bean,该作用域仅适用于WebApplicationContext环境
session 同一个HTTP Session共享一个Bean,不同Session使用不同的Bean,仅适用于WebApplicationContext环境
global-session 一般用于Portlet应用环境,该作用域仅适用于WebApplicationContext环境

singleton 作用域

singleton 是默认的作用域

当一个bean的作用域为 Singleton,那么 Spring IoC 容器中只会存在一个共享的 bean 实例,并且所有对 bean 的请求,只要 id 与该 bean 定义相匹配,则只会返回 bean 的同一实例。

也就是说,当将一个 bean 定义设置为 singleton 作用域的时候,Spring IoC 容器只会创建该 bean 定义的唯一实例

Singleton 是单例类型,就是在创建起容器时就同时自动创建了一个 bean 的对象,不管你是否使用,他都存在了,每次获取到的对象都是同一个对象。注意,Singleton 作用域是 Spring 中的缺省作用域。

prototype 作用域

当一个 bean 的作用域为 Prototype,表示一个 bean 定义对应多个对象实例。Prototype 作用域的 bean 会导致在每次对该 bean 请求(将其注入到另一个 bean 中,或者以程序的方式调用容器的 getBean() 方法)时都会创建一个新的 bean 实例。

Prototype 是原型类型它在我们创建容器的时候并没有实例化,而是当我们获取bean的时候才会去创建一个对象,而且我们每次获取到的对象都不是同一个对象。根据经验,对有状态的 bean 应该使用 prototype 作用域,而对无状态的bean则应该使用 singleton 作用域。

IOC创建对象的方式

在创建ApplicationContext实例时,bean已经被注册

例:

User

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
package com.bobo.pojo;
public class User {
private String name;
public User() {
System.out.println("User的无参构造");
}
//有参构造
public User(String name){
this.name=name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public void show(){
System.out.println("name+" + name);
}
}

MyTest.java

1
2
3
4
5
6
7
8
9
10
11
12
import com.bobo.pojo.User;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class MyTest {
public static void main(String[] args) {
//在配置文件加载的时候,容器中管理的对象就已经初始化了
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
User user = (User) context.getBean("user");
user.show();
}
}
  1. 使用无参构造创建对象(默认)

    beans.xml

    1
    2
    3
    4
    <!--无参构造创建对象-->
    <bean id="user" class="com.bobo.pojo.User">
    <property name="name" value="bobo"></property>
    </bean>

    输出结果

    1
    2
    User的无参构造
    name+bobo
  2. 使用有参构造创建对象

    1. 第一种:下标赋值

      applicationContext.xml

      1
      2
      3
      4
       <!--第一种:下标赋值-->
      <bean id="user" class="com.bobo.pojo.User">
      <constructor-arg index="0" value="第一种有参构造 "></constructor-arg>
      </bean>

      输出结果

      1
      name+第一种有参构造
    2. 第二种:类型赋值 多个相同类型不建议使用

      applicationContext.xml

      1
      2
      3
      4
      <!--第二种:类型赋值 多个相同类型不建议使用-->
      <bean id="user" class="com.bobo.pojo.User">
      <constructor-arg type="java.lang.String" value="第二种有参构造"/>
      </bean>

      基本类型可以直接用,引用类型必须写全限定名

      输出结果

      1
      name+第二种有参构造
    3. 第三种:直接通过参数名

      applicationContext.xml

      1
      2
      3
      <bean id="user" class="com.bobo.pojo.User">
      <constructor-arg name="name" value="第三种有参构造"/>
      </bean>

      输出结果

      1
      name+第三种有参构造

取别名

  1. alias标签
1
2
<!--给对象取别名-->
<alias name="user" alias="userbobo"/>
  1. 创建bean时通过name属性取别名,可以取多个别名,用多种方法分割,

    u1 u2 u3 u4 都是user的别名

1
2
3
<bean id="user" class="com.bobo.pojo.User" name="u1,u2 u3;u4">
<property name="name" value="bobo"></property>
</bean>

DI依赖注入

Spring框架的核心功能之一就是通过依赖注入的方式来管理Bean之间的依赖关系。

基于构造函数的依赖注入

当容器调用带有一组参数的类构造函数时,基于构造函数的 DI 就完成了,其中每个参数代表一个对其他类的依赖。

使用有参构造创建对象

DI.java

1
2
3
4
5
6
7
8
9
10
11
package com.bobo.dao;
public class DI {
private IDI idi;
public DI(IDI idi){
System.out.println("IDI被注入");
this.idi = idi;
}
public void diCheck(){
idi.checkIDI();
}
}

IDI.java

1
2
3
4
5
6
7
8
9
package com.bobo.dao;
public class IDI {
public IDI(){
System.out.println("IDI注入");
}
public void checkIDI() {
System.out.println("Inside checkIDI." );
}
}
1
2
3
4
<bean id="di" class="com.bobo.dao.DI">
<constructor-arg ref="idi"/>
</bean>
<bean id="idi" class="com.bobo.dao.IDI"/>

上面这个例子里,将依赖类 IDI.java注入到DI.java 文件,便称为依赖注入。

基于设值函数的依赖注入(重点)

依赖:bean对象的创建依赖于容器

注入:bean对象的所有属性,由容器来注入

Student.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
package com.bobo.pojo;
import java.util.*;
public class Student {
private String name;
private Address address;
private String[] books;
private List<String> hobbies;
private Map<String,String> card;
private Set<String> games;
private String wife;
private Properties info;

public String getWife() {
return wife;
}
public void setWife(String wife) {
this.wife = wife;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Address getAddress() {
return address;
}
public void setAddress(Address address) {
this.address = address;
}
public String[] getBooks() {
return books;
}
public void setBooks(String[] books) {
this.books = books;
}
public List<String> getHobbies() {
return hobbies;
}
public void setHobbies(List<String> hobbies) {
this.hobbies = hobbies;
}
public Map<String, String> getCard() {
return card;
}
public void setCard(Map<String, String> card) {
this.card = card;
}

public Set<String> getGames() {
return games;
}
public void setGames(Set<String> games) {
this.games = games;
}
public Properties getInfo() {
return info;
}
public void setInfo(Properties info) {
this.info = info;
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", address=" + address.toString() +
", books=" + Arrays.toString(books) +
", hobbies=" + hobbies +
", card=" + card +
", games=" + games +
", wife='" + wife + '\'' +
", info=" + info +
'}';
}
}

Address.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
package com.bobo.pojo;
public class Address {
private String address;
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
@Override
public String toString() {
return "Address{" +
"address='" + address + '\'' +
'}';
}
}

注入

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
<?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"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="address" class="com.bobo.pojo.Address">
<property name="address" value="西安"/>
</bean>
<bean id="student" class="com.bobo.pojo.Student">
<!--普通注入 value-->
<property name="name" value="bobo"/>
<!--bean注入 ref-->
<property name="address" ref="address"/>
<!--数组注入-->
<property name="books">
<array>
<value>红楼梦</value>
<value>水浒传</value>
<value>三国演义</value>
<value>西游记</value>
</array>
</property>
<!--List注入-->
<property name="hobbies">
<list>
<value>听歌</value>
<value>敲代码</value>
<value>看电影</value>
</list>
</property>
<!--Map-->
<property name="card">
<map>
<entry key="身份证" value="123123123123123123"/>
<entry key="银行卡" value="456456456456456456"/>
</map>
</property>
<!--Set-->
<property name="games">
<set>
<value>SCII</value>
<value>BOB</value>
</set>
</property>
<!--null-->
<property name="wife">
<null/>
</property>
<!--properites-->
<property name="info">
<props>
<prop key="学号">123123123</prop>
<prop key="性别">1</prop>
</props>
</property>
</bean>
</beans>

拓展方式注入

  1. c注入

    必须有有参构造器

    1
    2
    <!--c命名空间注入,通过构造器注入 : constructs-args-->
    <bean id="user2" class="com.bobo.pojo.User" c:age="18" c:name="bobo"/>
  2. p注入

    p 命名空间注入

    1
    2
    3
    <!--p命名注入,可以直接注入属性的值 :property-->
    <!--scope="singleton" 单例模式-->
    <bean id="user" class="com.bobo.pojo.User" p:name="bobo" scope="singleton"/>

注意

c 、 p 命名空间注入不能直接使用,需要导入约束

1
2
xmlns:p="http://www.springframework.org/schema/p" 
xmlns:c="http://www.springframework.org/schema/c"

SpringBean 自动装配

Spring 容器可以在不使用<constructor-arg><property> 元素的情况下自动装配相互协作的 bean 之间的关系。

显式装配

1
2
3
4
5
6
7
><bean id="cat" class="com.bobo.pojo.Cat"/>
><bean id="dog" class="com.bobo.pojo.Dog"/>
><bean id="people" class="com.bobo.pojo.People">-->
<property name="name" value="bobo"></property>-->
<property name="dog" ref="dog"></property>-->
<property name="cat" ref="cat"></property>-->
></bean>-->
  1. Spring 自动装配 byName

    1. 这种模式由属性名称指定自动装配。Spring 容器看作 beans,在 XML 配置文件中 beans 的 auto-wire 属性设置为 byName
    2. 然后,它尝试将它的属性与配置文件中定义为相同名称的 beans 进行匹配和连接。
    3. 如果找到匹配项,它将注入这些 beans,否则,它将抛出异常。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    <bean id="cat" class="com.bobo.pojo.Cat"/>
    <bean id="dog" class="com.bobo.pojo.Dog"/>
    <!--隐式装配 autowire="byName"
    会在容器中上下文中查找,和自己对象set方法后面的值对应的beanid!
    需要保证所有bean的id唯一,并且这个bean需要和自动注入的属性的set方法的值一致
    -->
    <bean id="people" class="com.bobo.pojo.People" autowire="byName">
    <property name="name" value="bobo"/>
    </bean>

    使用byName,需要保证所有bean的id唯一,并且这个bean需要和自动注入的属性的set方法一致

  2. Spring 自动装配 byType

    1. 这种模式由属性类型指定自动装配。Spring 容器看作 beans ,在 XML 配置文件中 beans 的 autowire 属性设置为 byType 。
    2. 然后,如果它的 type 恰好与配置文件中 beans 名称中的一个相匹配,它将尝试匹配和连接它的属性。
    3. 如果找到匹配项,它将注入这些 beans ,否则,它将抛出异常。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    <bean id="cat" class="com.bobo.pojo.Cat"/>
    <bean id="dog" class="com.bobo.pojo.Dog"/>
    <!--隐式装配 autowire="byType"
    会在容器中上下文中查找,和自己对象属性类型相同的beanid!
    要保证类型全局唯一
    需要保证所有bean的class唯一,并且这个bean需要和自动注入的属性的类型一致
    -->
    <bean id="people" class="com.bobo.pojo.People" autowire="byType">
    <property name="name" value="bobo"/>
    </bean>
    • 即使

      1
      <bean id="dog111" class="com.bobo.pojo.Dog"/>

      对象名称不匹配,也可以根据类型找到对应的bean

    要保证类型全局唯一
    需要保证所有bean的class唯一,并且这个bean需要和自动注入的属性的类型一致

  3. Spring 注解 自动装配

点击跳转注解配置

@Autowire 默认通过byType的方式实现 ,如果存在多个类型相同的,通过byname实现,必须要求这个对象存在
@Resource 默认通过byname的方式实现 ,如果找不到名字,则通过byType实现 ! 如果两个都找不到的情况下报错

People.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
package com.bobo.pojo;
import lombok.Data;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;

@Data
@AllArgsConstructor
@NoArgsConstructor
public class People {

@Autowired
private Cat cat;
//@Qualifier 允许beans 中对象名 与类中id与类型都不同
@Autowired
@Qualifier(value = "dogdog")
private Dog dog;
private String name;
}

applicationContext.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<?xml version="1.0" encoding="UTF-8"?>
<!--context、aop 支持注解装配-->
<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.bobo.pojo.Cat"/>
<bean id="dogdog" class="com.bobo.pojo.Dog"/>
<bean id="people" class="com.bobo.pojo.People"/>

<!--@Autowired 与 @Resource区别
都是用来自动装配的
@Autowire 默认通过byType的方式实现 ,如果存在多个类型相同的,通过byname实现,必须要求这个对象存在
@Resource 默认通过byname的方式实现 ,如果找不到名字,则通过byType实现 ! 如果两个都找不到的情况下报错
-->

</beans>

使用注解开发

使用注解开发,必须要保证aop的包导入

点击跳转使用注解开发

有两种方法:

1
2
3
4
<!--制定扫描包,包下注解会生效-->
<context:component-scan base-package="com.bobo"/>

<context:annotation-config/>

使用component-scan

<context:component-scan base-package="com.bobo"/>

1
2
3
4
5
6
7
8
9
10
11
package com.bobo.pojo;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
//@Component 等价于 <bean id="user" class="com.bobo.pojo.User"></bean>
@Component
public class User {
//@Value("bobo") 相当于 <property name="name" value="bobo"></property>
@Value("bobo")
public String name;

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
package com.bobo.pojo;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
//@Component 等价于 <bean id="user" class="com.bobo.pojo.User"></bean>
@Component
public class User {
//@Value("bobo") 相当于 <property name="name" value="bobo"></property>
public String name;
//也可以注入在set方法上
@Value("bobo")
public void setName(String name){
this.name = name;
}
}

@Component衍生注解

  1. @Repository 与 @Component功能相似,作用在dao层

    1
    2
    3
    4
    5
    6
    package com.bobo.dao;
    import org.springframework.stereotype.Repository;
    //@Repository 与 @Component功能相似,作用在dao层
    @Repository
    public class UserDao {
    }
  2. @Service 与 @Component功能相似,作用在service层

    1
    2
    3
    4
    5
    6
    package com.bobo.service;
    import org.springframework.stereotype.Service;
    //@Service 与 @Component功能相似,作用在service层
    @Service
    public class UserService {
    }
  3. @Controller 与 @Component功能相似,作用在controller层

    1
    2
    3
    4
    5
    6
    package com.bobo.controller;
    import org.springframework.stereotype.Controller;
    //@Controller 与 @Component功能相似,作用在controller层
    @Controller
    public class UserController {
    }

使用@Scope

作用就和在bean标签中使用scope属性实现的功能是一样的

1
@Scope

用于指定bean的作用范围

1
@Value

指定范围的取值

使用Java的方式配置Spring

点击跳转基于Java注解的配置

@import、@Configuration 和 @Bean 注解

@Configuration 注解类表示这个类可以使用 Spring IoC 容器作为 bean 定义的来源。

@Bean 注解告诉 Spring,一个带有 @Bean 的注解方法将返回一个对象,该对象应该被注册为在 Spring 应用程序上下文中的 bean。

@import 注解允许从另一个配置类中加载 @Bean 定义

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
package com.bobo.config;
import com.bobo.pojo.User;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.stereotype.Component;

//@Configuration 相当于<beans>
//@Configuration 代表这是一个配置累 与beans.xml是一样的
@Configuration
@ComponentScan("com.bobo.pojo")
public class boboConfig {

//注册一个bean,就相当于我门之前写的一个bean标签
//这个方法的名字,就相当于bean标签中的id属性
//这个方法的返回值就相当于bean标签中的class属性
@Bean
public User user(){
return new User(); //就是返回要注入到bean 的对象
}
}

这个方法的返回值就相当于bean标签中的class属性,所以在测试类中可以识别到User类

带有 @Bean 注解的方法名称作为 bean 的 ID,它创建并返回实际的 bean。你的配置类可以声明多个 @Bean。

一旦定义了配置类,你就可以使用 AnnotationConfigApplicationContext来加载并把他们提供给 Spring 容器,如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
import com.bobo.config.boboConfig;
import com.bobo.pojo.User;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
public class MyTest {
public static void main(String[] args) {
//如果完全使用了配类方式去做,只能通过AnnotationConfig上下文来获取容器,通过配置类的class对象加载
ApplicationContext context = new AnnotationConfigApplicationContext(boboConfig.class);
User getUser = (User) context.getBean("user");
System.out.println(user.getName());
}
}

AOP框架

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

点击跳转Spring-AOP配置

Spring 框架的一个关键组件是面向切面的编程(AOP)框架。

跨一个应用程序的多个点的功能被称为横切关注点,这些横切关注点在概念上独立于应用程序的业务逻辑。在软件开发过程中有各种各样的很好的切面的例子,如日志记录、审计、声明式事务、安全性和缓存等。

AOP 术语

描述
Aspect(切面) 一个模块具有一组提供横切需求的 APIs。例如,一个日志模块为了记录日志将被 AOP 方面调用。应用程序可以拥有任意数量的方面,这取决于需求。
Join point(连接点) 在你的应用程序中它代表一个点,你可以在插件 AOP 方面。你也能说,它是在实际的应用程序中,其中一个操作将使用 Spring AOP 框架。
Advice(通知) 这是实际行动之前或之后执行的方法。这是在程序执行期间通过 Spring AOP 框架实际被调用的代码。
Pointcut(切入点) 这是一组一个或多个连接点,通知应该被执行。你可以使用表达式或模式指定切入点正如我们将在 AOP 的例子中看到的。
Introduction 引用允许你添加新方法或属性到现有的类中。
Target object(被通知对象) 被一个或者多个方面所通知的对象,这个对象永远是一个被代理对象。也称为被通知对象。
Weaving Weaving 把方面连接到其它的应用程序类型或者对象上,并创建一个被通知的对象。这些可以在编译时,类加载时和运行时完成。
Proxy(代理) 向目标对象应用通知之后创建的对象

通知的类型

通知 描述
前置通知 在一个方法执行之前,执行通知。
后置通知 在一个方法执行之后,不考虑其结果,执行通知。
返回后通知 在一个方法执行之后,只有在方法成功完成时,才能执行通知。
抛出异常后通知 在一个方法执行之后,只有在方法退出抛出异常时,才能执行通知。
环绕通知 在建议方法调用之前和之后,执行通知。

基于 AOP 的 XML架构

在xml中使用 aop 命名空间标签,你需要导入 spring-aop 架构

测试类

1
2
3
4
5
6
7
8
9
10
11
12
13
import com.bobo.service.UserService;
import com.bobo.service.UserServiceImpl;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class MyTest {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
//动态代理 代理的是接口
UserService userService = context.getBean("userService", UserService.class);
userService.add();
}
}

方式一 自定义类

  1. 声明一个 aspect

    一个 aspect 是使用 元素声明的,支持的 bean 是使用 ref 属性引用的

    1
    2
    3
    4
    5
    6
    7
    8
    <aop:config>
    <aop:aspect id="myAspect" ref="aBean">
    ...
    </aop:aspect>
    </aop:config>
    <bean id="aBean" class="...">
    ...
    </bean>
  2. 声明一个切入点

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    <aop:config>
    <aop:aspect id="myAspect" ref="aBean">
    <aop:pointcut id="businessService"
    expression="execution(* com.bobo.service..*.*(..))"/>
    ...
    </aop:aspect>
    </aop:config>
    <bean id="aBean" class="...">
    ...
    </bean>
    • 一个切入点表达式决定了我们感兴趣的哪个方法会真正被执行。
    • 一个切入点标签包含一个名称和任意数量的参数。方法的真正内容是不相干的,并且实际上它应该是空的。
  3. 1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    <bean id="userService" class="com.bobo.service.UserServiceImpl"/>
    <bean id="diy" class="com.bobo.diy.DiyPointCut"/>
    <aop:config>
    <!--自定义切面,ref要引用的类-->
    <aop:aspect ref="diy">
    <!--切入点-->
    <aop:pointcut id="point" expression="execution(* com.bobo.service.UserServiceImpl.*(..) )"/>
    <!--通知-->
    <aop:before method="before" pointcut-ref="point"/>
    <aop:after method="after" pointcut-ref="point"/>
    </aop:aspect>
    </aop:config>

    Class DiyPointCut

    1
    2
    3
    4
    5
    6
    7
    8
    9
    package com.bobo.diy;
    public class DiyPointCut{
    public void before(){
    System.out.println("=============方法执行前=============");
    }
    public void after(){
    System.out.println("=============方法执行后=============");
    }
    }

    测试结果:

方式二 使用原生Spring API接口

  1. 声明一个通知器

    定义< aop:advisor >中引用的通知时,通知必须实现Advice接口

    1. 通知前环绕

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      package com.bobo.log;
      import org.springframework.aop.MethodBeforeAdvice;
      import java.lang.reflect.Method;
      public class Log implements MethodBeforeAdvice {
      //method 要执行的目标对象的方法
      //Object 参数
      @Override
      public void before(Method method, Object[] args, Object target) throws Throwable {
      System.out.println(target.getClass().getName()+"的"+method.getName()+"被执行了");
      }
      }
    2. 通知后环绕

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      package com.bobo.log;
      import org.springframework.aop.AfterAdvice;
      import org.springframework.aop.AfterReturningAdvice;
      import java.lang.reflect.Method;
      public class AfterLog implements AfterReturningAdvice {
      //returnValue 返回值
      @Override
      public void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable{
      System.out.println("执行了"+method.getName()+"方法,返回结果为:"+returnValue);
      }
      }

    配置XML

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    <!--注册bean-->
    <bean id="userService" class="com.bobo.service.UserServiceImpl"/>
    <bean id="log" class="com.bobo.log.Log"/>
    <bean id="afterLog" class="com.bobo.log.AfterLog"/>

    <!-- 配置aop:需要导入aop的约束-->
    <aop:config>
    <aop:pointcut id="pointcut" expression="execution(* com.bobo.service.UserServiceImpl.*(..) )"/>
    <!--执行环绕增加-->
    <aop:advisor advice-ref="log" pointcut-ref="pointcut"/>
    <aop:advisor advice-ref="afterLog" pointcut-ref="pointcut"/>
    </aop:config>

    测试结果:

方式三 使用注解配置

配置XML,开启注解支持

1
<aop:aspectj-autoproxy/>
  1. 声明一个 aspect

    1
    2
    3
    4
    5
    package com.bobo;
    import org.aspectj.lang.annotation.Aspect;
    @Aspect
    public class AspectModule {
    }
  2. 声明一个切入点

    1
    2
    3
    import org.aspectj.lang.annotation.Pointcut;
    @Pointcut("execution(* com.bobo.service.UserServiceImpl.*(..))")
    private void method() {}
  3. 1
    2
    3
    4
    <bean id="userService" class="com.bobo.service.UserServiceImpl"/>
    <bean id="AnnotationPointCut" class="com.bobo.diy.AnnotationPointCut"/>
    <!--开启注解支持 JDK(默认)proxy-target-class="false" cglib:proxy-target-class="true"-->
    <aop:aspectj-autoproxy/>
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    package com.bobo;
    import org.aspectj.lang.ProceedingJoinPoint;
    import org.aspectj.lang.Signature;
    import org.aspectj.lang.annotation.After;
    import org.aspectj.lang.annotation.Around;
    import org.aspectj.lang.annotation.Aspect;
    import org.aspectj.lang.annotation.Before;
    import org.springframework.context.annotation.Bean;

    @Aspect //标注这个类是一个切面
    public class AnnotationPointCut {

    @Before("execution(* com.bobo.service.UserServiceImpl.*(..))")
    public void before(){
    System.out.println("=============方法执行前=============");
    }

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

    @Around("execution(* com.bobo.service.UserServiceImpl.*(..))")
    public void around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
    System.out.println("环绕前");
    //执行方法
    Object proceed = proceedingJoinPoint.proceed();
    System.out.println("环绕后");
    }
    }

    ProceedingJoinPoint proceedingJoinPoint代表当前执行的方法

    测试结果:

注意

  1. aop:aspectop:advisor不同

    1. 实现方式不同

      aop:aspect定义切面时,只需要定义一般的bean就行,而定义aop:advisor中引用的通知时,通知必须实现Advice接口。

    2. 使用场景不同

      aop:advisor大多用于事务管理

  2. execution表达式

    1
    execution(* com.bobo.service..*. *(..))
    1. 第一个 * 表示返回类型, *号表示所有的类型
    2. 包名:表示需要拦截的包名,后面的两个句点表示当前包和当前包的所有子包,com.bobo.service包、子孙包下所有类的方法 (“..”出现在类名中时,后面必须跟“*”)
    3. 第二个 * 号:表示类名,*号表示所有的类
    4. *(..):最后这个星号表示方法名,*号表示所有的方法,后面括弧里面表示方法的参数,两个句点表示任何参数

整合Mybatis

整合Mybatis步骤官方中文文档

点击跳转Mybatis详解

导入相关jar包 点击跳转Spring-Mybatis Maven配置

  1. 要和 Spring 一起使用 MyBatis,需要在 Spring 应用上下文中定义至少两样东西:一个 SqlSessionFactory 和至少一个数据映射器类。

    在 MyBatis-Spring 中,可使用 SqlSessionFactoryBean来创建 SqlSessionFactory

    1. 1
      2
      3
      4
      <!--sqlSessionFactory-->
      <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
      <property name="dataSource" ref="dataSource"/>
      </bean>
    1
    2
    3
    4
    5
    6
    <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
    <property name="driverClassName" value="com.mysql.cj.jdbc.Driver"/>
    <property name="url" value="jdbc:mysql://localhost:3306/数据库名?useSSL=false&amp;serverTimezone=UTC&amp;characterEncoding=utf8&amp;useUnicode=true"/>
    <property name="username" value="用户名"/>
    <property name="password" value="密码"/>
    </bean>
    1. 1
      2
      3
      4
      5
      <!--SqlSessionTemplate 就是我们使用的sqlSession-->
      <bean id="sqlSession" class="org.mybatis.spring.SqlSessionTemplate">
      <!--只能使用构造器注入sqlSessionFactory ,因为没有set方法-->
      <constructor-arg index="0" ref="sqlSessionFactory"/>
      </bean>
    1. SqlSessionTemplate 是 MyBatis-Spring 的核心。作为 SqlSession 的一个实现,这意味着可以使用它无缝代替你代码中已经在使用的 SqlSessionSqlSessionTemplate 是线程安全的,可以被多个 DAO 或映射器所共享使用。

    2. 当调用 SQL 方法时(包括由 getMapper() 方法返回的映射器中的方法),SqlSessionTemplate 将会保证使用的 SqlSession 与当前 Spring 的事务相关。 此外,它管理 session 的生命周期,包含必要的关闭、提交或回滚操作。另外,它也负责将 MyBatis 的异常翻译成 Spring 中的 DataAccessExceptions

  2. 现在,这个 sqlSession 就可以直接注入到你的 Mapper bean 中了。你需要在你的 bean 中添加一个 SqlSession 属性,就像下面这样

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    package com.bobo.mapper;
    import com.bobo.pojo.User;
    import org.mybatis.spring.SqlSessionTemplate;
    import java.util.List;
    public class UserMapperImpl implements UserMapper{

    //我们的所有操作都使用sqlSession来执行,在原来,现在我们都使用SqlSessionTrmplate
    private SqlSessionTemplate sqlSession;
    public void setSqlSession(SqlSessionTemplate sqlSession){
    this.sqlSession=sqlSession;
    }
    }
  3. 按下面这样,注入 SqlSessionTemplate

    1
    2
    3
    <bean id="userMapper" class="com.bobo.mapper.UserMapperImpl">
    <property name="sqlSession" ref="sqlSession"/>
    </bean>
  4. 现在所有的映射语句可以进行批量操作了,可以在 Mapper.xml 中编写如下的代码

    1
    2
    3
    4
    5
    <mapper namespace="com.bobo.mapper.UserMapper">
    <select id="selectUser" resultType="user">
    select * from mybatis.user;
    </select>
    </mapper>
  5. 测试类

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    @Test
    public void test(){
    ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");

    UserMapper userMapper = context.getBean("userMapper", UserMapper.class);

    for (User user : userMapper.selectUser()) {
    System.out.println(user);
    }
    }

完整实例

pojo.User

1
2
3
4
5
6
7
8
9
package com.bobo.pojo;
import lombok.Data;

@Data
public class User {
private int id;
private String name;
private String pwd;
}

Mapper.UserMapper

1
2
3
4
5
6
package com.bobo.mapper;
import com.bobo.pojo.User;
import java.util.List;
public interface UserMapper {
public List<User> selectUser();
}

Mapper.UserMapper.xml

1
2
3
4
5
6
7
8
9
<?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.bobo.mapper.UserMapper">
<select id="selectUser" resultType="com.bobo.pojo.User">
select * from mybatis.user;
</select>
</mapper>

Mapper.UserMapperImpl

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
package com.bobo.mapper;
import com.bobo.pojo.User;
import org.mybatis.spring.SqlSessionTemplate;
import java.util.List;
public class UserMapperImpl implements UserMapper{

//我们的所有操作都使用sqlSession来执行,在原来,现在我们都使用SqlSessionTrmplate
private SqlSessionTemplate sqlSession;

public void setSqlSession(SqlSessionTemplate sqlSession){
this.sqlSession=sqlSession;
}
@Override
public List<User> selectUser() {
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
return mapper.selectUser();
}
}

resources.mybatis-config.xml

1
2
3
4
5
6
7
8
9
10
11
12
<?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>
<typeAliases>
<package name="com.bobo.pojo"/>
</typeAliases>
<!--<mappers>-->
<!-- <mapper class="com.bobo.mapper.UserMapper"/>-->
<!--</mappers>-->
</configuration>

resources.spring-dao.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop
https://www.springframework.org/schema/aop/spring-aop.xsd">

<!--DataSource:使用Spring的数据源替换Mybatis的配置
使用Spring提供的JDBC
-->
<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName" value="com.mysql.cj.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306数据库名?useSSL=false&amp;serverTimezone=UTC&amp;characterEncoding=utf8&amp;useUnicode=true"/>
<property name="username" value=""用户名/>
<property name="password" value="密码"/>
</bean>

<!--sqlSessionFactory-->
<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/bobo/mapper/UserMapper.xml"/>
</bean>

<!--SqlSessionTemplate 就是我们使用的sqlSession-->
<bean id="sqlSession" class="org.mybatis.spring.SqlSessionTemplate">
<!--只能使用构造器注入sqlSessionFactory ,因为没有set方法-->
<constructor-arg index="0" ref="sqlSessionFactory"/>
</bean>
</beans>

resources.applicationContext.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop
https://www.springframework.org/schema/aop/spring-aop.xsd">

<import resource="spring-dao.xml"/>
<bean id="userMapper" class="com.bobo.mapper.UserMapperImpl">
<property name="sqlSession" ref="sqlSession"/>
</bean>
</beans>

MyTest

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
import com.bobo.mapper.UserMapper;
import com.bobo.pojo.User;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import java.io.IOException;
import java.io.InputStream;
import java.util.List;
public class MyTest {

@Test
public void test(){
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");

UserMapper userMapper = context.getBean("userMapper", UserMapper.class);

for (User user : userMapper.selectUser()) {
System.out.println(user);
}
}
}

SqlSessionDaoSupport

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
package com.bobo.mapper;
import com.bobo.pojo.User;
import org.apache.ibatis.session.SqlSession;
import org.mybatis.spring.support.SqlSessionDaoSupport;
import java.util.List;

public class UserMapperImpl extends SqlSessionDaoSupport implements UserMapper{
@Override
public List<User> selectUser() {
SqlSession sqlSession = getSqlSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
return mapper.selectUser();
}
}

事务

一个使用 MyBatis-Spring 的其中一个主要原因是它允许 MyBatis 参与到 Spring 的事务管理中。而不是给 MyBatis 创建一个新的专用事务管理器,MyBatis-Spring 借助了 Spring 中的 DataSourceTransactionManager 来实现事务管理。

标准配置

要开启 Spring 的事务处理功能,在 Spring 的配置文件中创建一个 DataSourceTransactionManager 对象:

1
2
3
4
<!--配置声明式事务-->
<bean id="transcationManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<constructor-arg ref="dataSource"/>
</bean>

传入的 DataSource 可以是任何能够与 Spring 兼容的 JDBC DataSource。包括连接池和通过 JNDI 查找获得的 DataSource

注意:为事务管理器指定的 DataSource 必须和用来创建 SqlSessionFactoryBean 的是同一个数据源,否则事务管理器就无法工作了。

声明式事务

(交由容器管理事务)

配置

1
2
3
4
<!--配置声明式事务-->
<bean id="transcationManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<constructor-arg ref="dataSource"/>
</bean>

结合AOP实现事务的织入

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<!--配置事务的类 通知-->
<tx:advice id="txAdvice" transaction-manager="transcationManager">
<!--给哪些方法配置事务-->
<!--配置事务的传播特性-->
<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.bobo.mapper.*.*(..))"/>
<aop:advisor advice-ref="txAdvice" pointcut-ref="txPointCut"/>
</aop:config>
查看评论