根据 【动力节点】最新Spring框架教程,全网首套Spring6教程,跟老杜从零学spring入门到高级 以及老杜的原版笔记 https://www.yuque.com/docs/share/866abad4-7106-45e7-afcd-245a733b073f?# 《Spring6》 进行整理, 文档密码:mg9b
Spring 相关文章整理汇总归纳于:https://www.yuque.com/u27599042/zuisie
- Spring6 倡导全注解开发
- 注解的存在主要是为了简化 XML 的配置。
- 注解其实就是一个标记
回顾自定义注解
- 元注解:标注注解的注解
- @Target注解:用来修饰注解可以出现的位置
- @Target(value = {ElementType.TYPE, ElementType.FIELD}),Target标注的注解可以出现在类或字段上
- @Target(value = {ElementType.TYPE}),Target标注的注解可以出现在类上
- 使用某个注解的时候,如果注解的属性名是value的话,value可以省略。
- @Target({ElementType.TYPE})
- 使用某个注解的时候,如果注解的属性值是数组,并且数组中只有一个元素,大括号可以省略。
- @Target(ElementType.TYPE)
- @Retention:也是一个元注解,用来标注注解最终可以保留到什么时刻
- 如果注解要保存到class文件当中,并且可以被反射机制读取,则使用@Retention(RetentionPolicy.RUNTIME)
- @Retention(RetentionPolicy.SOURCE),表示Retention标注的注解只能保留到源文件中,字节码和运行时看不到Retention标注的注解
- 自定义注解与定义注解属性:
// 标注该注解可以用在类上 @Target(ElementType.TYPE) // 标注@Component注解最终保留在class文件当中,并且可以被反射机制读取。 @Retention(RetentionPolicy.RUNTIME) public @interface Component { // 定义注解的属性 // String是属性类型 // value是属性名 String value(); // 其他的属性 // 属性类型String // 属性名是name //String name(); // 数组属性 // 属性类型是:String[] // 属性名:names //String[] names(); //int[] ages(); //int age(); }
回顾自定义注解的使用
@Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) public @interface Component { String value(); }
@Component("user") public class User {}
通过反射获取注解
@org.junit.Test public void testGetAnnotationByReflect() throws Exception{ // 将类加载到JVM中 Class<?> clazz = Class.forName("cw.study.spring.bean.User"); // 判断类上是否有自定义的Component注解 if (clazz.isAnnotationPresent(Component.class)) { // 如果有,获取该注解 Component component = clazz.getAnnotation(Component.class); // 获取该类上注解的属性值 System.out.println(component.value()); } }
组件扫描原理
- 需求:给定一个包名,将这个包下的所有的带@Component注解的类进行实例化
@Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) public @interface Component { String value(); }
@Component("user") public class User {} @Component("cat") public class Cat {} public class Person {}
@org.junit.Test public void componentScan() { // 已知的包名如下,需要扫描包下所有的类,将有Component注解的类实例化 String packageName = "cw.study.spring.bean"; // 包名其实就是目录名,将包名转化为目录 // . 在正则中代表任意字符,需要进行转义,而 \\ 表示 \ 所以使用 \\. String directoryName = packageName.replaceAll("\\.", "/"); // 软件包是在类路径下的,所以可以使用系统加载器加载目录获取URL URL url = ClassLoader.getSystemClassLoader().getResource(directoryName); // 通过URL获取包对应的绝对路径 String path = url.getPath(); System.out.println("path = " + path); // 根据绝对路径创建目录对应的对象 File file = new File(path); // 获取目录下的文件对象 File[] files = file.listFiles(); System.out.println(files.length); // 遍历每个文件对象 Arrays.stream(files).forEach(f -> { try { // 获取文件名 String fName = f.getName(); // System.out.println("fName = " + fName); // 得到类名 String className = packageName + "." + fName.split("\\.")[0]; // 加载类到JVM Class<?> clazz = Class.forName(className); // 判断类上是否有Component注解 if (clazz.isAnnotationPresent(Component.class)) { // 获取注解 Component component = clazz.getAnnotation(Component.class); // 获取注解的属性(Bean的id) String id = component.value(); System.out.println(id); // 实例化对象 Object o = clazz.getDeclaredConstructor().newInstance(); System.out.println(o); } } catch (Exception e) { e.printStackTrace(); } }); }
声明 Bean 的注解
- 负责声明Bean的注解,常见的包括四个:
- @Component:组件
- @Controller:控制器
- @Service:业务
- @Repository:仓库(Dao)
- 上面四个注解中,@Controller,@Service,@Repository 都为 @Component 的别名(@AliasFor),其实这四个注解的功能都一样,用哪个都可以,但是在不同用途的 Bean 上使用不同的注解可以增强程序的可读性:
- 普通 bean:Component
- 控制器类上使用:Controller(控制层)
- service类上使用:Service(业务层)
- dao类上使用:Repository(持久层)
- 上面四个注解中,都是只有一个value属性,用来指定 bean 的 id
@Target({ElementType.TYPE}) // 只能使用在类上 @Retention(RetentionPolicy.RUNTIME) // 可以通过反射获取该注解 @Documented @Indexed public @interface Component { String value() default ""; } @Target({ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @Documented @Component public @interface Controller { // Component的别名 @AliasFor( annotation = Component.class ) String value() default ""; } @Target({ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @Documented @Component public @interface Service { // Component的别名 @AliasFor( annotation = Component.class ) String value() default ""; } @Target({ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @Documented @Component public @interface Repository { // Component的别名 @AliasFor( annotation = Component.class ) String value() default ""; }
Spring 注解的使用
第一步:加入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"> </beans>
第三步:在配置文件中指定扫描的包
- 在配置文件中指定扫描的包,告诉Spring需要进行扫描的包在什么位置
- Spring会扫描指定的包中的类,及其子包中的类,如果类被声明Bean的注解标记,则会创建该类的实例并将其放到Spring容器中
<?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="cw.study.spring"/> </beans>
第四步:在Bean类上使用注解
@Component("user") public class User {} @Controller("userController") public class UserController {}
测试
@org.junit.Test public void test01() { ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.xml"); Object user = applicationContext.getBean("user"); Object userController = applicationContext.getBean("userController"); System.out.println(user); System.out.println(userController); }
注意点
- 如果注解的属性名是value,那么value是可以省略的。
- 如果把value属性彻底去掉,spring会为Bean自动取名,默认名字的规律是:Bean类名首字母小写即可。
@Component public class User {} @Controller public class UserController {} @org.junit.Test public void test02() { ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.xml"); Object user = applicationContext.getBean("user"); Object userController = applicationContext.getBean("userController"); System.out.println(user); System.out.println(userController); }
- 如果要对多个包进行扫描,有两种解决方案:
- 第一种:在配置文件中指定多个包,用逗号隔开。
<context:component-scan base-package="cw.study.spring.bean, cw.study.spring.controller"/>
- 第二种:指定多个包的共同父包。
- Spring会扫描指定的包中的类,及其子包中的类
- 使用此方式会牺牲一些效率
<context:component-scan base-package="cw.study.spring"/>
选择性实例化 Bean
- 选择性实例化 Bean,就是只选择满足某些条件的类进行 bean 的实例化,或者排除满足某些条件的类,对这些类不进行 Bean 的实例化
package cw.spring.bean; import org.springframework.stereotype.Component; import org.springframework.stereotype.Controller; import org.springframework.stereotype.Repository; import org.springframework.stereotype.Service; /** * ClassName: A * Package: cw.spring.bean * Description: * * @Author tcw * @Create 2023-05-14 11:42 * @Version 1.0 */ @Component public class A { public A() { System.out.println("A的无参数构造方法执行"); } } @Controller class B { public B() { System.out.println("B的无参数构造方法执行"); } } @Service class C { public C() { System.out.println("C的无参数构造方法执行"); } } @Repository class D { public D() { System.out.println("D的无参数构造方法执行"); } } @Controller class E { public E() { System.out.println("E的无参数构造方法执行"); } }
方式一:**use-default-filters=“false” + **context:include-filter
- use-default-filters=“false”:不使用 spring 默认的实例化规则,即所有带有声明 bean 的注解全部失效,用注解 Component、Controller、Service、Repository 标注的 bean 都不进行实例化(让所有的声明 bean 的注解失效)
- use-default-filters 的默认值为 true
<context:include-filter type="annotation" expression="org.springframework.stereotype.Component"/>
:
- 表示被 Component 注解标注的 bean 进行实例化
- type 属性用于指定过滤类的条件的类型,即根据什么进行过滤,type=“annotation”,根据类的注解进行过滤
- expression 属性用于指定过滤的条件,expression=“org.springframework.stereotype.Component”,只有被Component注解标注的类才进行实例化
- 由于其他三个注解只是 Component 的别名,所以包含 Component 也就包含其他三种注解
- 注意:使用context:include-filter,use-default-filters的属性值必须为false,否则context:include-filter无效
<!-- 指定要进行扫描的包 --> <context:component-scan base-package="cw.spring.bean" use-default-filters="false"> <!-- 被 Component 注解标注的 bean 进行实例化 --> <!-- 由于其他三个注解只是 Component 的别名,所以包含 Component 也就包含其他三种注解 --> <!-- <context:include-filter type="annotation" expression="org.springframework.stereotype.Component"/> --> <!-- 被 Controller 注解标注的 bean 进行实例化 --> <context:include-filter type="annotation" expression="org.springframework.stereotype.Controller"/> </context:component-scan>
方式二:**use-default-filters=“true” + **context:exclude-filter
- use-default-filters=“true”:使用 spring 默认的实例化规则,即所有带有声明 bean 的注解全部生效,用注解 Component、Controller、Service、Repository 标注的 bean 都进行实例化
- use-default-filters 的默认值为 true
<context:exclude-filter type="annotation" expression="org.springframework.stereotype.Component"/>
:
- 表示被 Component 注解标注的 bean 不进行实例化,排除被 Component 注解标注的 bean
- 由于其他三个注解只是 Component 的别名,所以排除 Component 也就排除了其他三种注解
<!-- 指定要进行扫描的包 --> <context:component-scan base-package="cw.spring.bean"> <!-- 排除被 Component 注解标注的 bean,这些 bean 不参与实例化 --> <!-- 由于其他三个注解只是 Component 的别名,所以排除 Component 也就排除了其他三种注解 --> <!-- <context:exclude-filter type="annotation" expression="org.springframework.stereotype.Component"/> --> <!-- 排除被 Controller 注解标注的 bean,这些 bean 不参与实例化 --> <context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller"/> </context:component-scan>
负责注入的注解
- @Component @Controller @Service @Repository 这四个注解只是用来声明 Bean 的,声明后,这些 Bean 将会被实例化。
- 给 Bean 的属性赋值,使用如下注解:
- @Value
- @Autowired
- @Qualifier
- @Resource
@Value
- 用于简单类型注入
- 当属性的类型是简单类型时,可以使用@Value注解进行注入。
- @Value注解用于代替
<property name="" value=""/>
@Target({ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER, ElementType.ANNOTATION_TYPE}) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface Value { String value(); }
用在属性上
- @value 可以直接写在属性上,可以不用提供对应的 set 方法
package cw.spring.bean; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Component; /** * ClassName: MyDataSource * Package: cw.spring.bean * Description: * * @Author tcw * @Create 2023-05-14 12:13 * @Version 1.0 */ @Component public class MyDataSource { @Value("com.mysql.cj.jdbc.Driver") private String driver; @Value("jdbc:mysql://localhost:3306/spring") private String url; @Value("root") private String username; @Value("123456") private String password; @Override public String toString() { return "MyDataSource{" + "driver='" + driver + '\'' + ", url='" + url + '\'' + ", username='" + username + '\'' + ", password='" + password + '\'' + '}'; } }
用在 set 方法上
- @value 也可以写在属性对应的 set 方法上,实现属性值的注入
- 为了简化代码,一般不提供 set 方法,直接在属性上使用@Value 注解完成属性赋值。
package cw.spring.bean; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Component; /** * ClassName: MyDataSource * Package: cw.spring.bean * Description: * * @Author tcw * @Create 2023-05-14 12:13 * @Version 1.0 */ @Component public class MyDataSource { private String driver; private String url; private String username; private String password; @Value("com.mysql.cj.jdbc.Driver") public void setDriver(String driver) { this.driver = driver; } @Value("jdbc:mysql://localhost:3306/spring") public void setUrl(String url) { this.url = url; } @Value("root") public void setUsername(String username) { this.username = username; } @Value("123456") public void setPassword(String password) { this.password = password; } @Override public String toString() { return "MyDataSource{" + "driver='" + driver + '\'' + ", url='" + url + '\'' + ", username='" + username + '\'' + ", password='" + password + '\'' + '}'; } }
用在构造方法的形参上
- @Value 注解也可以用在构造方法的形参上
package cw.spring.bean; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Component; /** * ClassName: MyDataSource * Package: cw.spring.bean * Description: * * @Author tcw * @Create 2023-05-14 12:13 * @Version 1.0 */ @Component public class MyDataSource { private String driver; private String url; private String username; private String password; public MyDataSource( @Value("com.mysql.cj.jdbc.Driver") String driver, @Value("jdbc:mysql://localhost:3306/spring") String url, @Value("root") String username, @Value("123123") String password ) { this.driver = driver; this.url = url; this.username = username; this.password = password; } @Override public String toString() { return "MyDataSource{" + "driver='" + driver + '\'' + ", url='" + url + '\'' + ", username='" + username + '\'' + ", password='" + password + '\'' + '}'; } }
@Autowired
- @Autowired 注解用来注入非简单类型。
- Autowired:自动连线,或自动装配
- @Autowired,通过注解的方式进行自动装配
- @Autowired 注解不需要 set 方法
- 单独使用 @Autowired 注解,默认根据类型进行装配,即默认是 byType,如果需要根据名字进行自动装配则需要配合 @Qualifier 注解
- @Autowired 注解是根据类型进行自动装配,如果只使用@Autowired 注解的话,Spring容器中不能存在两个相同类型的实例,如果要使用@Autowired 注解并且Spring容器中存在两个相同类型的实例,则需要配合 @Qualifier 注解,根据名称进行装配
- 该注解可以标注在构造方法上、方法上、形参上、属性上、注解上,用法书写位置和@Value一样
- 该注解有一个 required 属性,默认值是 true,表示在注入的时候要求被注入的 Bean 必须是存在的,如果不存在则报错。如果 required 属性设置为 false,表示注入的 Bean 存在或者不存在都没关系,存在的话就注入,不存在的话,也不报错。
@Target({ElementType.CONSTRUCTOR, ElementType.METHOD, ElementType.PARAMETER, ElementType.FIELD, ElementType.ANNOTATION_TYPE}) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface Autowired { boolean required() default true; }
- @Autowired 注解使用的时候,可以不指定任何属性,直接使用
/** * ClassName: OrderDao * Package: cw.spring.dao * Description: * * @Author tcw * @Create 2023-05-14 12:50 * @Version 1.0 */ public interface OrderDao { void insert(); }
/** * ClassName: OrderDaoImplForMySQL * Package: cw.spring.dao.impl * Description: * * @Author tcw * @Create 2023-05-14 12:50 * @Version 1.0 */ @Repository public class OrderDaoImplForMySQL implements OrderDao { @Override public void insert() { System.out.println("MySQL数据库正在保存订单信息..."); } }
import cw.spring.dao.OrderDao; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; /** * ClassName: OrderService * Package: cw.spring.service * Description: * * @Author tcw * @Create 2023-05-14 12:49 * @Version 1.0 */ @Service("orderService") public class OrderService { @Autowired private OrderDao orderDao; public void save() { orderDao.insert(); } }
- 当类中构造方法只有一个时,并且构造方法上的参数和需要注入的属性能够对应上,@Autowired 注解可以省略,如果有多个构造方法,@Autowired 不能省略的。
- 最好不要省略,程序的可读性更高
/** * ClassName: OrderService * Package: cw.spring.service * Description: * * @Author tcw * @Create 2023-05-14 12:49 * @Version 1.0 */ @Service("orderService") public class OrderService { // @Autowired // @Qualifier("orderDaoImplForOracle") private OrderDao orderDao; public OrderService(OrderDao orderDao) { this.orderDao = orderDao; } public void save() { orderDao.insert(); } }
@Qualifier
- @Autowired 注解和 @Qualifier 注解联合起来才可以根据名称进行装配,在@Qualifier注解中指定Bean名称。
/** * ClassName: OrderDaoImplForOracle * Package: cw.spring.dao.impl * Description: * * @Author tcw * @Create 2023-05-14 12:59 * @Version 1.0 */ @Repository public class OrderDaoImplForOracle implements OrderDao { @Override public void insert() { System.out.println("Oracle数据库正在保存订单信息..."); } }
/** * ClassName: OrderService * Package: cw.spring.service * Description: * * @Author tcw * @Create 2023-05-14 12:49 * @Version 1.0 */ @Service("orderService") public class OrderService { @Autowired @Qualifier("orderDaoImplForOracle") private OrderDao orderDao; public void save() { orderDao.insert(); } }
@Resource
- @Resource注解可以完成非简单类型注入。
- @Resource注解是JDK扩展包中的,也就是说属于JDK的一部分,而@Autowired注解是Spring框架自己的。
- @Resource是JDK标准规范中的,更具有通用性,更推荐使用
- @Resource注解默认根据名称装配byName,未指定name时,使用属性名作为name。通过name找不到的话会自动启动通过类型byType装配。
- @Resource注解用在属性上、setter方法上、方法上。
@Target({ElementType.TYPE, ElementType.FIELD, ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) @Repeatable(Resources.class) public @interface Resource { String name() default ""; String lookup() default ""; Class<?> type() default Object.class; AuthenticationType authenticationType() default Resource.AuthenticationType.CONTAINER; boolean shareable() default true; String mappedName() default ""; String description() default ""; public static enum AuthenticationType { CONTAINER, APPLICATION; private AuthenticationType() { } } }
- @Resource注解属于JDK扩展包,所以不在JDK当中,需要额外引入以下依赖
- 引入扩展依赖
- 如果是JDK8的话不需要额外引入依赖,高于JDK11或低于JDK8需要引入以下依赖。
<dependency> <groupId>jakarta.annotation</groupId> <artifactId>jakarta.annotation-api</artifactId> <version>2.1.1</version> </dependency>
<dependency> <groupId>javax.annotation</groupId> <artifactId>javax.annotation-api</artifactId> <version>1.3.2</version> </dependency>
/** * ClassName: StudentDao * Package: cw.spring.dao * Description: * * @Author tcw * @Create 2023-05-14 16:03 * @Version 1.0 */ public interface StudentDao { void deleteById(); }
/** * ClassName: StudentDaoImplForMySQL * Package: cw.spring.dao.impl * Description: * * @Author tcw * @Create 2023-05-14 16:04 * @Version 1.0 */ @Repository public class StudentDaoImplForMySQL implements StudentDao { @Override public void deleteById() { System.out.println("MySQL数据库正在删除学生信息..."); } }
/** * ClassName: StudentService * Package: cw.spring.service * Description: * * @Author tcw * @Create 2023-05-14 16:05 * @Version 1.0 */ @Service public class StudentService { // name属性用于指定将要被注入到该属性的Bean的名字 @Resource(name = "studentDaoImplForMySQL") private StudentDao studentDao; public void delete() { studentDao.deleteById(); } }
全注解开发
- 全注解开发就是不再使用 spring 配置文件,写一个配置类来代替配置文件。
@Configuration 注解标注配置类
- 配置类使用 @Configuration 注解进行标注
@Configuration public class Spring6Config { }
@ComponentScan 注解配置扫描的包
- 通过 @ComponentScan 注解配置要扫描的包
@Configuration @ComponentScan({"cw.spring.dao", "cw.spring.service"}) public class Spring6Config { }
全注解开发下获取 Spring 容器
- 获取 spring 容器不再
new ClassPathXmlApplicationContext()
对象了,而是new AnnotationConfigApplicationContext()
@org.junit.Test public void test03() { // 获取Spring容器对象时,需要传配置类为参数 ApplicationContext applicationContext = new AnnotationConfigApplicationContext(Spring6Config.class); StudentService studentService = applicationContext.getBean("studentService", StudentService.class); studentService.delete(); }