⭐表示重要。
第一章:标记和扫描(⭐)
1.1 注解的作用
1.1.1 注解
- 注解和 XML 配置文件一样,注解本身并不能执行,注解本身仅仅只是做了一个标记,具体的功能是框架检测到注解标记的位置,然后针对这个位置按照注解标记的功能来执行具体的操作。
- 本质上,所有的一切操作都是Java代码来完成的,XML 和注解只是告诉框架中的 Java 代码如何执行。
1.1.2 扫描
- Spring 为了知道程序员在哪些地方标记了什么注解,就需要通过扫描的方式,来进行检测,然后根据注解进行后续操作。
1.2 环境准备
- JDK 11+。
- IDEA 2021+。
- Maven 3.8。
1.3 导入依赖
- pom.xml
<!-- Spring -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.3.12</version>
</dependency>
<!-- junit单元测试 -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13.2</version>
</dependency>
1.4 创建组件类
1.4.1 使用 @Component 注解标记的普通组件
- 示例:
package com.github.fairy.era.component;
import org.springframework.stereotype.Component;
/**
* @author 许大仙
* @version 1.0
* @since 2021-11-05 14:26
*/
@Component // 表明这是一个普通的组件
public class HelloComponent {
}
1.4.2 使用 @Controller 注解标记的控制器组件
这个组件就是三层架构(表现层、业务逻辑层、数据访问层)中的表现层,即控制器,相当于 JavaWeb 阶段的Servlet。
示例:
package com.github.fairy.era.controller;
import org.springframework.stereotype.Controller;
/**
* @author 许大仙
* @version 1.0
* @since 2021-11-05 14:28
*/
@Controller // 表明这是一个控制器组件
public class HelloController {
}
1.4.3 使用 @Service 注解标记的业务逻辑组件
这个组件就是三层架构(表现层、业务逻辑层、数据访问层)中的业务逻辑组件。
示例:
package com.github.fairy.era.service;
import org.springframework.stereotype.Service;
/**
* @author 许大仙
* @version 1.0
* @since 2021-11-05 14:31
*/
@Service // 表明这是一个业务逻辑组件
public class HelloService {
}
1.4.4 使用 @Repository 注解标记的持久化层组件
- 这个组件就是我们以前用的 Dao 类,但是以后我们整合了 Mybatis ,这里就变成了 Mapper 接口,而 Mapper 接口是由 Mybatis 和 Spring 的整合包负责扫描的。
- 由于 Mybatis 整合包想要把 Mapper 接口背后的代理类加入 Spring 的 IOC 容器需要结合 Mybatis 对 Mapper 配置文件的解析,所以这个事情是 Mybatis 和 Spring 的整合包来完成,将来由 Mybatis 负责扫描,也不使用
@Repository
注解。 当然,如果你使用 Spring Data 技术,可能需要使用该注解。
示例:
package com.github.fairy.era.dao;
import org.springframework.stereotype.Repository;
/**
* @author 许大仙
* @version 1.0
* @since 2021-11-05 14:35
*/
@Repository // 表明这是一个持久化层组件
public class HelloDao {
}
1.5 四个注解的本质
- @Repository注解的源码:
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface Repository {
@AliasFor(
annotation = Component.class
)
String value() default "";
}
- 通过源码我们可以知道,@Controller 、@Service 、@Repository 这三个注解只是在 @Component 注解的基础上起了三个新的名字。
- 对于 Spring 使用 IOC 容器管理这些组件来说,没有任何区别,但是 @Controller 、@Service 、@Repository 注解是给开发人员使用的,让我们能够很方便的分辨组件的作用。
注意:虽然它们本质上一样,但是为了代码的可读性,为了程序结构严谨我们肯定不能随便胡乱标记。
1.6 扫描
1.6.1 最基本的扫描方式
- applicationContext.xml
<?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:p="http://www.springframework.org/schema/p"
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.github.fairy.era"></context:component-scan>
</beans>
- 测试:
package com.github.fairy.era.bean;
import com.github.fairy.era.component.HelloComponent;
import com.github.fairy.era.controller.HelloController;
import com.github.fairy.era.dao.HelloDao;
import com.github.fairy.era.service.HelloService;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
/**
* @author 许大仙
* @version 1.0
* @since 2021-11-05 11:02
*/
public class SpringTest {
@Test
public void test(){
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
HelloController helloController = context.getBean(HelloController.class);
System.out.println("helloController = " + helloController);
HelloService helloService = context.getBean(HelloService.class);
System.out.println("helloService = " + helloService);
HelloDao helloDao = context.getBean(HelloDao.class);
System.out.println("helloDao = " + helloDao);
HelloComponent helloComponent = context.getBean(HelloComponent.class);
System.out.println("helloComponent = " + helloComponent);
}
}
1.6.2 指定要排除的组件
- applicationContext.xml
<?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:p="http://www.springframework.org/schema/p"
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.github.fairy.era">
<!--
指定不扫描的组件
context:exclude-filter标签:指定排除规则
type属性:指定根据什么来进行排除,annotation取值表示根据注解来排除
expression属性:指定排除规则的表达式,对于注解来说指定全类名即可
-->
<context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
</context:component-scan>
</beans>
- 测试:
package com.github.fairy.era.bean;
import com.github.fairy.era.component.HelloComponent;
import com.github.fairy.era.controller.HelloController;
import com.github.fairy.era.dao.HelloDao;
import com.github.fairy.era.service.HelloService;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
/**
* @author 许大仙
* @version 1.0
* @since 2021-11-05 11:02
*/
public class SpringTest {
@Test
public void test(){
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
HelloController helloController = context.getBean(HelloController.class);
System.out.println("helloController = " + helloController);
HelloService helloService = context.getBean(HelloService.class);
System.out.println("helloService = " + helloService);
HelloDao helloDao = context.getBean(HelloDao.class);
System.out.println("helloDao = " + helloDao);
HelloComponent helloComponent = context.getBean(HelloComponent.class);
System.out.println("helloComponent = " + helloComponent);
}
}
- 结果:
org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type 'com.github.fairy.era.controller.HelloController' available
at org.springframework.beans.factory.support.DefaultListableBeanFactory.getBean(DefaultListableBeanFactory.java:351)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.getBean(DefaultListableBeanFactory.java:342)
at org.springframework.context.support.AbstractApplicationContext.getBean(AbstractApplicationContext.java:1172)
at com.github.fairy.era.bean.SpringTest.test(SpringTest.java:22)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.base/java.lang.reflect.Method.invoke(Method.java:566)
at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:59)
at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:56)
at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
at org.junit.runners.ParentRunner$3.evaluate(ParentRunner.java:306)
at org.junit.runners.BlockJUnit4ClassRunner$1.evaluate(BlockJUnit4ClassRunner.java:100)
at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:366)
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:103)
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:63)
at org.junit.runners.ParentRunner$4.run(ParentRunner.java:331)
at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:79)
at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:329)
at org.junit.runners.ParentRunner.access$100(ParentRunner.java:66)
at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:293)
at org.junit.runners.ParentRunner$3.evaluate(ParentRunner.java:306)
at org.junit.runners.ParentRunner.run(ParentRunner.java:413)
at org.junit.runner.JUnitCore.run(JUnitCore.java:137)
at com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:69)
at com.intellij.rt.junit.IdeaTestRunner$Repeater.startRunnerWithArgs(IdeaTestRunner.java:33)
at com.intellij.rt.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:235)
at com.intellij.rt.junit.JUnitStarter.main(JUnitStarter.java:54)
1.6.3 仅扫描指定的组件
- applicationContext.xml
<?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:p="http://www.springframework.org/schema/p"
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.github.fairy.era" use-default-filters="false">
<!-- context:include-filter标签:指定在原有扫描规则的基础上追加的规则 -->
<context:include-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
</context:component-scan>
</beans>
- 测试:
package com.github.fairy.era.bean;
import com.github.fairy.era.controller.HelloController;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
/**
* @author 许大仙
* @version 1.0
* @since 2021-11-05 11:02
*/
public class SpringTest {
@Test
public void test(){
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
HelloController helloController = context.getBean(HelloController.class);
System.out.println("helloController = " + helloController);
}
}
1.7 组件的名称
- 在使用 XML 管理 bean的时候,每个 bean 都有唯一标识(bean的id属性的值),便于在其他地方进行引用,现在使用注解以后,每个组件依然有一个唯一的标识。
- 默认情况:类名首字母小写就是 bean 的id,比如:Student 类对应的 bean 的 id 就是 student 。
- 当然,也可以通过注解的 value 属性改变默认的 bean 的id。
package com.github.fairy.era.service;
import org.springframework.stereotype.Service;
/**
* @author 许大仙
* @version 1.0
* @since 2021-11-05 14:31
*/
@Service("hello") // 使用value属性改变默认的bean的id
public class HelloService {
}
第二章:自动装配(⭐)
2.1 需求和目标
- Controller 调用 Service 中的方法,Service 调用 Dao 中的方法。
- HelloDao.java
package com.github.fairy.era.dao;
import org.springframework.stereotype.Repository;
/**
* @author 许大仙
* @version 1.0
* @since 2021-11-05 14:35
*/
@Repository
public class HelloDao {
public String getMessage() {
return "你好,Spring";
}
}
- HelloService.java
package com.github.fairy.era.service;
import com.github.fairy.era.dao.HelloDao;
import org.springframework.stereotype.Service;
/**
* @author 许大仙
* @version 1.0
* @since 2021-11-05 14:31
*/
@Service
public class HelloService {
private HelloDao helloDao;
public String getMessage(){
return helloDao.getMessage();
}
}
- HelloController.java
package com.github.fairy.era.controller;
import com.github.fairy.era.service.HelloService;
import org.springframework.stereotype.Controller;
/**
* @author 许大仙
* @version 1.0
* @since 2021-11-05 14:28
*/
@Controller
public class HelloController {
private HelloService helloService;
public String getMessage() {
return helloService.getMessage();
}
}
2.2 前提
- 参与自动装配的组件都必须在 IOC 容器中。
2.3 @Autowired 注解
在成员变量上直接标记
@Autowired
注解即可,不需要提供 setXxx() 方法,以后我们在项目中的正式用法就是这样。示例:
package com.github.fairy.era.service;
import com.github.fairy.era.dao.HelloDao;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
/**
* @author 许大仙
* @version 1.0
* @since 2021-11-05 14:31
*/
@Service
public class HelloService {
// 给Service装配Dao
@Autowired
private HelloDao helloDao;
public String getMessage(){
return helloDao.getMessage();
}
}
package com.github.fairy.era.controller;
import com.github.fairy.era.service.HelloService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
/**
* @author 许大仙
* @version 1.0
* @since 2021-11-05 14:28
*/
@Controller
public class HelloController {
// 给Controller装配Service
@Autowired
private HelloService helloService;
public String getMessage() {
return helloService.getMessage();
}
}
2.4 @Autowired 注解的细节
- @Autowired 注解源码:
@Target({ElementType.CONSTRUCTOR, ElementType.METHOD, ElementType.PARAMETER, ElementType.FIELD, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Autowired {
boolean required() default true;
}
@Autowired 可以标记在构造器、方法、参数、属性、以及作为元注解使用。
示例:
package com.github.fairy.era.controller;
import com.github.fairy.era.service.HelloService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
/**
* @author 许大仙
* @version 1.0
* @since 2021-11-05 14:28
*/
@Controller
public class HelloController {
private final HelloService helloService;
@Autowired
public HelloController(HelloService helloService) {
this.helloService = helloService;
}
public String getMessage() {
return helloService.getMessage();
}
}
2.5 @Autowired 注解的工作流程
首先根据所需要的组件类型到 IOC 容器中查找
- 能够找到唯一的 bean:直接执行装配
- 如果完全找不到匹配这个类型的 bean:装配失败
- 和所需类型匹配的 bean 不止一个
- 没有
@Qualifier
注解:根据@Autowired
标记位置成员变量的变量名作为 bean 的 id 进行匹配- 能够找到:执行装配
- 找不到:装配失败
- 使用
@Qualifier
注解:根据@Qualifier
注解中指定的名称作为 bean 的id进行匹配- 能够找到:执行装配
- 找不到:装配失败
- 没有
示例:
package com.github.fairy.era.dao;
import org.springframework.stereotype.Repository;
/**
* @author 许大仙
* @version 1.0
* @since 2021-11-05 14:35
*/
@Repository
public class HelloDao {
public String getMessage() {
return "你好,Spring";
}
}
package com.github.fairy.era.service;
import com.github.fairy.era.dao.HelloDao;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
/**
* @author 许大仙
* @version 1.0
* @since 2021-11-05 14:31
*/
@Service("helloService2")
public class HelloService {
@Autowired
private HelloDao helloDao;
public String getMessage(){
return helloDao.getMessage();
}
}
package com.github.fairy.era.controller;
import com.github.fairy.era.service.HelloService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Controller;
/**
* @author 许大仙
* @version 1.0
* @since 2021-11-05 14:28
*/
@Controller
public class HelloController {
@Autowired
@Qualifier("helloService2")
private HelloService helloService;
public String getMessage() {
return helloService.getMessage();
}
}
2.6 佛系装配
- 给 @Autowired 注解设置 required = false 属性表示:能装就装,装不上就不装。但是实际开发时,基本上所有需要装配组件的地方都是必须装配的,用不上这个属性。
注意:如果类中同时存在装配属性的 setXxx() 方法会使 required = false 设定失效。
- 示例:
package com.github.fairy.era.controller;
import com.github.fairy.era.service.HelloService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
/**
* @author 许大仙
* @version 1.0
* @since 2021-11-05 14:28
*/
@Controller
public class HelloController {
// 给@Autowired注解设置required = false属性表示:能装就装,装不上就不装
@Autowired(required = false)
private HelloService helloService;
public String getMessage() {
return helloService.getMessage();
}
}
第三章:完全注解开发
3.1 创建配置类
- SpringApplication.java
package com.github.fairy.era;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
/**
* @author 许大仙
* @version 1.0
* @since 2021-11-05 15:20
*/
@ComponentScan(basePackages = "com.github.fairy.era") // 相当于<context:component-scan></context:component-scan>
@Configuration // 相当于配置文件
public class SpringApplication {
}
3.2 组件类
- Person.java
package com.github.fairy.era.bean;
/**
* @author 许大仙
* @version 1.0
* @since 2021-11-05 15:23
*/
public class Person {
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
'}';
}
}
- HelloDao.java
package com.github.fairy.era.dao;
import org.springframework.stereotype.Repository;
/**
* @author 许大仙
* @version 1.0
* @since 2021-11-05 14:35
*/
@Repository
public class HelloDao {
public String getMessage() {
return "你好,Spring";
}
}
- HelloService.java
package com.github.fairy.era.service;
import com.github.fairy.era.dao.HelloDao;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
/**
* @author 许大仙
* @version 1.0
* @since 2021-11-05 14:31
*/
@Service
public class HelloService {
@Autowired
private HelloDao helloDao;
public String getMessage(){
return helloDao.getMessage();
}
}
- HelloController.java
package com.github.fairy.era.controller;
import com.github.fairy.era.service.HelloService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
/**
* @author 许大仙
* @version 1.0
* @since 2021-11-05 14:28
*/
@Controller
public class HelloController {
// 给@Autowired注解设置required = false属性表示:能装就装,装不上就不装
@Autowired(required = false)
private HelloService helloService;
public String getMessage() {
return helloService.getMessage();
}
}
3.3 在配置类中配置 Bean
- SpringApplication.java
package com.github.fairy.era;
import com.github.fairy.era.bean.Person;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
/**
* @author 许大仙
* @version 1.0
* @since 2021-11-05 15:20
*/
@ComponentScan(basePackages = "com.github.fairy.era") // 相当于<context:component-scan></context:component-scan>
@Configuration // 相当于配置文件
public class SpringApplication {
@Bean // 相当于bean标签
public Person person(){
Person person = new Person();
person.setName("许大仙");
return person;
}
}
3.4 测试
package com.github.fairy.era.bean;
import com.github.fairy.era.SpringApplication;
import com.github.fairy.era.controller.HelloController;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
/**
* @author 许大仙
* @version 1.0
* @since 2021-11-05 11:02
*/
public class SpringTest {
@Test
public void test(){
ApplicationContext context = new AnnotationConfigApplicationContext(SpringApplication.class);
HelloController helloController = context.getBean(HelloController.class);
String message = helloController.getMessage();
System.out.println("message = " + message);
Person person = context.getBean(Person.class);
System.out.println("person = " + person);
}
}
第四章:整合 Junit4
4.1 概述
- Spring 整合 Junit 有如下的好处:
- ① 不需要自己创建 IOC 容器对象了。
- ② 任何需要的 bean 都可以在测试类中直接享受自动装配。
4.2 导入依赖
- pom.xml
<!-- Spring -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.3.12</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>5.3.12</version>
</dependency>
<!-- junit单元测试 -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13.2</version>
<scope>test</scope>
</dependency>
4.3 组件类
- Person.java
package com.github.fairy.era.bean;
/**
* @author 许大仙
* @version 1.0
* @since 2021-11-05 15:23
*/
public class Person {
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
'}';
}
}
4.4 配置类
- SpringApplication.java
package com.github.fairy.era;
import com.github.fairy.era.bean.Person;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
/**
* @author 许大仙
* @version 1.0
* @since 2021-11-05 15:20
*/
@ComponentScan(basePackages = "com.github.fairy.era") // 相当于<context:component-scan></context:component-scan>
@Configuration // 相当于配置文件
public class SpringApplication {
@Bean // 相当于bean标签
public Person person(){
Person person = new Person();
person.setName("许大仙");
return person;
}
}
4.5 测试
package com.github.fairy.era.bean;
import com.github.fairy.era.SpringApplication;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringRunner;
/**
* @author 许大仙
* @version 1.0
* @since 2021-11-05 11:02
*/
@RunWith(SpringRunner.class)
@ContextConfiguration(classes = SpringApplication.class)
public class SpringTest {
@Autowired
private Person person;
@Test
public void test(){
System.out.println("person = " + person);
}
}