- Spring IoC注解式开发
- 声明Bean的注解
@Component
:组件@Controller
:控制器@Service
:业务@Repository
:仓库(Dao)- 负责注入的注解
@Value
@Autowired
@Qualifier
@Resource
十二、★★★Spring IoC注解式开发
- 注解的存在主要是为了简化 XML 的配置。
- Spring6 倡导全注解开发。
12.1 回顾注解
注解其实就是一个标记。
元注解:标注注解的注解。
@Target
注解:用来修饰注解可以出现的位置。@Target(value = {ElementType.TYPE, ElementType.FIELD})
- 表示被
@Target
修饰的注解可以出现在类上、属性上
- 表示被
使用某个注解的时候,如果注解的属性名是
value
的话,value
可以省略:@Target({ElementType.TYPE, ElementType.FIELD})
使用某个注解的时候,如果注解的属性值是数组,并且数组中只有一个元素,那么大括号可以省略:
@Target(ElementType.TYPE)
@Retention
注解:用来标注注解最终可以保留到什么时刻。@Retention(RetentionPolicy.RUNTIME)
- 表示被
@Retention
修饰的注解最终保留在class
文件当中,并且可以被反射机制读取。
- 表示被
@Retention(RetentionPolicy.SOURCE)
- 表示被
@Retention
修饰的注解只能保留到源文件中,字节码文件中和运行时看不到该注解。
- 表示被
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
34package com.f.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* 自定义注解
*
* @author fzy
* @date 2024/1/22 12:33
*/
// @Target注解用来修饰@Component可以出现的位置
// 以下表示Component注解可以出现在类上、属性上
//@Target(value = {ElementType.TYPE, ElementType.FIELD})
// 使用某个注解的时候,如果注解的属性名是value的话,value可以省略
//@Target({ElementType.TYPE, ElementType.FIELD})
// 使用某个注解的时候,如果注解的属性值是数组,并且数组中只有一个元素,那么大括号可以省略
// @Retention也是一个元注解。用来标注@Component注解最终保留到到什么时候。
// 以下表示Component注解最终保留在class文件当中,并且可以被反射机制读取。
public Component {
// 定义注解的属性
// String是属性类型
// value是属性名字
String value();
// String[]是属性类型
// names是属性名字
String[] names();
}自定义注解的使用:
1
2
3
4
5
6
7
8
9
10
11
12package com.f.bean;
import com.f.annotation.Component;
/**
* @author fzy
* @date 2024/1/22 12:56
*/
public class User {
}
12.2 通过反射获取注解
1 | package com.f.test; |
1 | userBean |
12.3 组件扫描原理
为了方便后面代码编写,将
@Component
注解的names
属性注释掉。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
26package com.f.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* 自定义注解
*
* @author fzy
* @date 2024/1/22 12:33
*/
public Component {
// 定义注解的属性
// String是属性类型
// value是属性名字
String value();
// String[]是属性类型
// names是属性名字
//String[] names();
}需求:给定一个包名,将这个包下的所有的带
@Component
注解的类进行实例化。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
52package com.f.client;
import com.f.annotation.Component;
import java.io.File;
import java.net.URL;
import java.net.URLDecoder;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
/**
* @author fzy
* @date 2024/1/22 13:15
*/
public class ComponentScan {
public static void main(String[] args) {
Map<String, Object> singletonObjects = new HashMap<>();
// 目前只知道一个包的名字,扫描这个包下所有的类。
// 当这个类上有@Component注解的时候,实例化该对象,然后放到Map集合中。
String packageName = "com.f.bean";
String packagePath = packageName.replaceAll("\\.", "/");
// com是在类的根路径下的一个目录
URL url = ClassLoader.getSystemClassLoader().getResource(packagePath);
// 获取绝对路径
String path = url.getPath();
path = URLDecoder.decode(path, StandardCharsets.UTF_8); // 对获取到的URL进行解码
// 获取绝对路径下的所有文件
File file = new File(path);
File[] files = file.listFiles();
Arrays.stream(files).forEach(f -> {
try {
// 得到指定包下所有类的全限定类名
String className = packageName + "." + f.getName().split("\\.")[0];
// 通过反射机制解析注解
Class<?> clazz = Class.forName(className);
if (clazz.isAnnotationPresent(Component.class)) {
// 获取类上的注解
Component annotation = clazz.getAnnotation(Component.class);
String id = annotation.value();
Object obj = clazz.newInstance();
// 实例化对象,将其放到Map集合中
singletonObjects.put(id, obj);
}
} catch (Exception e) {
e.printStackTrace();
}
});
System.out.println(singletonObjects);
}
}
12.4 ★声明Bean的注解
负责声明
Bean
的注解,常见的包括四个:@Component
:组件@Controller
:控制器@Service
:业务@Repository
:仓库(Dao)
上面四个注解中,
@Controller
,@Service
,@Repository
都为@Component
的别名(@AliasFor
),其实这四个注解的功能都一样,用哪个都可以,但是在不同用途的Bean
上使用不同的注解可以增强程序的可读性:- 普通
bean
:@Component
。 - 控制器类上使用:
@Controller
(控制层)。 service
类上使用:@Service
(业务层)。dao
类上使用:@Repository
(持久层)。
**上面四个注解中,都只有一个
value
属性,用来指定bean
的id
**。- 普通
12.5 ★Spring注解的使用
加入
aop
依赖。当加入
spring-context
依赖之后,会自动关联加入aop
的依赖。
在配置文件中添加
context
命名空间。1
2
3
4
5
6
7
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">
</beans>在配置文件中指定扫描的包。
1
2
3
4
5
6
7
8
9
<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">
<!--给Spring框架指定要扫描哪些包中的类-->
<context:component-scan base-package="com.f.spring6.bean"/>
</beans>在相应的包中的
Bean
类上使用注解。1
2
3
4
5
6
7
8
9
10
11package com.f.spring6.bean;
import org.springframework.stereotype.Component;
/**
* @author fzy
* @date 2024/1/22 13:58
*/
public class User {
}测试。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19package com.f.spring6.test;
import com.f.spring6.bean.User;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
/**
* @author fzy
* @date 2024/1/22 14:01
*/
public class IoCAnnotationTest {
public void testBeanComponent() {
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.xml");
User user = applicationContext.getBean("user", User.class);
System.out.println(user);
}
}
注意:
如果注解的属性名是
value
,那么value
是可以省略的。如果把
value
属性彻底去掉,那么 Spring 会为Bean
自动取名,默认名字是:Bean 类名首字母变小写。例如:1
2
3
4
5
6
7
8
9
10
11package com.f.spring6.bean;
import org.springframework.stereotype.Component;
/**
* @author fzy
* @date 2024/1/22 13:58
*/
public class User {
}- 那
User Bean
的默认id
就是user
。
- 那
12.6 多个包扫描
如果要对多个包进行扫描,有两种解决方案:
第一种:在配置文件中指定多个包,用逗号隔开。
1
2
3
4
5
6
7
8
9
<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">
<!--给Spring框架指定要扫描哪些包中的类-->
<context:component-scan base-package="com.f.spring6.bean, com.f.spring6.dao"/>
</beans>第二种:指定多个包的共同父包。
- Spring 会扫描指定的包中的类,及其子包中的类。
- 但会牺牲一些效率。
1
2
3
4
5
6
7
8
9
<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.f.spring6"/>
</beans>
12.7 选择性实例化Bean
选择性实例化
Bean
,就是只选择满足某些条件的类进行bean
的实例化,或者排除满足某些条件的类,对这些类不进行Bean
的实例化。例如:假设在某个包下有很多
Bean
,有的Bean
上标注了@Component
,有的标注了@Controller
,有的标注了@Service
,有的标注了@Repository
。现在由于某种特殊业务的需要,只允许其中所有的@Controller
参与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
35
36
37
38package com.f.spring6.bean2;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Controller;
import org.springframework.stereotype.Repository;
import org.springframework.stereotype.Service;
/**
* @author fzy
* @date 2024/1/22 16:52
*/
public class A {
public A() {
System.out.println("A的无参数构造方法执行...");
}
}
class B {
public B() {
System.out.println("B的无参数构造方法执行...");
}
}
class C {
public C() {
System.out.println("C的无参数构造方法执行...");
}
}
class D {
public D() {
System.out.println("D的无参数构造方法执行...");
}
}
方式一:
use-default-filters="false"
+context:include-filter
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<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">
<!--
第一种解决方案:use-default-filters="false"
如果这个属性是false,表示com.f.spring6.bean2包下所有的带有声明Bean的注解全部失效。
即@Component、@Controller、@Service、@Repository全部失效
我们再使用context:include-filter来使得某几种注解生效
-->
<context:component-scan base-package="com.f.spring6.bean2" use-default-filters="false">
<!--只有@Repository生效-->
<context:include-filter type="annotation" expression="org.springframework.stereotype.Repository"/>
</context:component-scan>
</beans>use-default-filters="false"
:不使用 Spring 默认的实例化规则,即所有带有声明 bean 的注解全部失效,用注解 Component、Controller、Service、Repository 标注的 bean 都不进行实例化(让所有的声明 bean 的注解失效)。- 再使用
context:include-filter
来使得某几种注解生效。 - 注意:由于其他三个注解只是
@Component
的别名,所以包含@Component
也就包含了其他三种注解。- 也就是说,当用下面的标签时:
<context:include-filter type="annotation" expression="org.springframework.stereotype.Component"/>
,会使得注解 Component、Controller、Service、Repository 标注的 bean 都生效。
- 也就是说,当用下面的标签时:
方式二:
use-default-filters="true"
+context:exclude-filter
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<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">
<!--
第二种解决方案:use-default-filters="true"
如果这个属性是true,表示com.f.spring6.bean2包下所有的带有声明Bean的注解全部有效。
即@Component、@Controller、@Service、@Repository全部有效。
我们再使用context:exclude-filter来使得某几种注解失效
use-default-filters默认值就是true,可以不用写
-->
<context:component-scan base-package="com.f.spring6.bean2" use-default-filters="true">
<!--只有@Repository失效-->
<context:exclude-filter type="annotation" expression="org.springframework.stereotype.Repository"/>
</context:component-scan>
</beans>use-default-filters="true"
:使用 Spring 默认的实例化规则,即所有带有声明 bean 的注解全部生效,用注解 Component、Controller、Service、Repository 标注的 bean 都进行实例化。use-default-filters
默认值就是true
,可以不用写。
- 再使用
context:exclude-filter
来使得某几种注解失效。 - 注意:由于其他三个注解只是
@Component
的别名,所以排除@Component
也就排除了其他三种注解。
12.8 负责注入的注解
@Component
、@Controller
、@Service
、@Repository
这四个注解是用来声明Bean
的,声明后这些Bean
将被实例化。接下来我们看一下,如何给Bean
的属性赋值。给
Bean
属性赋值需要用到这些注解:@Value
@Autowired
@Qualifier
@Resource
12.8.1 ★@Value
用于简单类型注入。
当属性的类型是简单类型时,可以使用
@Value
注解进行注入。@Value
注解用于代替<property name="" value=""/>
用在属性上:
@Value
可以直接写在属性上,并且可以不用提供对应的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
47
48
49
50
51
52
53
54
55package com.f.spring6.bean3;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import javax.sql.DataSource;
import java.io.PrintWriter;
import java.sql.Connection;
import java.sql.SQLException;
import java.sql.SQLFeatureNotSupportedException;
import java.util.logging.Logger;
/**
* @author fzy
* @date 2024/1/24 12:43
*/
public class MyDataSource implements DataSource {
private String driver;
private String url;
private String username;
private String password; //使用@Value注解注入的话,可以用在属性上,并且可以不提供set方法。
public String toString() {
return "MyDataSource{" +
"driver='" + driver + '\'' +
", url='" + url + '\'' +
", username='" + username + '\'' +
", password='" + password + '\'' +
'}';
}
//public void setDriver(String driver) {
// this.driver = driver;
//}
//
//public void setUrl(String url) {
// this.url = url;
//}
//
//public void setUsername(String username) {
// this.username = username;
//}
//
//public void setPassword(String password) {
// this.password = password;
//}
......
}用在
set
方法上:@Value
也可以写在属性对应的set
方法上,实现属性值的注入。为了简化代码,一般不提供
set
方法,而是直接在属性上使用@Value
注解完成属性赋值。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
33package com.f.spring6.bean3;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
/**
* @author fzy
* @date 2024/1/24 12:56
*/
public class User2 {
private String name;
private int age;
// @Value注解也可以使用在方法上
public void setName(String name) {
this.name = name;
}
public void setAge(int age) {
this.age = age;
}
public String toString() {
return "User2{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
用在构造方法的形参上:
@Value
注解也可以用在构造方法的形参上。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
27package com.f.spring6.bean3;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
/**
* @author fzy
* @date 2024/1/24 13:02
*/
public class Product {
private String name;
private double price;
public Product(double price) { String name,
this.name = name;
this.price = price;
}
public String toString() {
return "Product{" +
"name='" + name + '\'' +
", price=" + price +
'}';
}
}
12.8.2 @Autowired
@Autowired
注解用来注入非简单类型。Autowired
:自动连线,或自动装配。@Autowired
:通过注解的方式进行自动装配。@Autowired
注解不需要set
方法。
单独使用
@Autowired
注解,默认根据类型进行装配,即默认是byType
,如果需要根据名字进行自动装配,则需要配合@Qualifier
注解。@Autowired
注解是根据类型进行自动装配,如果只使用@Autowired
注解的话,Spring 容器中不能存在两个相同类型的实例,例如两个类实现了同一个接口,如果要使用@Autowired
注解并且 Spring 容器中存在两个相同类型的实例,则需要配合@Qualifier
注解,根据名称进行装配。
1
2
3
4
5
6
7
8
9package org.f.spring6.dao;
/**
* @author fzy
* @date 2024/1/24 13:11
*/
public interface OrderDao {
void insert();
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16package org.f.spring6.dao.impl;
import org.f.spring6.dao.OrderDao;
import org.springframework.stereotype.Repository;
/**
* @author fzy
* @date 2024/1/24 13:12
*/
public class OrderDaoImplForMysql implements OrderDao {
public void insert() {
System.out.println("Mysql数据库正在保存订单信息...");
}
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16package org.f.spring6.dao.impl;
import org.f.spring6.dao.OrderDao;
import org.springframework.stereotype.Repository;
/**
* @author fzy
* @date 2024/1/24 13:27
*/
public class OrderDaoImplForOracle implements OrderDao {
public void insert() {
System.out.println("Oracle数据库正在保存订单信息...");
}
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21package org.f.spring6.service;
import org.f.spring6.dao.OrderDao;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
/**
* @author fzy
* @date 2024/1/24 13:13
*/
public class OrderService {
// @Autowired注解使用的时候,不需要指定任何属性,直接使用这个注解即可。
// 这个注解的作用是根据类型byType进行自动装配
private OrderDao orderDao;
public void generate() {
orderDao.insert();
}
}1
2
3
4
5
6
7
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="org.f.spring6"/>
</beans>1
2
3
4
5
6
public void testAutowired() {
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring-autowired.xml");
OrderService orderService = applicationContext.getBean("orderService", OrderService.class);
orderService.generate();
}报异常:
1
org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'orderService': Unsatisfied dependency expressed through field 'orderDao': No qualifying bean of type 'org.f.spring6.dao.OrderDao' available: expected single matching bean but found 2: orderDaoImplForMysql,orderDaoImplForOracle
12.8.3 @Qualifier
@Autowired
注解和@Qualifier
注解联合起来使得可以根据名称进行装配,在@Qualifier
注解中指定 Bean 名称。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24package org.f.spring6.service;
import org.f.spring6.dao.OrderDao;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Service;
/**
* @author fzy
* @date 2024/1/24 13:13
*/
public class OrderService {
// @Autowired注解使用的时候,不需要指定任何属性,直接使用这个注解即可。
// 这个注解的作用是根据类型byType进行自动装配
// @Autowired和@Qualifier联合使用,可以根据名字进行自动装配
private OrderDao orderDao;
public void generate() {
orderDao.insert();
}
}输出结果:
1
Mysql数据库正在保存订单信息...
12.8.4 ★@Resource
@Resource
注解也可以完成非简单类型注入。@Resource
注解是JDK 扩展包中的,也就是说属于 JDK 的一部分,而@Autowired
注解是 Spring 框架自己的。@Resource
是JDK 标准规范中的,更具有通用性,更推荐使用。
@Resource
注解默认根据名称装配byName
,未指定name
时,使用属性名作为name
。通过name
找不到的话会自动启动通过类型byType
装配。@Resource
注解用在类上、属性上、方法上,不能用在构造方法上。@Resource
注解属于 JDK 扩展包,所以不在 JDK 当中,需要额外引入以下依赖(如果是 JDK8 的话不需要额外引入依赖,高于 JDK11 或低于 JDK8 则需要引入以下依赖。):1
2
3
4
5
6<!--@Resource注解的依赖-->
<dependency>
<groupId>jakarta.annotation</groupId>
<artifactId>jakarta.annotation-api</artifactId>
<version>2.1.1</version>
</dependency>
1 | package cn.f.spring6.dao; |
1 | package cn.f.spring6.dao.impl; |
1 | package cn.f.spring6.service; |
1 |
|
1 |
|
12.9 全注解开发
- 全注解开发就是不再使用 Spring 配置文件,而是写一个配置类来代替配置文件。
1 | package cn.f.spring6; |
1 |
|
- 获取 Spring 容器不再是
new ClassPathXmlApplicationContext()
对象了,而是new AnnotationConfigApplicationContext()
。