Featured image of post Spring学习笔记

Spring学习笔记

用来记录Spring的学习心得

花了3天时间,Spring也终于看完了,SSM已经干完了两个了,感觉人都要没了😇。还是老样子,留下笔记以免遗忘。

下面的内容都是基于遇见狂神说Spring5视频教程而来,在此感谢能提供这么优秀的教程。

# Spring

# 简介

Spring就是用来简化Java开发的,只能说学Java不学Spring,就像读四大名著不读红楼梦……

2002年,Rod Jahnson首次推出了Spring框架雏形interface21框架。

2004年3月24日,Spring框架以interface21框架为基础,经过重新设计,发布了1.0正式版。

很难想象Rod Johnson的学历 , 他是悉尼大学的博士,然而他的专业不是计算机,而是音乐学。

Spring理念 : 使现有技术更加实用。 本身就是一个大杂烩 , 整合现有的框架技术。

SSM:SpringMVC+Spring+MyBatis

几个常用的网址:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
<!-- https://mvnrepository.com/artifact/org.springframework/spring-webmvc -->
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-webmvc</artifactId>
    <version>5.3.9</version>
</dependency>

<!-- https://mvnrepository.com/artifact/org.springframework/spring-jdbc -->
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-jdbc</artifactId>
    <version>5.3.9</version>
</dependency>

# 优点

  • Spring是一个开源免费的框架 , 容器

  • Spring是一个轻量级的框架 , 非侵入式的

  • 控制反转(IoC),面向切面(Aop)

  • 对事务的支持 , 对框架的支持

一句话概括:

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

# 拓展

  • Spring Boot
    • 一套快速配置脚手架
    • 可以基于Spring Boot 快速开发单个微服务
  • Spring Cloud
    • Spring Cloud是基于Spring Boot实现的

学习路线:Spring -> SpringMVC -> Spring Boot -> Spring Cloud

# IOC理论推导

原先的开发模式:

  1. 先写一个UserDao接口

    1
    2
    3
    
    public interface UserDao {
        public void getUser();
    }
    
  2. 再去写Dao的实现类

    1
    2
    3
    4
    5
    6
    
    public class UserDaoImpl implements UserDao {
        @Override
        public void getUser() {
            System.out.println("获取用户数据");
    	}
    }
    
  3. 然后去写UserService的接口

    1
    2
    3
    
    public interface UserService {
        public void getUser();
    }
    
  4. 最后写Service的实现类

    1
    2
    3
    4
    5
    6
    7
    8
    
    public class UserServiceImpl implements UserService {
        private UserDao userDao = new UserDaoImpl();
    
        @Override
        public void getUser() {
            userDao.getUser();
        }
    }
    
  5. 测试一下

    1
    2
    3
    4
    5
    6
    
    @Test
    public void test(){
        //用户实际调用的是业务层,dao层他们并不需要接触!
        UserService service = new UserServiceImpl();
        service.getUser();
    }
    

如果需要更换Dao层的实现方法,每次都需要在UserServiceImpl里手动修改new的对象,这样做十分麻烦。

现在可以使用set接口实现

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
public class UserServiceImpl implements UserService {
   	private UserDao userDao;
    
	// 利用set进行动态实现值的注入
   	public void setUserDao(UserDao userDao) {
		this.userDao = userDao;
	}

	@Override
	public void getUser() {
		userDao.getUser();
	}
}
  • 以前所有东西都是由程序去进行控制创建。
  • 而现在是由我们自行控制创建对象,把主动权交给了调用者。程序不用去管怎么创建,怎么实现了,它只负责提供一个接口。

这种思想,从本质上解决了问题,我们程序员不再去管理对象的创建了,更多的去关注业务的实现。耦合性大大降低, 这也就是IOC的原型!

# IOC本质

控制反转IoC(Inversion of Control),是一种设计思想,DI(依赖注入)是实现IoC的一种方法,也有人认为DI只是IoC的另一种说法。没有IoC的程序中 , 我们使用面向对象编程 , 对象的创建与对象间的依赖关系完全硬编码在程序中,对象的创建由程序自己控制,控制反转后将对象的创建转移给第三方,个人认为所谓控制反转就是:获得依赖对象的方式反转了。

IoC图示

Spring的工作原理:

container magic

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

# 第一个Spring程序

  1. 添加Maven依赖

    1
    2
    3
    4
    5
    6
    7
    8
    
    <dependencies>
        <!-- https://mvnrepository.com/artifact/org.springframework/spring-webmvc -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-webmvc</artifactId>
            <version>5.3.9</version>
        </dependency>
    </dependencies>
    
  2. pojo包下创建实体类Hello

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    
    package top.lbqaq.pojo;
    
    /**
     * @author luoboQAQ
     * @Date 2021/8/12 上午 10:49
     */
    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 + '\'' +
                    '}';
        }
    }
    
  3. resources目录下编写Spring的配置文件

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    
    <?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就是java对象 , 由Spring创建和管理
        原来是 类型  变量名 = new 类型();
              Hello hello = new Hello();
        现在 id=变量名 class=new的对象 property是给变量赋的初值
        -->
        <bean id="hello" class="top.lbqaq.pojo.Hello">
            <!--引用另外一个bean , 不是用value 而是用 ref-->
            <property name="str" value="Spring"/>
        </bean>
    
    </beans>
    
  4. 进行测试

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    
    import org.springframework.context.ApplicationContext;
    import org.springframework.context.support.ClassPathXmlApplicationContext;
    import top.lbqaq.pojo.Hello;
    
    /**
     * @author luoboQAQ
     * @Date 2021/8/12 上午 10:53
     */
    public class MyTest {
        public static void main(String[] args) {
            //获取Spring的上下文对象
            ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
            //我们的对象都在Spring中管理了,我们要使用直接从中取即可
            Hello hello = (Hello) context.getBean("hello");
            System.out.println(hello);
        }
    }
    

现在Hello对象由Spring来创建和管理,这个过程就叫控制反转

  • 控制:谁来控制对象的创建,传统应用程序的对象是由程序本身控制创建的,使用Spring后,对象是由Spring来创建的。

  • 反转:程序本身不创建对象,而变成被动的接收对象。

依赖注入:就是利用set方法来进行注入的。

IOC是一种编程思想,由主动的编程变成被动的接收。

# IOC创建对象方式

  1. 通过无参构造函数(默认)

  2. 通过有参构造函数,有三种方式

    • 下标赋值

      1
      2
      3
      
      <bean id="user" class="top.lbqaq.pojo.User">
          <constructor-arg index="0" value="小明"/>
      </bean>
      
    • 用类型赋值(不推荐)

      1
      2
      3
      
      <bean id="user" class="top.lbqaq.pojo.User">
          <constructor-arg type="java.lang.String" value="小明"/>
      </bean>
      
    • 通过参数名创建

      1
      2
      3
      
      <bean id="user" class="top.lbqaq.pojo.User">
          <constructor-arg name="name" value="小明"/>
      </bean>
      

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

# Spring配置

# 别名(alias)

1
2
<!--设置别名:在获取Bean的时候可以使用别名获取-->
<alias name="user" alias="userNew"/>

# Bean的配置

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
<!--bean就是java对象,由Spring创建和管理-->

<!--
   id 是bean的标识符,要唯一,如果没有配置id,name就是默认标识符
   如果配置id,又配置了name,那么name是别名
   name可以设置多个别名,可以用逗号,分号,空格隔开
   如果不配置id和name,可以根据applicationContext.getBean(.class)获取对象;

class是bean的全限定名=包名+类名
-->
<bean id="hello" name="hello2 h2,h3;h4" class="top.lbqaq.pojo.Hello">
   <property name="name" value="Spring"/>
</bean>

# import

一般用于团队开发,将多个配置文件合为一个配置文件applicationContext.xml

1
<import resource="{path}/beans.xml"/>

# 依赖注入

# 概念

  • 依赖注入(Dependency Injection,DI)。
  • 依赖 : 指Bean对象的创建依赖于容器,Bean对象的依赖资源。
  • 注入 : 指Bean对象所依赖的资源,由容器来设置和装配。

# 构造器注入

见前面的IOC创建对象方式

# set注入【重点】

# 测试环境

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
public class Address {
    private String address;

    public String getAddress() {
        return address;
    }

    public void setAddress(String address) {
        this.address = address;
    }
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
public class Student {
    private String name;
    private Address address;
    private String[] books;
    private List<String> hobbys;
    private Map<String,String> card;
    private Set<String> games;
    private String wife;
    private Properties info;
}

# 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
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
<?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="top.lbqaq.pojo.Address">
        <property name="address" value="西安"/>
    </bean>

    <bean id="student" class="top.lbqaq.pojo.Student">
        <!--普通值注入,value-->
        <property name="name" value="小明"/>

        <!--Bean注入,ref-->
        <property name="address" ref="address"/>

        <!--数组注入-->
        <property name="books">
            <array>
                <value>红楼梦</value>
                <value>西游记</value>
                <value>水浒传</value>
                <value>三国演义</value>
            </array>
        </property>

        <!--list-->
        <property name="hobbys">
            <list>
                <value>听歌</value>
                <value>看电影</value>
                <value>打游戏</value>
            </list>
        </property>

        <!--map-->
        <property name="card">
            <map>
                <entry key="身份证" value="112233445566778899"/>
                <entry key="银行卡" value="123987879172197"/>
            </map>
        </property>

        <!--Set-->
        <property name="games">
            <set>
                <value>LOL</value>
                <value>CSGO</value>
            </set>
        </property>

        <!--null-->
        <property name="wife">
            <null/>
        </property>

        <!--Properties-->
        <property name="info">
            <props>
                <prop key="driver">20211213</prop>
                <prop key="url"></prop>
                <prop key="username">root</prop>
                <prop key="password">123456</prop>
            </props>
        </property>
    </bean>
</beans>

具体用法见上面的代码就行了。

# 扩展方式注入

# p命名空间

  1. 导入命名空间

    1
    
    xmlns:p="http://www.springframework.org/schema/p"
    
  2. bean标签里直接使用

    1
    
    <bean id="user" class="top.lbqaq.pojo.User" p:age="18" p:name="小明"/>
    

相当于set注入

# c命名空间

  1. 导入命名空间

    1
    
    xmlns:c="http://www.springframework.org/schema/c"
    
  2. bean标签里直接使用

    1
    
    <bean id="user2" class="top.lbqaq.pojo.User" c:age="20" c:name="小红"/>
    

相当于有参构造函数注入

# Bean的作用域

类别 说明
singleton (默认)将每个 Spring IoC 容器的单个 bean 定义范围限定为单个对象实例。
prototype 将单个 bean 定义的作用域限定为任意数量的对象实例。
request 将单个 bean 定义的范围限定为单个 HTTP 请求的生命周期。也就是说,每个 HTTP 请求都有一个在单个 bean 定义后面创建的 bean 实例。仅在可感知网络的 Spring ApplicationContext中有效。
session 将单个 bean 定义的范围限定为 HTTP Session的生命周期。仅在可感知网络的 Spring ApplicationContext上下文中有效。
application 将单个 bean 定义的范围限定为ServletContext的生命周期。仅在可感知网络的 Spring ApplicationContext上下文中有效。
websocket 将单个 bean 定义的范围限定为WebSocket的生命周期。仅在可感知网络的 Spring ApplicationContext上下文中有效。
  • 单例模式(默认):每次获取的都是同一个对象

    1
    
    <bean id="user" class="top.lbqaq.pojo.User" p:age="18" p:name="小明" scope="singleton"/>
    
  • 原型模式:每次get都是从容器中产生一个新对象

    1
    
    <bean id="user" class="top.lbqaq.pojo.User" p:age="18" p:name="小明" scope="prototype"/>
    
  • 其余requestsessionapplicationwebsocket这些只在web开发中使用。

# Bean的自动装配

  • 自动装配是使用spring满足bean依赖的一种方法
  • spring会在应用上下文中为某个bean寻找其依赖的bean。

Spring中bean有三种装配机制,分别是:

  1. 在xml中显式配置
  2. 在java中显式配置
  3. 隐式的bean发现机制和自动装配【重要】

# 环境搭建

1
2
3
4
5
public class Cat {
    public void shout(){
        System.out.println("miao~");
    }
}
1
2
3
4
5
public class Dog {
    public void shout(){
        System.out.println("wang~");
    }
}
 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
public class People {
    private Cat cat;
    private Dog dog;
    private String name;

    public Cat getCat() {
        return cat;
    }

    public void setCat(Cat cat) {
        this.cat = cat;
    }

    public Dog getDog() {
        return dog;
    }

    public void setDog(Dog dog) {
        this.dog = dog;
    }

    public String getName() {
        return name;
    }

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

# ByName自动装配

1
2
3
4
5
6
<bean id="cat" class="top.lbqaq.pojo.Cat"/>
<bean id="dog" class="top.lbqaq.pojo.Dog"/>

<bean id="people" class="top.lbqaq.pojo.People" autowire="byName">
    <property name="name" value="小明"/>
</bean>

会自动在容器上下文中查找,和自己对象set方法后面的值相对应的beanid

# ByType自动装配

1
2
3
4
5
6
<bean id="cat" class="top.lbqaq.pojo.Cat"/>
<bean id="dog" class="top.lbqaq.pojo.Dog"/>

<bean id="people" class="top.lbqaq.pojo.People" autowire="byType">
    <property name="name" value="小明"/>
</bean>

自动在容器上下文中查找,和自己对象属性类型相同的Bean

小结:

  • byname要保证所有的bean的id唯一,并且这个bean需要和自动注入的属性的set方法的值一致
  • bytype的时候,需要保证bean的class唯一,并且这个bean需要和自动注入的属性的类型一致

# 使用注解自动装配

# 准备工作

  1. 导入aop包

  2. 导入约束

    1
    2
    3
    4
    
    xmlns:context="http://www.springframework.org/schema/context"
    
    http://www.springframework.org/schema/context
    http://www.springframework.org/schema/context/spring-context.xsd
    
  3. 配置注解支持<context:annotation-config/>

# @Autowired

  • 在属性或在set方法上使用。

  • 使用Autowired可以不需要set方法。

  • @Autowired是按类型自动转配的。

  • @Autowired(required=false) 说明对象可以为null

# @Qualifier

  • 如果环境比较复杂,自动装配无法通过一个@Autowired完成,可以通过@Qualifier(value="xxx")来实现。

  • @Qualifier相当于byName

1
2
3
4
5
6
@Autowired
@Qualifier(value = "cat2")
private Cat cat;
@Autowired
@Qualifier(value = "dog2")
private Dog dog;

# @Resource

  • @Resource如有指定的name属性,先按该属性进行byName方式查找装配;
  • 其次再进行默认的byName方式进行装配;
  • 如果以上都不成功,则按byType的方式自动装配。
  • 都不成功,则报异常。
1
2
3
4
5
6
7
8
public class User {
   //如果允许对象为null,设置required = false,默认为true
   @Resource(name = "cat2")
   private Cat cat;
   @Resource
   private Dog dog;
   private String str;
}

# 小结

@Autowired与@Resource异同:

  1. @Autowired与@Resource都可以用来装配bean。都可以写在字段上,或写在setter方法上。

  2. @Autowired默认按类型装配(属于spring规范),默认情况下必须要求依赖对象必须存在,如果要允许null 值,可以设置它的required属性为false,如:@Autowired(required=false) ,如果我们想使用名称装配可以结合@Qualifier注解进行使用

  3. @Resource(属于J2EE复返),默认按照名称进行装配,名称可以通过name属性进行指定。如果没有指定name属性,当注解写在字段上时,默认取字段名进行按照名称查找,如果注解写在setter方法上默认取属性名进行装配。当找不到与名称匹配的bean时才按照类型进行装配。但是需要注意的是,如果name属性一旦指定,就只会按照名称进行装配。

它们的作用相同都是用注解方式注入对象,但执行顺序不同。@Autowired先byType,@Resource先byName。

# 使用注解开发

使用注解开发需要导入aop的包,导入约束和配置注解支持

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

        <!--指定要扫描的包,这个包下的注解就会生效-->
        <context:component-scan base-package="top.lbqaq.pojo"/>
        <context:annotation-config/>
</beans>

# Bean的实现

1
2
3
4
@Component
public class User {
    public String name = "小明";
}

使用@Component注解,相当于配置文件中的<bean id="user" class="当前注解的类"/>

注意需要在配置文件中指定要扫描的包<context:component-scan base-package="top.lbqaq.pojo"/>

# 属性注入

使用@Value注解,相当于配置文件中的<property name="name" value="小明"/>

可以在声明上加,也可以在set方法上加

1
2
3
4
5
@Component
public class User {
    @Value("小明")
    public String name;
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
@Component
public class User {

   public String name;

   @Value("小明")
   public void setName(String name) {
       this.name = name;
  }
}

# 衍生注解

对于@Component注解,有三个衍生注解,分别对应MVC的三层。

  • Dao层:@Repository
  • Service层:@Service
  • Web层:@Controller

这四个注解的作用都是代表将某个类注册到Spring中,装配Bean

# 自动装配注解

  • @Autowired:按类型自动转配
  • @Qualifier(value="xxx"):按name自动装配
  • @Resource:Java提供的,先name再type,比较复杂

详细的解释可以看上面

# 作用域

@Scope

  • singleton:单例模式
  • prototype:多例模式
1
2
3
4
5
6
@Component
@Scope("prototype")
public class User {
    @Value("小明")
    public String name;
}

# 小结

XML与注解比较

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

推荐用法

  • xml管理Bean
  • 注解完成属性注入

# 使用Java类进行配置

实体类

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
public class User {
    private String name;

    @Override
    public String toString() {
        return "User{" +
                "name='" + name + '\'' +
                '}';
    }

    public String getName() {
        return name;
    }

    @Value("小明")
    public void setName(String name) {
        this.name = name;
    }
}

配置类

1
2
3
4
5
6
7
@Configuration
public class LbqaqConfig {
    @Bean
    public User getUser(){
        return new User();
    }
}

这里的@Configuration表明这是Spring的配置

@Bean是用来注册bean,这里的返回值就Bean的类型,方法名就是bean的id

测试

1
2
3
4
5
6
7
8
public class MyTest {
    @Test
    public void test1(){
        ApplicationContext context = new AnnotationConfigApplicationContext(LbqaqConfig.class);
        User user = (User) context.getBean("getUser");
        System.out.println(user.getName());
    }
}

# 代理模式

AOP的底层机制就是动态代理。

# 静态代理

角色分析

  • 抽象角色:一般使用接口或者抽象类来实现
  • 真实角色:被代理的角色
  • 代理角色:代理真实角色。代理真实角色后 , 一般会做一些附属的操作。
  • 客户:使用代理角色来进行一些操作。

举例:

Rent.java即抽象角色

1
2
3
4
//抽象角色:租房
public interface Rent {
    public void rent();
}

Host.java即真实角色

1
2
3
4
5
6
//真实角色: 房东,房东要出租房子
public class Host implements Rent{
    public void rent() {
        System.out.println("房屋出租");
    }
}

Proxy.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
//代理角色:中介
public class Proxy implements Rent {

    private Host host;
    public Proxy() { }
    public Proxy(Host host) {
        this.host = host;
    }

    //租房
    public void rent(){
        seeHouse();
        host.rent();
        fare();
    }
    //看房
    public void seeHouse(){
        System.out.println("带房客看房");
    }
    //收中介费
    public void fare(){
        System.out.println("收中介费");
    }
}

Client.java即客户

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
//客户类,一般客户都会去找代理!
public class Client {
    public static void main(String[] args) {
        //房东要租房
        Host host = new Host();
        //中介帮助房东
        Proxy proxy = new Proxy(host);

        //你去找中介!
        proxy.rent();
    }
}

分析:

在这个过程中,你直接接触的就是中介,就如同现实生活中的样子,你看不到房东,但是你依旧租到了房东的房子通过代理,这就是所谓的代理模式。

静态代理的好处:

  • 可以使得我们的真实角色更加纯粹,不再去关注一些公共的事情。
  • 公共的业务由代理来完成,实现了业务的分工。
  • 公共业务发生扩展时变得更加集中和方便。

缺点:

  • 类多了,多了代理类,工作量变大了,开发效率降低。

我们在不改变原来的代码的情况下,实现了对原有功能的增强,这是AOP中最核心的思想。

AOP

# 动态代理

  • 动态代理的角色和静态代理的一样。

  • 动态代理的代理类是动态生成的,静态代理的代理类是我们提前写好的

  • 动态代理分为两类:一类是基于接口动态代理,一类是基于类的动态代理

    • 基于接口的动态代理—-JDK动态代理
    • 基于类的动态代理–cglib
    • 现在用的比较多的是 javasist 来生成动态代理 . 百度一下javasist
    • 我们这里使用JDK的原生代码来实现,其余的道理都是一样的!

JDK的动态代理需要了解两个类

核心 : InvocationHandlerProxy

代码实现:

1
2
3
4
//租房
public interface Rent {
    void rent();
}
1
2
3
4
5
6
7
8
//房东
public class Host implements Rent {

    @Override
    public void rent() {
        System.out.println("房东要出租");
    }
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
public class ProxyInvocationHandler implements InvocationHandler {
    //被代理的接口
    private Rent rent;

    public void setRent(Rent rent) {
        this.rent = rent;
    }

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

    @Override
    //处理代理实例,并返回结果
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        //动态代理就是依赖反射实现
        Object result = method.invoke(rent, args);
        return result;
    }
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
public class Client {
    public static void main(String[] args) {
        //真实角色
        Host host=new Host();

        ProxyInvocationHandler pih = new ProxyInvocationHandler();
        pih.setRent(host);
        Rent proxy = (Rent) pih.getProxy();
        proxy.rent();
    }
}

动态代理代理接口

通用的代码

 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
public class ProxyInvocationHandler implements InvocationHandler {
    private Object target;

    public void setTarget(Object target) {
        this.target = target;
    }

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

    // proxy : 代理类
    // method : 代理类的调用处理程序的方法对象.
    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 methodName) {
        System.out.println("执行了" + methodName + "方法");
    }

}

# AOP

# 什么是AOP

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

AOP

通俗点说,就是在不改变原有代码的情况下去增加新的功能。

# AOP在Spring中

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

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

2

SpringAOP中,通过Advice定义横切逻辑,Spring中支持5种类型的Advice:

Advice

# 使用Spring实现AOP

导入依赖包

1
2
3
4
5
6
7
<!-- https://mvnrepository.com/artifact/org.aspectj/aspectjweaver -->
<dependency>
    <groupId>org.aspectj</groupId>
    <artifactId>aspectjweaver</artifactId>
    <version>1.9.7</version>
    <scope>runtime</scope>
</dependency>

# 方式一:使用Spring的方法

业务接口和实现类

1
2
3
4
5
6
public interface UserService {
    void add();
    void delete();
    void update();
    void select();
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
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 select() {
        System.out.println("查询一个用户");
    }
}

写两个增强类

1
2
3
4
5
6
7
8
9
public class Log implements MethodBeforeAdvice {
    //method : 要执行的目标对象的方法
    //objects : 被调用的方法的参数
    //o : 目标对象
    @Override
    public void before(Method method, Object[] objects, Object o) throws Throwable {
        System.out.println(o.getClass().getName()+"的"+method.getName()+"被执行了");
    }
}
1
2
3
4
5
6
7
public class AfterLog implements AfterReturningAdvice {
    //o:返回值
    @Override
    public void afterReturning(Object o, Method method, Object[] objects, Object o1) throws Throwable {
        System.out.println("执行了"+method.getName()+"方法,返回结果为"+o);
    }
}

配置xml,这里需要引入aop命名空间

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<?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
       https://www.springframework.org/schema/aop/spring-aop.xsd">

    <!--注册Bean-->
    <bean id="userService" class="top.lbqaq.service.UserServiceImpl"/>
    <bean id="log" class="top.lbqaq.log.Log"/>
    <bean id="afterLog" class="top.lbqaq.log.AfterLog"/>

    <!--配置aop:需要导入aop约束-->
    <aop:config>
        <!--切入点:expression:表达式 execution()-->
        <aop:pointcut id="pointcut" expression="execution(* top.lbqaq.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>

execution表达式解析:

  • execution(修饰符 返回值 包名.类名/接口名.方法名(参数列表))
  • 修饰符可以忽略
  • (..)可以代表所有参数,(*)代表一个参数,(*,String)代表第一个参数为任何值,第二个参数为String类型

最后测试

1
2
3
4
5
6
7
8
public class MyTest {
    @Test
    public void test(){
        ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
        UserService userService = (UserService) context.getBean("userService");
        userService.select();
    }
}

# 方法二:使用自定义类来实现

创建自定义类

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

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

配置xml

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
<bean id="diy" class="top.lbqaq.diy.DiyPointCut"/>
<aop:config>
    <!--自定义切面,ref为要引用的类-->
    <aop:aspect ref="diy">
        <!--切入点-->
        <aop:pointcut id="point" expression="execution(* top.lbqaq.service.UserServiceImpl.*(..))"/>
        <!--通知-->
        <aop:before method="before" pointcut-ref="point"/>
        <aop:after method="after" pointcut-ref="point"/>
    </aop:aspect>
</aop:config>

# 方式三:使用注解来实现AOP

  1. 在类上标注注解@Aspect,如果没有该注解,就在maven配置里把<scope>runtime</scope>去掉

  2. 在方法前加上@Before()@After()注解,并在括号中填入表达式

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    
    //使用注解实现AOP
    @Aspect
    public class AnnotationPointCut {
        @Before("execution(* top.lbqaq.service.UserServiceImpl.*(..))")
        public void before(){
            System.out.println("=====方法执行前=====");
        }
    
        @After("execution(* top.lbqaq.service.UserServiceImpl.*(..))")
        public void after(){
            System.out.println("=====方法执行后=====");
        }
    
        //在环绕增强中,我们可以给定一个参数,代表我们要获取处理切入的点
        @Around("execution(* top.lbqaq.service.UserServiceImpl.*(..))")
        public void around(ProceedingJoinPoint jp) throws Throwable {
            System.out.println("环绕前");
    
            //执行方法
            Object proceed = jp.proceed();
    
            System.out.println("环绕后");
        }
    }
    
  3. 在Spring配置文件注册Bean并开启注解支持

    1
    2
    3
    
    <bean id="annotationPointCut" class="top.lbqaq.diy.AnnotationPointCut"/>
    <!--开启注解支持-->
    <aop:aspectj-autoproxy/>
    

# 整合MyBatis

首先要导入相关jar包

 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
<dependencies>
    <!-- junit -->
    <dependency>
        <groupId>junit</groupId>
        <artifactId>junit</artifactId>
        <version>4.13</version>
        <scope>test</scope>
    </dependency>
    <!-- mysql驱动 -->
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <version>8.0.25</version>
        <scope>runtime</scope>
    </dependency>
    <!-- mybatis -->
    <dependency>
        <groupId>org.mybatis</groupId>
        <artifactId>mybatis</artifactId>
        <version>3.5.7</version>
    </dependency>
    <!-- mybatis-spring -->
    <dependency>
        <groupId>org.mybatis</groupId>
        <artifactId>mybatis-spring</artifactId>
        <version>2.0.6</version>
    </dependency>
    <!-- spring框架 -->
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-webmvc</artifactId>
        <version>5.3.9</version>
    </dependency>
    <!-- spring-jdbc -->
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-jdbc</artifactId>
        <version>5.3.9</version>
    </dependency>
    <!-- AOP 织入器 -->
    <dependency>
        <groupId>org.aspectj</groupId>
        <artifactId>aspectjweaver</artifactId>
        <version>1.9.7</version>
    </dependency>
</dependencies>

# 回顾MyBatis

  1. 编写实体类

    1
    2
    3
    4
    5
    6
    7
    
    public class User {
        private int id;
        private String name;
        private String pwd;
        //setter,getter
        //toString,构造
    }
    
  2. 编写Mybatis配置文件

     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
    
    <?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="top.lbqaq.pojo"/>
        </typeAliases>
    
        <environments default="development">
            <environment id="development">
                <transactionManager type="JDBC"/>
                <dataSource type="POOLED">
                    <property name="driver" value="com.mysql.cj.jdbc.Driver"/>
                    <property name="url" value="jdbc:mysql://localhost:3306/mybatis"/>
                    <property name="username" value="root"/>
                    <property name="password" value="root"/>
                </dataSource>
            </environment>
        </environments>
        <!--每一个mapper都需要注册-->
        <mappers>
            <mapper class="top.lbqaq.mapper.UserMapper"/>
        </mappers>
    
    </configuration>
    
  3. 编写接口

    1
    2
    3
    
    public interface UserMapper {
        List<User> selectUser();
    }
    
  4. 编写接口对应的xml文件

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    
    <?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="top.lbqaq.mapper.UserMapper">
    
        <select id="selectUser" resultType="User">
            select * from user
        </select>
    
    </mapper>
    
  5. 解决Maven静态资源过滤问题

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    
    <build>
       <resources>
           <resource>
               <directory>src/main/java</directory>
               <includes>
                   <include>**/*.properties</include>
                   <include>**/*.xml</include>
               </includes>
               <filtering>true</filtering>
           </resource>
       </resources>
    </build>
    
  6. 测试

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    
    public class MyTest {
        @Test
        public void test() throws IOException {
            String resource = "mybatis-config.xml";
            InputStream inputStream = Resources.getResourceAsStream(resource);
            SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
            SqlSession sqlSession = sqlSessionFactory.openSession();
    
            UserMapper mapper = sqlSession.getMapper(UserMapper.class);
    
            List<User> userList = mapper.selectUser();
            for (User user: userList){
                System.out.println(user);
            }
    
            sqlSession.close();
        }
    }
    

# MyBatis-Spring

MyBatis-Spring 会帮助你将 MyBatis 代码无缝地整合到 Spring 中。官方文档

# 整合实现一

  1. 引入Spring配置文件spring-dao.xml

    1
    2
    3
    4
    
    <?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">
    
  2. 配置数据源

    1
    2
    3
    4
    5
    6
    7
    8
    9
    
    <!--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/mybatis"/>
        <property name="username" value="root"/>
        <property name="password" value="root"/>
    </bean>
    
  3. 配置SqlSessionFactory,关联MyBatis

    1
    2
    3
    4
    5
    6
    
    <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:top/lbqaq/mapper/*.xml"/>
    </bean>
    
  4. 注册sqlSessionTemplate,关联sqlSessionFactory

    1
    2
    3
    4
    
    <bean id="sqlSession" class="org.mybatis.spring.SqlSessionTemplate">
        <!--使用构造器注入,因为它没有set方法-->
        <constructor-arg index="0" ref="sqlSessionFactory"/>
    </bean>
    
  5. 增加Mapper接口的实现类

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    
    public class UserMapperImpl implements UserMapper{
        //原来我们的所有操作,都使用sqlSession来执行;现在都使用SqlSessionTemplate
        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();
        }
    }
    
  6. 将实现类注册到Spring中

    1
    2
    3
    
    <bean id="userMapper" class="top.lbqaq.mapper.UserMapperImpl">
        <property name="sqlSession" ref="sqlSession"/>
    </bean>
    
  7. 测试

    1
    2
    3
    4
    5
    6
    7
    8
    
    @Test
    public void test2(){
        ApplicationContext context = new ClassPathXmlApplicationContext("spring-dao.xml");
        UserMapper userMapper = context.getBean("userMapper", UserMapper.class);
        for (User user : userMapper.selectUser()) {
            System.out.println(user);
        }
    }
    

此时再去看MyBatis的配置文件,里面大部分配置都被Spring整合了

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
<?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="top.lbqaq.pojo"/>
    </typeAliases>
</configuration>

这里建议将typeAliasessettings留在MyBatis的配置文件中。

以后可以将Spring配置文件拆开,spring-dao.xml用来处理数据库相关代码,applicationContext.xml作为总配置文件。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
<?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="spring-dao.xml"/>

    <bean id="userMapper" class="top.lbqaq.mapper.UserMapperImpl">
        <property name="sqlSession" ref="sqlSession"/>
    </bean>
    
</beans>

# 整合方式二

使用SqlSessionDaoSupport

  1. 修改实现类,继承SqlSessionDaoSupport

    1
    2
    3
    4
    5
    6
    
    public class UserMapperImpl2 extends SqlSessionDaoSupport implements UserMapper{
        @Override
        public List<User> selectUser() {
            return getSqlSession().getMapper(UserMapper.class).selectUser();
        }
    }
    
  2. 配置Bean

    1
    2
    3
    
    <bean id="userMapper2" class="top.lbqaq.mapper.UserMapperImpl2">
        <property name="sqlSessionFactory" ref="sqlSessionFactory"/>
    </bean>
    
  3. 测试

    1
    2
    3
    4
    5
    6
    7
    8
    
    @Test
    public void test2(){
        ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
        UserMapper userMapper = context.getBean("userMapper2", UserMapper.class);
        for (User user : userMapper.selectUser()) {
            System.out.println(user);
        }
    }
    

使用这种方法,就不需要上面的第4步了,步骤就更加简洁了。

# 声明式事务

spring中的事务:

  • 编程式事务:在代码中进行事务的管理
  • 声明式事务:AOP

一般我们都采用声明式事务,将事务管理作为横切关注点,通过aop方法模块化。

使用方法:

  1. 引入头文件约束tx

    1
    2
    3
    4
    
    xmlns:tx="http://www.springframework.org/schema/tx"
    
    http://www.springframework.org/schema/tx
    http://www.springframework.org/schema/tx/spring-tx.xsd"
    
  2. 配置事务管理器

    1
    2
    3
    4
    
    <!--配置声明式事务-->
    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource" />
    </bean>
    
  3. 配置事务通知

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    
    <!--配置事务通知-->
    <tx:advice id="txAdvice" transaction-manager="transactionManager">
        <!--给那些方法配置事务-->
        <tx:attributes>
            <!--配置事务的传播特性 propagation-->
            <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>
    

    Spring的事务的传播特性一共有7种:

    • propagation_requierd:如果当前没有事务,就新建一个事务,如果已存在一个事务中,加入到这个事务中,这是最常见的选择。
    • propagation_supports:支持当前事务,如果没有当前事务,就以非事务方法执行。
    • propagation_mandatory:使用当前事务,如果没有当前事务,就抛出异常。
    • propagation_required_new:新建事务,如果当前存在事务,把当前事务挂起。
    • propagation_not_supported:以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。
    • propagation_never:以非事务方式执行操作,如果当前事务存在则抛出异常。
    • propagation_nested:如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则执行与propagation_required类似的操作

    一般第一种就够用了。

  4. 配置AOP

    注意要导入头文件依赖aop,详细的内容在上面,或者IDEA也会自动补全

    1
    2
    3
    4
    5
    
    <!--配置事务切入-->
    <aop:config>
        <aop:pointcut id="txPointCut" expression="execution(* top.lbqaq.mapper.*(..))"/>
        <aop:advisor advice-ref="txAdvice" pointcut-ref="txPointCut"/>
    </aop:config>
    
Licensed under CC BY-NC-SA 4.0
最后更新于 2021-08-14 16:30:00