1. 回顾注解
注解的存在主要是为了简化 XML 的配置,Spring6倡导全注解开发
// 元注解:标注注解的注解。Target:注解可以出现的位置
// 表示 @Component 可以出现在 类上、属性上
@Target(value = {ElementType.TYPE, ElementType.FIELD})
// @Retention 也是一个元注解,用来标注 @Component 注解最终保留在 class 文件当中,并可以被反射机制读取。
@Retention(RetentionPolicy.RUNTIME)
public @interface Component {
String name();
int[] nums();
}
@Component(name = "value", nums = {1, 2, 3})
public class User {
}
public class ReflectAnnotationTest1 {
public static void main(String[] args) throws Exception {
Class<?> aClass = Class.forName("com.powernode.spring6.bean.User");
// 判断类上有没有这个注解
if (aClass.isAnnotationPresent(Component.class)) {
// 获取类上的注解
Component annotation = aClass.getAnnotation(Component.class);
// 访问注解属性
System.out.println(annotation.name());
}
}
}
组件扫描原理
public class ComponentScan {
public static void main(String[] args) {
Map<String, Object> beanMap = new HashMap<String, Object>();
// 只知道包的名字,扫描这个包下所有类,当这个类上有 @Component 注解的时候,实例化该对象,然后放到 Map 集合当中
String packageName = "com.powernode.spring6.bean";
// 写扫描程序
// . 在正则表达式中表示任意字符
String packagePath = packageName.replaceAll("\\.", "/");
// com 是在类的根路径下的一个目录
URL url = ClassLoader.getSystemClassLoader().getResource(packagePath);
String path = url.getPath();
// 获取绝对路径下的所有文件
File file = new File(path);
File[] files = file.listFiles();
Arrays.stream(files).forEach(f -> {
// System.out.println(f.getName().split("\\.")[0]);
try {
String className = packageName + "." + f.getName().split("\\.")[0];
// 通过反射机制解析注解
Class<?> aClass = null;
aClass = Class.forName(className);
// 判断类上是否有这个注解
if (aClass.isAnnotationPresent(Component.class)) {
// 获取注解
Component component = aClass.getAnnotation(Component.class);
String id = component.value();
// 有这个注解的都要创建对象
Object o = aClass.newInstance();
beanMap.put(id, o);
}
} catch (Exception e) {
e.printStackTrace();
}
});
System.out.println(beanMap);
}
}
2. 声明 Bean 的注解
负责声明Bean的注解,常见的包括四个:
- @Component
- @Controller
- @Service
- @Repository
这几个本质上没区别,都是 @Component 的别名,就是在三层架构中起一个规范的作用
3. Spring 注解的使用
如何使用上述注解?
- 加入 aop 的依赖
- 在配置文件中添加 context 命名空间
- 在配置文件中指定扫描的包
- 在 Bean 类上使用注解
第一步:加入 aop 依赖
在加入 spring-context 依赖后,会关联加入 aop 的依赖。
第二步:在配置文件中添加 context 命名空间
<?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">
<!-- 给 Spring 框架指定要扫描哪些包中的类 -->
<context:component-scan base-package="com.powernode.spring6.bean"/>
</beans>
4. 多个包扫描
<!-- 多个包使用逗号隔开 -->
<context:component-scan base-package="com.powernode.spring6.bean, com.powernode.spring6.dao"/>
<!-- 也可以指定这些包的共同父包,但这样效率会降低 -->
<context:component-scan base-package="com.powernode.spring6" --></context:component-scan>
5. 选择性实例化 Bean
假设在某个包下有很多 Bean,有的是 Component,有的是 Controller。但因为某种特殊的业务,我们只允许 Controller 参与Bean管理,其他的都不实例化,该怎么做?
<!-- 第一种解决方案
use-default-filters="false"
false 表示包下所有的带有声明 Bean 的注解全部失效 @Component、@Service、@Service、@Repository
-->
<context:component-scan base-package="com.powernode.spring6.bean2" use-default-filters="false">
<!-- 只有 @Repository 被包含进来 -->
<context:include-filter type="annotation" expression="org.springframework.stereotype.Repository"/>
<context:include-filter type="annotation" expression="org.springframework.stereotype.Service"/>
</context:component-scan>
<!-- 第二种解决方案
use-default-filters="true"
false 表示包下所有的带有声明 Bean 的注解全部生效
-->
<context:component-scan base-package="com.powernode.spring6.bean2" use-default-filters="true">
<!-- 只有 @Repository 被包含进来 -->
<context:exclude-filter type="annotation" expression="org.springframework.stereotype.Repository"/>
<context:exclude-filter type="annotation" expression="org.springframework.stereotype.Service"/>
</context:component-scan>
5. 负责注入的注解
上面那四个注解是用来声明 Bean 的,声明之后 Bean 就会实例化,然后呢,就要给 Bean 的属性赋值啦,用到的注解有:
- @Value
- @Autowired
- @Qualifier
- @Resource
5.1 @Value
当属性的类型是简单类型时,可以使用 @Value 注解注入
@Component
public class User2 {
@Value("张二蛋")
private String name;
@Value("30")
private int age;
public User2(@Value("张四蛋") String name, @Value("38") int age) {
this.name = name;
this.age = age;
}
@Value("张三蛋")
public void setName(String name) {
this.name = name;
}
@Value("33")
public void setAge(int age) {
this.age = age;
}
@Override
public String toString() {
return "User2{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
5.2 @Autowired 与 @Qualifier
@Autowired 注解可以用来注入非简单类型
单独使用 @Autowired 注解,默认根据类型自动装配(byType)
该注解可以标注在哪里?
- 构造方法上
- 方法上
- 形参上
- 属性上
- 注解上
该注解有一个 required 属性,默认值是true,表示注入的时候要求被注入的 Bean 必须是存在的,如果不存在则报错。
根据类型
// @Autowired 使用时,不需要指定任何属性,直接使用
// 根据 byType 自动装配
@Autowired
private OrderDao orderDao;
根据名字
@Autowired
@Qualifier("orderDaoImplForOracle")
private OrderDao orderDao;
public void generate() {
orderDao.insert();
}
如果一个类当中构造方法只有一个,并且构造方法上的参数和属性都能够对应上,@Autowired 注解可以省略
5.3 @Resource
@Resource 注解也可以完成非简单类型注入,那他与 @Autowired 有什么区别呢?
- @Resource 注解是 JDK 扩展包中的,也就是说属于 JDK 的一部分。所以该注解是标准注解,更具有通用性。
- @Autowired 注解是Spring 框架自己的。
- @Resource 注解默认根据名称装配 byName,未指定 name 时,使用属性名作为 name。通过 name 中不到的话会自动启动通过类型 byType 装配。
- @Autowired 注解默认根据类型装配 byType,如果想根据名称,需要配合 @Qualifier 注解一起用。
- @Resource 注解用在属性上、setter 方法上。
- @Autowired 注解用在属性上、setter 方法上、构造方法上、构造方法参数上。
@Resource 注解属于 JDK 扩展包,不在 JDK 中,需要额外引入下面的依赖(JDK 8 不用)
<dependency>
<groupId>jakarata.annotation</groupId>
<artifactId>jakarta.annotation-api</artifactId>
<version>2.1.1</version>
</dependency>
一定要注意:Spring6 已不再支持 JavaEE,它支持 JakartaEE9。(Oracle 把 JavaEE 给 Apache 了,Apache 给改名了,之前接触的所有
javax.*一律改为jakarta.*了
Spring5 则引入下面的依赖
<dependency>
<groupId>javax.annotation</groupId>
<artifactId>javax.annotation-api</artifactId>
<version>1.3.2</version>
</dependency>
@Service("studentService")
public class StudentService {
@Resource(name="studentDaoImplForMySQL")
private StudentDao studentDao;
public void deleteStudent () {
studentDao.deleteById();
}
}
5.4 全注解开发
@Configuration
@ComponentScan({"cn.powernode.dao", "cn.powernode.service"})
public class Spring6Config {
}
@Test
public void testNoXML() {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(Spring6Config.class);
StudentService studentService = context.getBean("studentService", StudentService.class);
studentService.deleteStudent();
}