March 2, 2023

Spring

Spring简介

Spring的英文翻译为春天,可以说是给Java程序员带来了春天,因为它极大的简化了开发。我得出一个公式:Spring = 春天 = Java程序员的春天 = 简化开发。最后的简化开发正是Spring框架带来的最大好处。
Spring是一个开放源代码的设计层面框架,它是于2003 年兴起的一个轻量级的Java 开发框架。由Rod Johnson创建,其前身为Interface21框架,后改为了Spring并且正式发布。Spring是为了解决企业应用开发的复杂性而创建的。它解决的是业务逻辑层和其他各层的松耦合问题,因此它将面向接口的编程思想贯穿整个系统应用。框架的主要优势之一就是其分层架构,分层架构允许使用者选择使用哪一个组件,同时为 J2EE 应用程序开发提供集成的框架。Spring使用基本的JavaBean来完成以前只可能由EJB完成的事情。然而,Spring的用途不仅限于服务器端的开发。从简单性、可测试性和松耦合的角度而言,任何Java应用都可以从Spring中受益。简单来说,Spring是一个分层的JavaSE/EE full-stack(一站式) 轻量级开源框架。Spring 的理念:不去重新发明轮子。其核心是控制反转(IOC)和面向切面(AOP)。

IOC本质

Spring提供的容器又称为IoC容器,什么是IoC?
IoC全称Inversion of Control,直译为控制反转。那么何谓IoC?在理解IoC之前,我们先看看通常的Java组件是如何协作的。这里我们以JDBC中调用多种数据库为例子
image.png

1
2
3
4
5
6
	package com.boogipop.dao;

public interface UserDao {
void getUser();
}

1
2
3
4
5
6
7
8
9
10
package com.boogipop.dao;

public class UserDaomysqlimp implements UserDao{

@Override
public void getUser() {
System.out.println("mysql:getuser");
}
}

1
2
3
4
5
6
7
8
9
package com.boogipop.dao;

public class UserDaooracleimp implements UserDao{
@Override
public void getUser() {
System.out.println("oracle:getuser");
}
}

1
2
3
4
5
package com.boogipop.service;
public interface Userservice {
void getUser();
}

1
2
3
4
5
6
7
8
9
10
11
12
13
package com.boogipop.service;

import com.boogipop.dao.UserDao;
import com.boogipop.dao.UserDaomysqlimp;

public class Userserviceimp implements Userservice{
private UserDao userDao=new UserDaomysqlimp();
@Override
public void getUser() {
userDao.getUser();
}
}

1
2
3
4
5
6
7
8
9
10
import com.boogipop.service.Userservice;
import com.boogipop.service.Userserviceimp;

public class Execute {
public static void main(String[] args) {
Userserviceimp userservice=new Userserviceimp();
userservice.getUser();
}
}

结果不出意外的话就是调用mysql的sout了:
image.png
这是常规的JDBC调用方法,取决权在服务端,因为userDao属性是由后台设置的,IOC是什么呢,就是把控制权反转,也就是给到客户端,我们修改一下Userserviceimp:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
package com.boogipop.service;

import com.boogipop.dao.UserDao;
import com.boogipop.dao.UserDaomysqlimp;

public class Userserviceimp implements Userservice{
private UserDao userDao;

public void setUserDao(UserDao userDao) {
this.userDao = userDao;
}

@Override
public void getUser() {
userDao.getUser();
}
}

image.png
这次我们可以控制userDao属性来得到想要的输出结果,在这两个例子之间发生了革命性的变化,从服务端主导控制权反转到了客户端set userDao,也就是客户端主导权
这就是控制反转IOC的意思

HelloSpring

上面已经讲了IOC的理论知识,这里就来演示如何用Spring做到IOC,首先让我们先导入pom依赖(有坑)

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
<?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">
<modelVersion>4.0.0</modelVersion>

<groupId>org.example</groupId>
<artifactId>Spring</artifactId>
<version>1.0-SNAPSHOT</version>

<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<!-- https://mvnrepository.com/artifact/org.springframework/spring-core -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>6.0.4</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.springframework/spring-beans -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-beans</artifactId>
<version>6.0.4</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>6.0.4</version>
</dependency>
</dependencies>


</project>

注意我这里的依赖导的是6.0x版本,这个版本的spring框架要求java在17以上,因此我们要注意一下java的版本,否则会出现版本对应错误的报错。
导入依赖后就创建我们的beans的xml文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<?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="mysql" class="com.boogipop.dao.UserDaomysqlimp"/>
<bean id="oracle" class="com.boogipop.dao.UserDaooracleimp"/>
<!--
ref:引用容器中创建好的对象,如mysql和oracle
value: 直接引用值
-->
<bean id="Userserviceimp" class="com.boogipop.service.Userserviceimp">
<property name="userDao" ref="mysql"/>
</bean>
</beans>

首先什么是JavaBean呢?在java中假如一个类符合如下规范,例如:

1
2
3
4
5
6
7
8
9
10
public class Person {
private String name;
private int age;

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

public int getAge() { return this.age; }
public void setAge(int age) { this.age = age; }
}

如果set和get都满足上述实例,那么这样的类就被我们称为JavaBean,我们的beans.xml中的Userserviceimp就满足了这一点,定义好了beans文件就可以通过它来创建我们想要的对象了:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import com.boogipop.service.Userserviceimp;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class Execute {
public static void main(String[] args) {
// 获取Applicationcontetxt
ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
// 有了容器啥都有了
Userserviceimp userserviceimp = (Userserviceimp) context.getBean("Userserviceimp");
userserviceimp.getUser();
}

}

// mysql:getuser

在这里也是和上述JDBC发生了一样的变化,现在控制权反转到了客户手上,用户只需要输入配置文件,我们就可以根据配置文件来提供对应的服务,这就是Spring实现IOC

IOC创建对象方式

上述例子我们bean类都是用默认的无参构造实例化的,而面对有参构造该如何去实现呢?

方式一(通过类型,不推荐)

1
2
3
4
5
6
7
8
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="user" class="com.boogipop.pojo.User">
<constructor-arg type="java.lang.String" value="Boogipop"/>
</bean>
</beans>
1
2
3
4
5
6
7
8
9
10
11
12
import com.boogipop.pojo.User;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class Execute {
public static void main(String[] args) {
ApplicationContext context=new ClassPathXmlApplicationContext("beans.xml");
User user = (User) context.getBean("user");
System.out.println(user);
}
}

image.png
行云流水一气呵成,这种方法有瑕疵,就是万一2个形参都是一样的类型就没办法了

方式二(通过索引)

1
2
3
4
5
6
7
8
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="user" class="com.boogipop.pojo.User">
<constructor-arg index="0" value="Boogipop"/>
</bean>
</beans>

通过索引来指定参数,0就代表第一个

方式三(通过形参名称)

1
2
3
4
5
6
7
8
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="user" class="com.boogipop.pojo.User">
<constructor-arg name="name" value="Boogipop"/>
</bean>
</beans>

三种方法任你选择,另外的还有个事情就是Spring是什么时候就帮我们创建好了对象呢?
答案是在beans.xml中写入时就已经实例化了,我们可以添加2个pojo类,写上无参构造方法:

1
2
3
4
5
6
7
8
package com.boogipop.pojo;

public class Admin {
public Admin() {
System.out.println("i am admin");
}
}

1
2
3
4
5
6
7
8
package com.boogipop.pojo;

public class BlackBan {
public BlackBan() {
System.out.println("i am blacklist");
}
}

1
2
3
4
5
6
7
8
9
10
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="user" class="com.boogipop.pojo.User">
<constructor-arg name="name" value="Boogipop"/>
</bean>
<bean id="black" class="com.boogipop.pojo.BlackBan"/>
<bean id="admin" class="com.boogipop.pojo.Admin"/>
</beans>

image.png
最后执行的时候你会发现另外2个虽然我们没用getbean去取出来,但是也实例化了,这就说明spring在读取beans.xml的时候就实例化了所有的类

Spring配置

别名

首先别名分为namealias两种,不过说实话别名一般不用,因为bean的id实际上也就是一种别名啊,因此只有在团队项目中才会用上别名

1
2
3
4
<bean id="admin" class="com.boogipop.pojo.Admin" name="u1 u2,u3;u4;u5"/
<!-- 方式二-->
<bean id="admin" class="com.boogipop.pojo.Admin"/>
<alias name="admin" alias="ADMIN0"/>

import

import也就是上面说的团队项目中会用到的,在test类中导入xml配置文件时也说了,我们可以导入多个beans配置文件,但是为了方便,一般就导入一个
在这一个beans配置文件中也可以导入其他配置文件,用的就是import标签
<import resource="test.xml"/>

DI依赖注入(Dependency Injection)

环境搭建

DI就是依赖注入的意思,前面讲过用构造器去注入(创建对象),同样的还有其他两种方式,接下来介绍的就是这两种,先搭建一个复杂环境

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
package com.boogipop.pojo;

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

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;
//.....下面就是get和set
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
package com.boogipop.pojo;

public class Address {
private String address;

public void setAddress(String address) {
this.address = address;
}

public String getAddress() {
return address;
}
}

Set注入

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
<?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.boogipop.pojo.Address">
<property name="address" value="Chengdu"/>
</bean>
<bean id="student" class="com.boogipop.pojo.Student">
<property name="address" ref="address"/>
<property name="books">
<array>
<value>Internet</value>
<value>Life</value>
</array>
</property>
<property name="card">
<map>
<entry key="ID" value="20213000278"></entry>
<entry key="name" value="Boogipop"></entry>
</map>
</property>
<property name="games">
<set>
<value>LOL</value>
<value>Arknights</value>
</set>
</property>
<property name="hobbys">
<list>
<value>sleep</value>
<value>die</value>
</list>
</property>
<property name="info">
<props>
<prop key="name">Boogipop</prop>
<prop key="age">19</prop>
</props>
</property>
<property name="name" value="Boogipop"/>
<!-- <property name="wife" value=""/>-->
<property name="wife">
<null></null>
</property>
</bean>
</beans>

注意一下设置为null有两种方法,然后我们测试运行一下:

1
2
3
4
5
6
7
8
9
10
11
import com.boogipop.pojo.Student;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class Exec {
public static void main(String[] args) {
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
Student student = context.getBean("student", Student.class);
System.out.println(student);
}
}

在这里可以发现getbean时可以多加一个参数来指定类型,这样我们就不需要强制转换了
Student{name='Boogipop', address=Address{address='Chengdu'}, books=[Internet, Life], hobbys=[sleep, die], card={ID=20213000278, name=Boogipop}, games=[LOL, Arknights], wife='null', info={name=Boogipop, age=19}}
最后结果也是成功输出

c命名空间、p命名空间注入

在这里c对应的是constructor、p对应的是properties
在使用这两个命名空间前需要先声明:

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

将这两条语句假如到开始的xml属性中,声明p和c,然后再创建个普通的user类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
package com.boogipop.pojo;

public class User {
private String name;
private int age;

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

public String getName() {
return name;
}

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

首先先演示一下p命名空间:

1
<bean id="user" class="com.boogipop.pojo.User" p:name="Boogipop" p:age="19"/>

image.png

c命名空间也是一个道理,只不过c命名空间有个前提就是需要对应的构造器,因为本质是通过constructor嘛

1
<bean id="user" class="com.boogipop.pojo.User" c:name="Boogipop" c:age="19"/>

Bean的作用域

Bean的作用域有如下几种

Scope Description
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上下文中有效。

重点区分一下singletonprototype两种,前者就是我们mybatis学的单例模式,所谓单例模式就是在beans中创建的对象只有一个因此假如你getbean获取2次,两次获取的bean都是一模一样的(hashcode相同),而使用prototype获取的每次都是不同的
剩下几种作用域的解释如下:

(1)当一个bean的作用域为Request,表示在一次HTTP请求中,一个bean定义对应一个实例;即每个HTTP请求都会有各自的bean实例,它们依据某个bean定义创建而成。该作用域仅在基于web的Spring ApplicationContext情形下有效。考虑下面bean定义:

<bean id="loginAction" class=com.foo.LoginAction" scope="request"/>
  针对每次HTTP请求,Spring容器会根据loginAction bean的定义创建一个全新的LoginAction bean实例,且该loginAction bean实例仅在当前HTTP request内有效,因此可以根据需要放心的更改所建实例的内部状态,而其他请求中根据loginAction bean定义创建的实例,将不会看到这些特定于某个请求的状态变化。当处理请求结束,request作用域的bean实例将被销毁。

  (2)当一个bean的作用域为Session,表示在一个HTTP Session中,一个bean定义对应一个实例。该作用域仅在基于web的Spring ApplicationContext情形下有效。考虑下面bean定义:

<bean id="userPreferences" class="com.foo.UserPreferences" scope="session"/>
  针对某个HTTP Session,Spring容器会根据userPreferences bean定义创建一个全新的userPreferences bean实例,且该userPreferences bean仅在当前HTTP Session内有效。与request作用域一样,可以根据需要放心的更改所创建实例的内部状态,而别的HTTP Session中根据userPreferences创建的实例,将不会看到这些特定于某个HTTP Session的状态变化。当HTTP Session最终被废弃的时候,在该HTTP Session作用域内的bean也会被废弃掉。

  (3)当一个bean的作用域为Global Session,表示在一个全局的HTTP Session中,一个bean定义对应一个实例。典型情况下,仅在使用portlet context的时候有效。该作用域仅在基于web的Spring ApplicationContext情形下有效。考虑下面bean定义:

<bean id="user" class="com.foo.Preferences "scope="globalSession"/>
  global session作用域类似于标准的HTTP Session作用域,不过仅仅在基于portlet的web应用中才有意义。Portlet规范定义了全局Session的概念,它被所有构成某个portlet web应用的各种不同的portlet所共享。在global session作用域中定义的bean被限定于全局portlet Session的生命周期范围内。

自动装配Bean

原始自动装配

上面的例子中我们都是使用标签对属性进行手动装配的,而spring可以实现自动装配,自动装配也分为注解和原始2种,这里先讲原始的
原始自动装配可以根据byNamebyType来进行装配,首先是byName:

byName

定义一个people接口,来3个实现类:

1
2
3
4
5
6
package com.boogipop.dao;

public interface PeoPle {
void say();
}

1
2
3
4
5
6
7
8
9
package com.boogipop.dao;

public class ChinesePeople implements PeoPle{
@Override
public void say() {
System.out.println("i am chinese people");
}
}

1
2
3
4
5
6
7
8
9
package com.boogipop.dao;

public class AmericanPeople implements PeoPle{
@Override
public void say() {
System.out.println("i am americanpeople");
}
}

1
2
3
4
5
6
7
8
9
package com.boogipop.dao;

public class JapanesePeople implements PeoPle{
@Override
public void say() {
System.out.println("i live in Tokyo");
}
}

接下来我们再定义一个测试POJO类,也就是我们需要自动装配的对象:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
package com.boogipop.pojo;

import com.boogipop.dao.PeoPle;

public class Person {
private PeoPle peoPle;
public void say(){
peoPle.say();
}

public PeoPle getPeoPle() {
return peoPle;
}

public void setPeoPle(PeoPle peoPle) {
this.peoPle = peoPle;
}
}

最后就是编写beans了:

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"
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="com.boogipop.dao"/>
<bean class="com.boogipop.dao.AmericanPeople" id="americanPeople"/>
<!--注意这里chinese的id为people-->
<bean class="com.boogipop.dao.ChinesePeople" id="people"/>
<bean class="com.boogipop.dao.JapanesePeople" id="japanesePeople"/>
<!--autowire自动填充byname以名字来填充-->
<bean class="com.boogipop.pojo.Person" id="person" autowire="byName"/>
</beans>
1
2
3
4
5
6
7
8
9
10
11
12
import com.boogipop.pojo.Person;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class Test {
public static void main(String[] args) {
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
Person person = context.getBean("person", Person.class);
person.say();

}
}

image.png
在3种people中我让Chinesepeople的id为people,自动填充的方式是byName,因此被识别了,byName识别name是根据set方法后面的名字,如这里是setPeople,那么他会去找bean中有没有叫people的id

byType

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"
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="com.boogipop.dao"/>
<bean class="com.boogipop.dao.AmericanPeople" id="americanPeople"/>
<!--注意这里chinese的id为people-->
<!-- <bean class="com.boogipop.dao.ChinesePeople" id="people"/>-->
<!-- <bean class="com.boogipop.dao.JapanesePeople" id="japanesePeople"/>-->
<!--autowire自动填充byname以名字来填充-->
<bean class="com.boogipop.pojo.Person" id="person" autowire="byType"/>
</beans>

这里我将其他2个people注释掉了,由于people属性是People类型的,因此根据byType会自动获取到bean中的AmericanPeople,假如取消掉另外2个people的注释,那么会报错,因为三个属性都一样,这也是一个弊端

注解自动装配

在Spring中有着和mybatis中一样智能的注解,注解会更加方便,但是注解和原始自动装配中各有优缺点,注解开发中方便,但是维护起来就是火葬场了,原始方法开发可能麻烦点,但是可读性就大大提高了

AutoWire注解

autowire注解就是实现自动装配用的,用法如下,我们把beans.xml中Person的autowire取消,如下:

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"
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="com.boogipop.dao"/>
<bean class="com.boogipop.dao.AmericanPeople" id="americanPeople"/>
<!--注意这里chinese的id为people-->
<!-- <bean class="com.boogipop.dao.ChinesePeople" id="people"/>-->
<!-- <bean class="com.boogipop.dao.JapanesePeople" id="japanesePeople"/>-->
<!--autowire自动填充byname以名字来填充-->
<bean class="com.boogipop.pojo.Person" id="person"/>
</beans>

在set方法或者属性上加上一个AutoWire注解:

1
2
3
4
5
6
7
 @Autowired
public void setPeople(People people) {
this.people = people;
}
// 或者
@Autowired
private People people;

image.png
同样可以运行成功,在这里autowire注解是根据Type去识别的,autowire可以识别name也可以识别type,它的顺序是优先为byType,若有重复的类型则根据byName
我们在beans种多放出来一个bean:

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"
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="com.boogipop.dao"/>
<bean class="com.boogipop.dao.AmericanPeople" id="americanPeople"/>
<!--注意这里chinese的id为people-->
<bean class="com.boogipop.dao.ChinesePeople" id="people"/>
<!-- <bean class="com.boogipop.dao.JapanesePeople" id="japanesePeople"/>-->
<!--autowire自动填充byname以名字来填充-->
<bean class="com.boogipop.pojo.Person" id="person"/>
</beans>

这时候就是根据id来识别了,也就是people,无疑调用的是chinese:
image.png
然后autowire注解里有个属性:
image.png
默认为true也就是该属性默认不为空,改为false就是可以为空

Resource注解

resource注解的用法和Autowire其实一模一样,只不过有一些微小的区别:

1
2
3
4
@Resource
public void setPeople(People people) {
this.people = people;
}

resource是jdk原生自带的,优先级为先byName再byType,但由于我这里是JDK19,无法使用,因此就不介绍了,高版本中使用AutoWire足以,Resource稍作了解吧

AutoWire+Qualifier使用

当beans中有很多重复种类,并且id又对应不上的情况,那么就得使用Qualifier注解去指定bean的ID了,我们把beans配置改为如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<?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="com.boogipop.pojo"/>
<bean class="com.boogipop.dao.AmericanPeople" id="americanPeople"/>
<bean class="com.boogipop.dao.ChinesePeople" id="chinesePeople"/>
<bean class="com.boogipop.dao.JapanesePeople" id="japanesePeople"/>
<!--autowire自动填充byname以名字来填充-->
<bean class="com.boogipop.pojo.Person" id="person"/>
</beans>

image.png
默认是报错的,因为id和type都对应不上,这时候就要指定id:

1
2
3
@Autowired
@Qualifier(value = "chinesePeople")
private People people;

image.png
发射成功

Spring注解开发

在上边儿学了一些自动装配的注解,装配可以实现自动化注解,那么xml配置文件可不可以用注解来实现呢?答案是可以的

Component注解

首先来讲讲component是个什么东西,英文翻译过来叫做组件,也就是beans的组件,那么就是bean了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
package com.boogipop.pojo;

import com.boogipop.dao.People;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Component;
@Component
public class Person {
@Autowired
@Qualifier(value = "chinesePeople")
private People people;
public void say(){
people.say();
}

public People getPeople() {
return people;
}
public void setPeople(People people) {
this.people = people;
}
}

在上述的pojo类中,给类定义了component后就意味着不需要再xml配置文件中再写bean了,component注解等效于<bean class="com.boogipop.pojo.Person" id="person"/>

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: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="com.boogipop.pojo"/>
<bean class="com.boogipop.dao.AmericanPeople" id="americanPeople"/>
<bean class="com.boogipop.dao.ChinesePeople" id="chinesePeople"/>
<bean class="com.boogipop.dao.JapanesePeople" id="japanesePeople"/>
</beans>

也就是现在bean标签就可以用注解替代,那么怎么实现值的简单初始化呢?

1
2
@Value("Boogipop")
private String name;

使用value标签即可进行简单的初始化,另外与component等效的还有3种注解,另外三种作用一模一样,但是作用场景不同,是用于区分的:

另外component也是可以自定义id的,component("")就可以自定义id的名称

Scope注解

另外还有个@Scope注解,写在类上,可以指定作用域,就是上述提到的

配置文件注解实现

上述操作把bean标签省略掉了,那我们能不能更进一步把XML配置文件直接给略了,答案也是可以的

Configuration标签和Bean标签

Configuration标签意思就是<beans>....</beans>大标签,也就是说现在可以定义一个Config类来实现配置

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.boogipop.config;

import com.boogipop.dao.AmericanPeople;
import com.boogipop.dao.ChinesePeople;
import com.boogipop.dao.JapanesePeople;
import com.boogipop.dao.People;
import com.boogipop.pojo.Person;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class Config {
@Bean
public Person getPerson(){
return new Person();
}
@Bean
public People chinesepeople(){
return new ChinesePeople();
}
@Bean
public People americanpeople(){
return new AmericanPeople();
}
@Bean
public People japanesepeople(){
return new JapanesePeople();
}
}

@Configuration在这里就是beans标签,下面的Bean注解代表着bean标签,和component的作用是几乎一样的(有些小区别后面会讲)然后这里bean可以通过bean("")指定别名

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
package com.boogipop.pojo;

import com.boogipop.dao.People;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
@Component
public class Person {
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", people=" + people +
'}';
}

@Value("Boogipop")
private String name;
@Autowired
@Qualifier(value = "chinesepeople")
private People people;
public void say(){
people.say();
}

public People getPeople() {
return people;
}
public void setPeople(People people) {
this.people = people;
}
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import com.boogipop.config.Config;
import com.boogipop.pojo.Person;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class Test {
public static void main(String[] args) {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(Config.class);
Person person = context.getBean("person", Person.class);
person.say();
System.out.println(person);

}
}

这里就是根据类来获取配置
其他注解也是一样去使用的:
image.png

ComponentScan标签

也就是xml配置文件中的 <context:component-scan base-package="com.boogipop.pojo"/>
image.png

Import标签

也就是导入其他配置类

component和bean的异同处

很多人可能注意到了上面bean和component的作用岂不是重复了?在这可以说确实是重复了,这两者也是有区别的:https://www.cnblogs.com/konglxblog/p/16441424.html

静态代理模式

在分析java反序列化的时候介绍了静态代理,这个就自己去看吧

动态代理模式

和之前讲的一样
https://juejin.cn/post/6844903978342301709#heading-11

动态代理和静态代理的区别

我们先来复习一下class文件的加载,我们编写的.Java文件经过javac编译之后,会产生.class文件,这种.class文件是二进制文件,里面的内容是只有JVM能够识别,在代码运行之前,JVM会读取.class文件,解析.class文件内的信息,取出二进制数据,加载进内存中,从而生成对应的Class对象。
而静态代理和动态代理最主要的区别就是:静态代理在我们的代码运行之前,代理类的.class文件就已经存在,例如上述的RoomAgency.java,在经过javac编译之后,就会变成RoomAgency.class;而动态代理则与静态代理相反,在代码运行之前不存在代理类的.class文件,在代码运行时才动态的生成代理类。

AOP实现方式一

AOP就是面向切面编程,也就是和上述动态代理大致的意思是一样的,Here we go,这里说一下我对AOP的看法吧,其实我一直很不理解为啥官网喜欢把一些简单的东西搞得不像人话一样,我觉得初学者应该都有这种感觉,不管你理解能力多强,你不理解就是不理解他说的话,但其实AOP就是一个很单纯的一个切入作用,就是你定义好了一个类,在这个类的切入点切一刀,然后在这个点前后加一些新的东西,比如鉴权或者是清空缓存,就是这么一个很简单的道理。。。
首先是环境搭建,我们就写一个简易版的JDBC事务:

1
2
3
4
5
6
7
8
9
package com.boogipop.service;

public interface CURD {
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
22
23
24
25
package com.boogipop.service;

public class SimpleImpl implements CURD{

@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("查询数据");
}
}

我们接下来让每个增删改查前和增删改查后分别输出BeforeAfter(不改变接口和实现类的代码):

1
2
3
4
5
6
7
8
9
10
11
12
13
package com.boogipop.cutface;

import org.springframework.aop.MethodBeforeAdvice;

import java.lang.reflect.Method;

public class Before implements MethodBeforeAdvice {
@Override
public void before(Method method, Object[] args, Object target) throws Throwable {
System.out.println("Before");
}
}

1
2
3
4
5
6
7
8
9
10
11
12
13
package com.boogipop.cutface;

import org.springframework.aop.AfterReturningAdvice;

import java.lang.reflect.Method;

public class After implements AfterReturningAdvice {
@Override
public void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable {
System.out.println("After");
}
}

这两个就是我们要插入的内容,接下来回到之前说的beans文件中,我们需要在xml中注册:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:p="http://www.springframework.org/schema/p" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-4.1.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-4.1.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.1.xsd">
<bean id="after" class="com.boogipop.cutface.After"/>
<bean id="before" class="com.boogipop.cutface.Before"/>
<bean id="simple" class="com.boogipop.service.SimpleImpl"/>
<!--开始配置aop-->
<aop:config>
<!--定义切入点-->
<!-- 执行环绕增加 -->
<aop:pointcut id="point" expression="execution(* com.boogipop.service.SimpleImpl.*(..))"/>
<aop:advisor advice-ref="before" pointcut-ref="point"/>
<aop:advisor advice-ref="after" pointcut-ref="point"/>
</aop:config>
</beans>

配置中有个expresssion,有关excution表达式:https://blog.csdn.net/ABCD898989/article/details/50809321
https://blog.csdn.net/loongshawn/article/details/72303040
其实就是和通配符一样
image.png
实现效果如下,成功在方法执行前加上了before和after

AOP实现方式二

上述是通过继承了spring的切面类实现的,我们也可以自定义我们的切面类

1
2
3
4
5
6
7
8
9
10
11
package com.boogipop.cutface;

public class Selfcut {
public void after(){
System.out.println("self after");
}
public void before(){
System.out.println("self before");
}
}

我们可以把这个类当做切面切入进去:

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
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:p="http://www.springframework.org/schema/p" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-4.1.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-4.1.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.1.xsd">
<bean id="after" class="com.boogipop.cutface.After"/>
<bean id="before" class="com.boogipop.cutface.Before"/>
<bean id="simple" class="com.boogipop.service.SimpleImpl"/>
<bean id="selfcut" class="com.boogipop.cutface.Selfcut"/>
<!--开始配置aop-->
<aop:config>
<!--引入selfcut作为切面类-->
<aop:aspect ref="selfcut">
<!--定义切入点-->
<aop:pointcut id="point" expression="execution(* com.boogipop.service.SimpleImpl.*(..))"/>
<!--定义通知,引入切点-->
<aop:after method="after" pointcut-ref="point"/>
<aop:before method="before" pointcut-ref="point"/>
</aop:aspect>
</aop:config>
</beans>

image.png
同样可以实现相同的功能

参考文献:

Spring中使用AOP的两种方式(基于注解和XML配置)_Luck_ZZ的博客-CSDN博客_基于xml配置的aop需要开启<aop:aspectj-autoproxy />吗

注解实现AOP

记得导入织入的包

1
2
3
4
5
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.19</version>
</dependency>

AOP同样可以完全使用注解完成,在这里就全部使用注解包括beans的配置文件,我也一并用注解代替,但是首先先介绍一下单纯用注解实现AOP而不省去配置文件的做法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:p="http://www.springframework.org/schema/p" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-4.1.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-4.1.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.1.xsd">
<bean id="simple" class="com.boogipop.service.SimpleImpl"/>
<!-- 需要注册bean -->
<bean id="selfcut" class="com.boogipop.cutface.Selfcut"/>
<!-- 开启注解扫描 -->
<aop:aspectj-autoproxy/>
</beans>
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
package com.boogipop.cutface;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Before;

//定义切面类
@Aspect
public class Selfcut {
@Before("execution(* com.boogipop.service.SimpleImpl.*(..))")
public void before(){
System.out.println("===self before===");
}
@After("execution(* com.boogipop.service.SimpleImpl.*(..))")
public void after(){
System.out.println("===self after===");
}
@Around("execution(* com.boogipop.service.SimpleImpl.*(..))")
public void around(ProceedingJoinPoint jp) throws Throwable {
System.out.println("环绕前");
// 执行切入点的方法
Object proceed = jp.proceed();
System.out.println("环绕后");
// 获取签名
System.out.println(jp.getSignature());
}
@AfterReturning("execution(* com.boogipop.service.SimpleImpl.*(..))")
public void afterreturn(){
System.out.println("after return");
}
}

image.png
顺序为环绕前->执行前->方法执行->返回后通知->执行后->环绕执行后(这里是spring6的顺序,spring5貌似不一样,不过问题不大),这就是注解和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
package com.boogipop.config;

import com.boogipop.cutface.Selfcut;
import com.boogipop.service.CURD;
import com.boogipop.service.SimpleImpl;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;

@Configuration
@ComponentScan("com.boogipop")
@EnableAspectJAutoProxy //开启aop注解支持
public class Config {
@Bean
public Selfcut selfcut(){
return new Selfcut();
}
@Bean
public CURD curd(){
return new SimpleImpl();
}

}

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
package com.boogipop.cutface;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Before;

//定义切面类
@Aspect
public class Selfcut {
@Before("execution(* com.boogipop.service.SimpleImpl.*(..))")
public void before(){
System.out.println("===self before===");
}
@After("execution(* com.boogipop.service.SimpleImpl.*(..))")
public void after(){
System.out.println("===self after===");
}
@Around("execution(* com.boogipop.service.SimpleImpl.*(..))")
public void around(ProceedingJoinPoint jp) throws Throwable {
System.out.println("环绕前");
// 执行切入点的方法
Object proceed = jp.proceed();
System.out.println("环绕后");
// 获取签名
System.out.println(jp.getSignature());
}
@AfterReturning("execution(* com.boogipop.service.SimpleImpl.*(..))")
public void afterreturn(){
System.out.println("after return");
}
}

1
2
3
4
5
6
7
8
9
10
11
12
13
import com.boogipop.config.Config;
import com.boogipop.service.CURD;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class Testproxy {
public static void main(String[] args) {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(Config.class);
CURD curd = context.getBean("curd", CURD.class);
curd.select();
}
}

image.png
结果也是一样的,注解真方便

整合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
30
31
32
33
34
35
36
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.30</version>
</dependency>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.6</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.springframework/spring-jdbc -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>6.0.4</version>
</dependency>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
<version>3.0.1</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>6.0.4</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13.2</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.19</version>
</dependency>
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>
<properties resource="db.properties">
<property name="useless" value="useless"/>
</properties>
<settings>
<setting name="logImpl" value="STDOUT_LOGGING"/>
</settings>
<typeAliases>
<package name="com.boogipop.pojo"/>
</typeAliases>
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"></transactionManager>
<dataSource type="POOLED">
<property name="driver" value="${driver}"></property>
<property name="url" value="${url}"></property>
<property name="username" value="${username}"></property>
<property name="password" value="${password}"></property>
</dataSource>
</environment>
</environments>
<mappers>
<mapper resource="UserMapper.xml"/>
</mappers>
</configuration>
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
<?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="com.boogipop.mapper.UserMapper">
<select id="getuserlist" resultType="User">
select * from user
</select>
<!--补充一个getuserbyid的select标签-->
<select id="getuserbyname" resultType="User" parameterType="String">
select * from user where name=#{name}
</select>
<insert id="adduser" parameterType="User">
insert into mybatis.user(id,name,pwd) values(#{id},#{name},#{pwd});
</insert>
<delete id="deluser" parameterType="int">
delete from user where id=#{id}
</delete>
<update id="cguser" parameterType="User">
update user set name=#{name},pwd=#{pwd} where id=#{id}
</update>
<insert id="mapadduser" parameterType="Map">
insert into user(id,name) values(#{id},#{name})
</insert>
<select id="getuserbylike" parameterType="Map" resultType="User">
select * from user where name like concat("%",#{name},"%")
</select>
</mapper>

1
2
3
4
driver=com.mysql.cj.jdbc.Driver
url=jdbc:mysql://localhost:3306/mybatis?useSSL=true&seUnicode=true&characterEncoding=UTF-8
username=root
password=jiayou357753
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
package com.boogipop.pojo;

import org.apache.ibatis.type.Alias;

@Alias("User")
public class User {
private int id;
private String name;
private String pwd;

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

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

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

public void setPwd(String pwd) {
this.pwd = pwd;
}

public int getId() {
return id;
}

public String getName() {
return name;
}

public String getPwd() {
return pwd;
}

public User(int id, String name, String pwd) {
this.id = id;
this.name = name;
this.pwd = pwd;
}
}

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
package com.boogipop.utils;

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 java.io.IOException;
import java.io.InputStream;

public class MybatisUtil {
private static SqlSessionFactory sqlSessionFactory; //提升作用域
static {
try {
//获取sqlsessionFactory对象 这一步是必须的
String resource = "mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
} catch (IOException e) {
e.printStackTrace();
}

}
//既然有了 SqlSessionFactory,顾名思义,我们可以从中获得 SqlSession 的实例。SqlSession 提供了在数据库执行 SQL 命令所需的所有方法
public static SqlSession getSqlsession(){
return sqlSessionFactory.openSession(true);
}
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
package com.boogipop.mapper;

import com.boogipop.pojo.User;

import java.util.List;
import java.util.Map;

public interface UserMapper {
List<User> getuserlist();
//根据ID返回数据
User getuserbyname(String name);
//添加用户
Boolean adduser(User user);
//删除用户
Boolean deluser(int id);
//更新用户
Boolean cguser(User user);
//添加用户Map
Boolean mapadduser(Map<String,Object> map);
//模糊查询
User getuserbylike(Map<String,Object> map);
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import com.boogipop.mapper.UserMapper;
import com.boogipop.pojo.User;
import com.boogipop.utils.MybatisUtil;
import org.apache.ibatis.session.SqlSession;
import org.junit.Test;

import java.util.List;

public class Exec {
@Test
public void selectall(){
SqlSession sqlsession = MybatisUtil.getSqlsession();
UserMapper mapper = sqlsession.getMapper(UserMapper.class);
List<User> getuserlist = mapper.getuserlist();
for (User user : getuserlist) {
System.out.println(user);
}
sqlsession.close();
}
}

经过一系列的配置终于把先前的mybatis拿过来了,熟悉之后很自然

方式一

回顾一下mybatis中我们是如何进行JDBC事务处理的,我们首先定义了mybatis核心配置文件使用JDBC连接了mysql,随后准备了util类,返回了我们sqlsession,之后又添加了mapper和pojo以及Mapper.xml配置文件,最后在核心配置文件注册了mapper
我们调用mapper就通过util获取sqlsession,随后获取mapper,最后执行事务,提交事务,关闭sqlsession,在这些步骤中:获取sqlsession、获取mapper、关闭sqlsession。这几个步骤重复了,这时候我们就可以用spring-mybaits包去整合它们

1
2
3
4
jdbc.driver=com.mysql.cj.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/mybatis?useSSL=true&seUnicode=true&characterEncoding=UTF-8
jdbc.username=root
jdbc.password=jiayou357753
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"?>
<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 https://www.springframework.org/schema/context/spring-context.xsd">
<!-- 注意这里-->
<context:component-scan base-package="com.boogipop.mapper"/>
<!--Datasource:使用spring的数据源替换mybatis的配置,选一个连接池 c3p0,jdbc,druid
这里我们使用spring提供的jdbc:org.springframework.jdbc.datasource
-->
<context:property-placeholder location="db.properties"/>
<bean id="datasource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName" value="${jdbc.driver}"/>
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
</bean>
<!-- 获取sqlsessionfactory-->
<bean id="sqlsessionfactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<!--绑定mybatis配置文件-->
<property name="dataSource" ref="datasource"/>
<property name="configLocation" value="classpath:mybatis-config.xml"/>
<property name="mapperLocations" value="classpath:com/boogipop/mapper/UserMapper.xml"/>
</bean>
<bean class="org.mybatis.spring.SqlSessionTemplate" id="sqlsession">
<!--只能使用构造器创建,因为没有set方法-->
<constructor-arg index="0" ref="sqlsessionfactory"/>
</bean>
</beans>
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>
<settings>
<setting name="logImpl" value="STDOUT_LOGGING"/>
</settings>
<typeAliases>
<package name="com.boogipop.pojo"/>
</typeAliases>
</configuration>
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
<?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="com.boogipop.mapper.UserMapper">
<select id="getuserlist" resultType="User">
select * from user
</select>
<select id="getuserbyname" resultType="User" parameterType="String">
select * from user where name=#{name}
</select>
<insert id="adduser" parameterType="User">
insert into mybatis.user(id,name,pwd) values(#{id},#{name},#{pwd});
</insert>
<delete id="deluser" parameterType="int">
delete from user where id=#{id}
</delete>
<update id="cguser" parameterType="User">
update user set name=#{name},pwd=#{pwd} where id=#{id}
</update>
<insert id="mapadduser" parameterType="Map">
insert into user(id,name) values(#{id},#{name})
</insert>
<select id="getuserbylike" parameterType="Map" resultType="User">
select * from user where name like concat("%",#{name},"%")
</select>
</mapper>

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
package com.boogipop.mapper;

import com.boogipop.pojo.User;

import java.util.List;
import java.util.Map;

public interface UserMapper {
List<User> getuserlist();
//根据ID返回数据
User getuserbyname(String name);
//添加用户
Boolean adduser(User user);
//删除用户
Boolean deluser(int id);
//更新用户
Boolean cguser(User user);
//添加用户Map
Boolean mapadduser(Map<String,Object> map);
//模糊查询
User getuserbylike(Map<String,Object> map);
}

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
package com.boogipop.mapper;

import com.boogipop.pojo.User;
import org.mybatis.spring.SqlSessionTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.util.List;
import java.util.Map;
@Component
public class UserMapperImpl implements UserMapper{
@Autowired
private SqlSessionTemplate sqlsession;

@Override
public List<User> getuserlist() {
UserMapper mapper = sqlsession.getMapper(UserMapper.class);
return mapper.getuserlist();
}

@Override
public User getuserbyname(String name) {
return null;
}

@Override
public Boolean adduser(User user) {

return null;
}

@Override
public Boolean deluser(int id) {
return null;
}

@Override
public Boolean cguser(User user) {
return null;
}

@Override
public Boolean mapadduser(Map<String, Object> map) {
return null;
}

@Override
public User getuserbylike(Map<String, Object> map) {
return null;
}
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import com.boogipop.mapper.UserMapper;
import com.boogipop.mapper.UserMapperImpl;
import com.boogipop.pojo.User;
import com.boogipop.utils.MybatisUtil;
import org.apache.ibatis.session.SqlSession;
import org.junit.Test;
import org.springframework.context.support.ClassPathXmlApplicationContext;

import java.util.List;

public class Exec {
@Test
public void selectall(){
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("ApplicationContext.xml");
UserMapper userMapperImpl = context.getBean("userMapperImpl", UserMapper.class);
List<User> getuserlist = userMapperImpl.getuserlist();
for (User user : getuserlist) {
System.out.println(user);
}
}
}

image.png
有一说一我还是喜欢原来的用法,我不知道这傻逼意味何在,徒增BUG,这东西能不用就不用真的,真傻比我草,在这我们还保留了一部分的mybatis配置文件,其实也是可以省略的,mybatis配置文件中剩下的是日志和别名的设置,其中日志可能无法替换,但是别名可以用typeAliasesPackage标签代替
image.png

坑点:
这里的db.properties一定要注意,变量不要直接用username和password:
明明密码对的,被拒绝了,找了好久原因,后来发现将db.properties配置文件里的username改为jdbc.username就好了,可能是因为username被系统变量占用了。
当spring容器在进行启动的时候,会读取当前系统某些环境变量的配置,当前系统的用户名使用username来表示的,所以做好的方式是添加前缀,使用jdbc.username.
https://www.cnblogs.com/fps2tao/p/14109885.html

方式二

根据官网的来说,你还可以通过让实现类继承sqlsessiondaosupport类从而获取sqlsession:
image.png
根据官网的解释我们也来实操一遍:

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
<?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 https://www.springframework.org/schema/context/spring-context.xsd">
<!-- 注意这里-->
<context:component-scan base-package="com.boogipop.mapper"/>
<!--Datasource:使用spring的数据源替换mybatis的配置,选一个连接池 c3p0,jdbc,druid
这里我们使用spring提供的jdbc:org.springframework.jdbc.datasource
-->
<context:property-placeholder location="db.properties"/>
<bean id="datasource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName" value="${jdbc.driver}"/>
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
</bean>
<!-- 获取sqlsessionfactory-->
<bean id="sqlsessionfactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<!--绑定mybatis配置文件-->
<property name="dataSource" ref="datasource"/>
<property name="configLocation" value="classpath:mybatis-config.xml"/>
<property name="mapperLocations" value="classpath:com/boogipop/mapper/UserMapper.xml"/>
</bean>
<bean class="org.mybatis.spring.SqlSessionTemplate" id="sqlsession">
<!--只能使用构造器创建,因为没有set方法-->
<constructor-arg index="0" ref="sqlsessionfactory"/>
</bean>
<bean id="userMapper2" class="com.boogipop.mapper.UserMapperImpl2">
<property name="sqlSessionFactory" ref="sqlsessionfactory"/>
</bean>
</beans>
1
2
3
4
5
6
7
public class UserMapperImpl2 extends SqlSessionDaoSupport implements UserMapper {
@Override
public List<User> getuserlist() {
SqlSession sqlSession = getSqlSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
return mapper.getuserlist();
}

image.png
通过继承的方式也可以达到相同的目的

我自己比较推荐的方式三

还有一种方式就不需要我们自己写实现类了,让spring自己去创建Mapper,这样我觉得其实是最方便的:

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
<?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 https://www.springframework.org/schema/context/spring-context.xsd">
<!-- 注意这里-->
<context:component-scan base-package="com.boogipop.mapper"/>
<!--Datasource:使用spring的数据源替换mybatis的配置,选一个连接池 c3p0,jdbc,druid
这里我们使用spring提供的jdbc:org.springframework.jdbc.datasource
-->
<context:property-placeholder location="db.properties"/>
<bean id="datasource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName" value="${jdbc.driver}"/>
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
</bean>
<!-- 获取sqlsessionfactory-->
<bean id="sqlsessionfactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<!--绑定mybatis配置文件-->
<property name="dataSource" ref="datasource"/>
<property name="configLocation" value="classpath:mybatis-config.xml"/>
<property name="mapperLocations" value="classpath:com/boogipop/mapper/UserMapper.xml"/>
</bean>
<bean class="org.mybatis.spring.SqlSessionTemplate" id="sqlsession">
<!--只能使用构造器创建,因为没有set方法-->
<constructor-arg index="0" ref="sqlsessionfactory"/>
</bean>
<bean id="userMapper2" class="com.boogipop.mapper.UserMapperImpl2">
<property name="sqlSessionFactory" ref="sqlsessionfactory"/>
</bean>
<!--加载mapper包中的所有接口,通过sqlSessionFactory获取sqlSession对象,然后创建所有的dao接口并存储在Spring容器中-->
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<!-- 这里的value和上面sqlsessionfactory的id对应 -->
<property name="sqlSessionFactoryBeanName" value="sqlsessionfactory"/>
<property name="basePackage" value="com.boogipop.mapper"/>
</bean>
</beans>
1
2
3
4
5
6
7
8
9
@Test
public void selectall3(){
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("ApplicationContext.xml");
UserMapper userMapper = context.getBean("userMapper", UserMapper.class);
List<User> list = userMapper.getuserlist();
for (User user : list) {
System.out.println(user);
}
}

然后直接获取id为接口名字首字母小写的bean,获取到的就是注册好的Mapper:
image.png

假如被这个bean的名字所困扰,也可以直接改bean的名字:
image.png
在接口上直接用注解注册个bean即可,猜测spring是在创建对象的时候就对我们的usermapepr3做了处理,将其变成了mapper

Spring声明式事务

首先什么是事务呢?事务遵循ACID原则:

以上就是事务的一些基本概念,也就是说事务属于逻辑上的处理,为了让程序有顺序有原则的去执行,不会因为别的事务破坏另一个事务执行

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
<?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" xmlns:tx="http://www.springframework.org/schema/tx"
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/context
https://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.1.xsd">
<!-- 注意这里-->
<context:component-scan base-package="com.boogipop.mapper"/>
<!--Datasource:使用spring的数据源替换mybatis的配置,选一个连接池 c3p0,jdbc,druid
这里我们使用spring提供的jdbc:org.springframework.jdbc.datasource
-->
<context:property-placeholder location="db.properties"/>
<bean id="datasource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName" value="${jdbc.driver}"/>
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
</bean>
<!-- 获取sqlsessionfactory-->
<bean id="sqlsessionfactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<!--绑定mybatis配置文件-->
<property name="dataSource" ref="datasource"/>
<property name="configLocation" value="classpath:mybatis-config.xml"/>
<property name="mapperLocations" value="classpath:com/boogipop/mapper/UserMapper.xml"/>
</bean>
<bean class="org.mybatis.spring.SqlSessionTemplate" id="sqlsession">
<!--只能使用构造器创建,因为没有set方法-->
<constructor-arg index="0" ref="sqlsessionfactory"/>
</bean>
<bean id="userMapper2" class="com.boogipop.mapper.UserMapperImpl2">
<property name="sqlSessionFactory" ref="sqlsessionfactory"/>
</bean>
<!--加载mapper包中的所有接口,通过sqlSessionFactory获取sqlSession对象,然后创建所有的dao接口并存储在Spring容器中-->
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<!-- 这里的value和上面sqlsessionfactory的id对应 -->
<property name="sqlSessionFactoryBeanName" value="sqlsessionfactory"/>
<property name="basePackage" value="com.boogipop.mapper"/>
</bean>
<!-- 配置声明式事务-->
<bean class="org.springframework.jdbc.datasource.DataSourceTransactionManager" id="transactionManager">
<property name="dataSource" ref="datasource"/>
</bean>
<!-- 结合AOP事务织入-->
<tx:advice id="txadvice" transaction-manager="transactionManager">
<!-- 给方法配置事务-->
<tx:attributes>
<tx:method name="getuserlist" propagation="REQUIRED"/>
<tx:method name="getuserbyname" propagation="REQUIRED"/>
</tx:attributes>
</tx:advice>
<!-- aop切面-->
<aop:config>
<!-- 切入点-->
<aop:pointcut id="point" expression="execution(* com.boogipop.mapper..*.*(..))"/>
<!-- 切入方法-->
<aop:advisor advice-ref="txadvice" pointcut-ref="point"/>
</aop:config>
</beans>

这样的话执行起来就会符合上述的ACID事务原则,其中要注意propagation的属性,一共有七种类型的事务,详细参考可以看一开始的链接
另外事务的实现还可以直接使用@Transactional注解去实现

小结

完结撒花咯,下一站就是SpringMVC紧跟着就是SpringBoot,这时候又得新学一些东西,加油加油

About this Post

This post is written by Boogipop, licensed under CC BY-NC 4.0.

#开发