Spring开端

Spring项目的创建

首先,我们用Maven创建工程并引入spring-context依赖:

1
2
3
4
5
6
7
8
9
10
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>6.0.8</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>5.2.6.RELEASE</version>
</dependency>

我们先编写一个MailService,用于在用户登录和注册成功后发送邮件通知:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class MailService {
private ZoneId zoneId = ZoneId.systemDefault();

public void setZoneId(ZoneId zoneId) {
this.zoneId = zoneId;
}

public String getTime() {
return ZonedDateTime.now(this.zoneId).format(DateTimeFormatter.ISO_ZONED_DATE_TIME);
}

public void sendLoginMail(User user) {
System.err.println(String.format("Hi, %s! You are logged in at %s", user.getName(), getTime()));
}

public void sendRegistrationMail(User user) {
System.err.println(String.format("Welcome, %s!", user.getName()));

}
}

再编写一个UserService,实现用户注册和登录:

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
public class UserService {
private MailService mailService;

public void setMailService(MailService mailService) {
this.mailService = mailService;
}

private List<User> users = new ArrayList<>(List.of( // users:
new User(1, "bob@example.com", "password", "Bob"), // bob
new User(2, "alice@example.com", "password", "Alice"), // alice
new User(3, "tom@example.com", "password", "Tom"))); // tom

public User login(String email, String password) {
for (User user : users) {
if (user.getEmail().equalsIgnoreCase(email) && user.getPassword().equals(password)) {
mailService.sendLoginMail(user);
return user;
}
}
throw new RuntimeException("login failed.");
}

public User getUser(long id) {
return this.users.stream().filter(user -> user.getId() == id).findFirst().orElseThrow();
}

public User register(String email, String password, String name) {
users.forEach((user) -> {
if (user.getEmail().equalsIgnoreCase(email)) {
throw new RuntimeException("email exist.");
}
});
User user = new User(users.stream().mapToLong(u -> u.getId()).max().getAsLong() + 1, email, password, name);
users.add(user);
mailService.sendRegistrationMail(user);
return user;
}
}

注意到UserService通过setMailService()注入了一个MailService

然后,我们需要编写一个特定的application.xml配置文件,告诉Spring的IoC容器应该如何创建并组装Bean:

1
2
3
4
5
6
7
8
9
10
11
12
<?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="userService" class="com.itranswarp.learnjava.service.UserService">
<property name="mailService" ref="mailService" />
</bean>

<bean id="mailService" class="com.itranswarp.learnjava.service.MailService" />
</beans>
  • 每个<bean ...>都有一个id标识,相当于Bean的唯一ID;
  • userServiceBean中,通过<property name="..." ref="..." />注入了另一个Bean;
  • Bean的顺序不重要,Spring根据依赖关系会自动正确初始化。

把上述XML配置文件用Java代码写出来,就像这样:

1
2
3
UserService userService = new UserService();
MailService mailService = new MailService();
userService.setMailService(mailService);

只不过Spring容器是通过读取XML文件后使用反射完成的。

如果注入的不是Bean,而是booleanintString这样的数据类型,则通过value注入,例如,创建一个HikariDataSource

1
2
3
4
5
6
7
<bean id="dataSource" class="com.zaxxer.hikari.HikariDataSource">
<property name="jdbcUrl" value="jdbc:mysql://localhost:3306/test" />
<property name="username" value="root" />
<property name="password" value="password" />
<property name="maximumPoolSize" value="10" />
<property name="autoCommit" value="true" />
</bean>

最后一步,我们需要创建一个Spring的IoC容器实例,然后加载配置文件,让Spring容器为我们创建并装配好配置文件中指定的所有Bean,这只需要一行代码:

1
2
3
4
// 获取Bean:
UserService userService = context.getBean(UserService.class);
// 正常调用:
User user = userService.login("bob@example.com", "password");

完整的main()方法如下:

1
2
3
4
5
6
7
8
public class Main {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("application.xml");
UserService userService = context.getBean(UserService.class);
User user = userService.login("bob@example.com", "password");
System.out.println(user.getName());
}
}

ApplicationContext

我们从创建Spring容器的代码:

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

可以看到,Spring容器就是ApplicationContext,它是一个接口,有很多实现类,这里我们选择ClassPathXmlApplicationContext,表示它会自动从classpath中查找指定的XML配置文件。

获得了ApplicationContext的实例,就获得了IoC容器的引用。从ApplicationContext中我们可以根据Bean的ID获取Bean,但更多的时候我们根据Bean的类型获取Bean的引用:

1
UserService userService = context.getBean(UserService.class);

Spring还提供另一种IoC容器叫BeanFactory,使用方式和ApplicationContext类似:

1
2
BeanFactory factory = new XmlBeanFactory(new ClassPathResource("application.xml"));
MailService mailService = factory.getBean(MailService.class);

BeanFactoryApplicationContext的区别在于,BeanFactory的实现是按需创建,即第一次获取Bean时才创建这个Bean,而ApplicationContext会一次性创建所有的Bean。实际上,ApplicationContext接口是从BeanFactory接口继承而来的,并且,ApplicationContext提供了一些额外的功能,包括国际化支持、事件和通知机制等。通常情况下,我们总是使用ApplicationContext,很少会考虑使用BeanFactory

小结

Spring的IoC容器接口是ApplicationContext,并提供了多种实现类;

通过XML配置文件创建IoC容器时,使用ClassPathXmlApplicationContext

持有IoC容器后,通过getBean()方法获取Bean的引用。

关于原型对象

Spring默认使用单例模式(单例模式,整个生命周期都只有这一个对象,对象的唯一。),但是如果我们要让一个对象每次被实例化时产生的都是一个新对象,那我们就需要设置Bean的scope=”prototype”

Spring自动装配

属性名:AutoWire 属性值:【ByName,ByType,constructor,default,no】

ByName:自动在容器上下文中寻找,和自己对象Set方法后面的值对应的BeanID,需要保证所有Bean的ID唯一性,并且这个Bean需要和自动注入的Set方法的值一致!

1
2
3
<bean id="people" class="People" autowire="byName">
<property name="name" value="王某"/>
</bean>

ByType:自动在容器上下文中寻找,和自己对象属性类型值相同的Bean,这时,我们可以取消ID属性,因为装配过程不再依赖于ID。需要保证所有Bean的Class唯一性,并且这个Bean需要和自动注入的属性的类型一致!

使用注解实现自动装配

关于注解

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@Autowired:根据属性类型进行自动装配,若接口不止一个实现,则按照属性名(因为可以不加set)进行匹配bean
@Qualifier:必须配合Autowired使用,可以帮助指定实现类id
@Resource:可以根据类型注入,可以根据名称注入
@Value:注入普通类型属性
@Autowired:可以根据类型注入,可以根据名称注入(默认优先进行byType装配)
若接口不止一个实现,则按照属性名(因为可以不加set)进行匹配bean
创建实体类对象,在实体类中对象类型属性上或set方法上加上该注解。
加入该注解后,该属性可以不需要添加set方法
@Qualifier:若Autowired按属性名匹配,则可以配合@Qualifier 注解指定实现类id
@Resource: 可以根据类型注入,可以根据名称注入(默认优先进行byName装配)
如果同时指定了name和type,则从Spring上下文中找到唯一匹配的bean进行装配,找不到则抛出异常
如果指定了name,则从上下文中查找名称(id)匹配的bean进行装配,找不到则抛出异常
如果指定了type,则从上下文中找到类型匹配的唯一bean进行装配,找不到或者找到多个,都会抛出异常
如果既没有指定name,又没有指定type,则自动按照byName方式进行装配;如果没有匹配,则回退为一个原始类型进行匹配,如果匹配则自动装配;
@Value:注入普通类型属性
代替了<property name="..." value="..."/> 的繁琐操作。

使用注解须知:

1.导入约束 context约束

2.配置注解的支持 context:annotation-config

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"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
https://www.springframework.org/schema/context/spring-context.xsd">
<context:annotation-config/>

</beans>

@Autowired

直接在属性上使用即可!也可以在set上使用!

使用Autowired我们可以不用再编写set方法,前提是这个自动装配的属性在IOC(spring)容器中存在,且符合名字ByName!

科普:

1
@Nullable   字段标记了这个注解,说明这个字段可以为null;
1
2
3
4
5
false值可以为null
@Autowired(required = false)
另外还有@Resource注解(自动通过名字,类型,注入)
@Component(组件,放在类上,说明这个类被Spring管理;了,这就是Bean)

@Component衍生注解

@Component拥有几个衍生注解,在之后的SpringMVC中会进行三层架构分层

dao(@Repository)

Service(@Service)

controller(@Controller)

四个注解功能是一样的,都是将某个类注册到Spring中,装配Bean

自动装配

1
2
3
@Autowired不能唯一自动装配注解:自动装配通过类型,名字
如果@Autowired不能唯一自动装配注解,那么就需要@Qualifier(value="")

作用域

1
2
3
4
5
6
7
@Component
@Scope("prototype")
public class User {
@Value("小王")
public String name;

}

完全使用Java方式配置Spring

配置文件:

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
package top.yuanfangwa.Config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import top.yuanfangwa.pojo.USer;

/**
* @Author:无知啊
* @ProJect_Name:SpringDemo
* @Date:2022/3/1214:20
* @Description:
* @Since version:1.0
*/
//这个也会被Spring容器接管,因为他本身也是一个Component,
// @Configuration代表着这是一个配置类,就和之前的Beans.xml是一样的
@Configuration
@ComponentScan("top.yuanfangwa.pojo")
public class Config {
//注册一个Bean就相当于我们之前写的一个Bean标签
//方法名就相当于之前Bean标签的ID属性
// 返回值就相当于之前的class属性
@Bean
public USer getBean() {
return new USer();//就是要返回的Bean
}
}

测试:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import top.yuanfangwa.Config.Config;
import top.yuanfangwa.pojo.USer;

/**
* @Author:无知啊
* @ProJect_Name:SpringDemo
* @Date:2022/3/1214:22
* @Description:
* @Since version:1.0
*/
public class Mytset {
public static void main(String[] args) {
ApplicationContext Context = new AnnotationConfigApplicationContext(Config.class);
USer user = (USer) Context.getBean("getBean");
System.out.println(user.getName());

}
}

这样的配置在Spring boot中随处可见

代理模式

代理模式可以使得原本的操作变得纯粹

aop约束

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

Aop 表达式

Aop中expression表达式的表示方法

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
常见的切面表达式
1 所有公有方法的执行

execution(public * *(..))

2 所有以set开头的公有方法的执行

execution(* set*(..))

3 AccountService接口下的所有方法的执行

execution(* com.LightseaBlue.service.AccountService.*(..))

4 com.LightseaBlue.service包下的所有方法的执行

execution(* com.LightseaBlue.service.*.*(..))

5 com.LightseaBlue.service包及其子包下的所有方法的执行

execution(* com.LightseaBlue.service..*.*(..))

6 匹配com.LightseaBlue.service包下的所有类的所有方法(不含子包)

within(com.LightseaBlue.service.*)

7 com.LightseaBlue.service包和子包的所有方法

within(com.LightseaBlue.service..*)

8 匹配AccountService的代理类(不支持通配符)

this(com.LightseaBlue.service.AccountService)

spring整合Mybatis

约束

1
2
3
4
5
6
7
8
9
10
11
<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"
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop
https://www.springframework.org/schema/aop/spring-aop.xsd
http://www.springframework.org/schema/tx
https://www.springframework.org/schema/aop/spring-tx.xsd">
</beans>

总体来说,Spring负责了我们原本所需要负责的所有事情,我们只需要去设定实体类以及注入所需要的信息。

spring 事务

默认情况下只会回滚RuntimeException(运行时异常)和Error(错误),对于普通的 Exception(非运行时异常),它不会回滚。回滚生效也是有范围的,需处于public