hi,大家好,今天为大家继续带来Spring相关知识
上一期我们已经学习了Spring对象的基本的存储和读取了,但是那个过程比较复杂,我们来学习Bean更加简单的存储和读取
我们上一期学习的时候存储bean需要配置xml.现在我们需要一个注解就可以完成,但是要先配置路径
在xml文件中配置扫描包路径,其实说的通俗一点就是Bean对象在哪个包,就写哪个路径
蓝色的这一块,作用是配置bean的扫描根路径,只有这个当前目录下的类才会扫描是否添加了注解,如果添加了注解,就将这个添加了注解的类放到IOC容器
红色的就是我创建的包,这个包名称要和spring-config里面的base-package里面引用的内容一样
存储bean对象有两种方法
1.通过类注解(五大类)
2.通过方法注解
@Controller,控制器存储,作用是校验参数的合法性
package com.java.demo;
import org.springframework.stereotype.Controller;
/**
* Created with IntelliJ IDEA.
* Description:
* User: WHY
* Date: 2023-07-21
* Time: 15:09
*/
@Controller(value = "userinfo")
public class User {
public void sayHi(){
System.out.println("hi User");
}
}
@Controller这里可以起一个名字,加上这个注解,就代表这个类已经被注入到了IOC容器中,我们来获取bean
import com.java.demo.User;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
/**
* Created with IntelliJ IDEA.
* Description:
* User: WHY
* Date: 2023-07-21
* Time: 15:08
*/
public class APP {
public static void main(String[] args) {
//得到容器对象
ApplicationContext context=new
ClassPathXmlApplicationContext("spring-config.xml");
//得到bean对象
User user=context.getBean("userinfo",User.class);
//使用bean
user.sayHi();
}
}
也可以不设置,那么写的时候就是默认写成类名首字母小写
我们再次在com.java.demo包下创建一个Student类,不加注解,运行会报错,那么我们可以采用加注解,也可以采用xml的形式,这俩可以混用
import com.java.demo.Student;
import com.java.demo.User;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
/**
* Created with IntelliJ IDEA.
* Description:
* User: WHY
* Date: 2023-07-21
* Time: 15:08
*/
public class APP {
public static void main(String[] args) {
//得到容器对象
ApplicationContext context1=new
ClassPathXmlApplicationContext("spring-config.xml");
//得到bean对象
User user=context1.getBean("user",User.class);
//使用bean
user.sayHi();
ApplicationContext context=new
ClassPathXmlApplicationContext("spring-config.xml");
//得到bean对象
Student student=context.getBean("student",Student.class);
//使用bean
student.sayHi();
}
}
这下两个类都可以执行了
接下来我们不改变扫描路径,我们来验证一下该包的子包是否能执行,我们把这个例子放到@Service下面
@Service属于服务层,主要功能是业务组装,负责调用其他接口
public class APP {
public static void main(String[] args) {
//得到容器对象
ApplicationContext context1=new
ClassPathXmlApplicationContext("spring-config.xml");
UserService userService=context1.getBean("userService",UserService.class);
userService.sayHi();
}
}
为什么一定要设置扫描根路径呢?
是为了性能考虑,也可以不设置,让spring扫描Java包下所有的类,但是那样太低效了,规定一个范围,spring就会根据这个范围扫描它的类以及子包,提高了效率
@Resopority是仓库层,也称数据持久层,功能是实际业务处理
@Repository
public class UserService {
public void sayHi(){
System.out.println("hi, UserService");
}
}
@Configuration
public class UserService {
public void sayHi(){
System.out.println("hi, UserService");
}
}
@Component
public class UserService {
public void sayHi(){
System.out.println("hi, UserService");
}
}
通过上述代码我们可以发现,这几个注解功能是⼀样的,为什么需要这么多的类注解呢,就像看到身份证号前几位就可以区分这个人是哪里人是一样的,在程序中,当程序员看到这些注解,就清楚的知道这个类的功能
通过上述代码我们可以发现,这几个注解功能是⼀样的,为什么需要这么多的类注解呢,就像看到身份证号前几位就可以区分这个人是哪里人是一样的,在程序中,当程序员看到这些注解,就清楚的知道这个类的功能
我们采用一个通俗的例子来进行讲解
比如小魏去坐高铁,那么首先要过安检,那么如果安检都过不去,那么就更不用提后面的事情了,此时的安检系统就相当于@Controller,过了安检以后,如果有问题可以去咨询台问,这个咨询台的工作人员是为大家服务的,那么这个相当于@Service,然后当听到工作人员说去某个窗口买票,然后小魏去到该窗口办理,这个窗口相当于@Repository,而这个等候厅的椅子啊这些都是组件,工具,这就相当于@Component.
配置层就是给该项目做一些配置的,比如设置端口号,session存储时间啥的,也就是@Configuration
项目里面的这几个关系是这样的
Java项目分层
看到@Controller是基于@Component实现的
@Service也是基于@Component实现的
@Repository基于@Componnent实现的
@Configuration基于@Component实现
这四个注解都基于@Component注解实现
如果这个类的名称第一个字母大写,第二个字母小写,那么就用类名的小写形式,就像这样
@Configuration
public class LApple {
public void sayHi(){
System.out.println("hi ,LApple");
}
}
public class APP {
public static void main(String[] args) {
//得到容器对象
ApplicationContext context1=new
ClassPathXmlApplicationContext("spring-config.xml");
LApple lApple=context1.getBean("lApple",LApple.class);
lApple.sayHi();
}
}
当遇到第一个字母是大写,第二个字母也是大写的时候我们怎么办呢?
返回它本来的类名即可
那么为什么会出现这样的问题呢?
咱们这个时候就需要看看Spring的源码了
public String generateBeanName(BeanDefinition definition, BeanDefinitionRegistry registry) {
if (definition instanceof AnnotatedBeanDefinition) {
String beanName = this.determineBeanNameFromAnnotation((AnnotatedBeanDefinition)definition);
if (StringUtils.hasText(beanName)) {
return beanName;
}
}
看到这里,会发现当名字为空或者长度为0时,返回本身,长度大于1或者第一个字母和第二个字母时大写字母时.返回本身,当第一个为大写,第二个不为大写,将第一个字母变为小写返回
创建一个Article
package com.java.demo.model;
public class Article {
private int id;
private String title;
private String content;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public String getContent() {
return content;
}
public void setContent(String content) {
this.content = content;
}
@Override
public String toString() {
return "Article{" +
"id=" + id +
", title='" + title + '\'' +
", content='" + content + '\'' +
'}';
}
}
再创建一个Articles类
package com.java.demo;
import com.java.demo.model.Article;
import org.springframework.context.annotation.Bean;
/**
* Created with IntelliJ IDEA.
* Description:
* User: WHY
* Date: 2023-07-21
* Time: 19:26
*/
public class Articles {
//要把当前返回的对象存储到IOC容器中
@Bean
public Article article(){
//伪代码
Article article=new Article();
article.setId(1);
article.setTitle("钢铁是怎样炼成的");
article.setContent("一个人的一生不应该这样度过,不应该因碌碌无为而......");
return article;
}
}
啊哦,竟然报错了,因为@Bean注解必须结合五大注解一起使用
那么为什么一定要加这个注解呢,如果不加,那么spring的扫描工作量就会很大,要扫描com.java.demo下所有的方法看哪一个加了bean,效率很低,为了提高性能,就要加上
那么还有一个拓展性的问题,这个Articles会被注入吗?
我们来验证一下
在Articles创建一个sayHi方法
package com.java.demo;
import com.java.demo.model.Article;
import org.springframework.context.annotation.Bean;
import org.springframework.stereotype.Component;
/**
* Created with IntelliJ IDEA.
* Description:
* User: WHY
* Date: 2023-07-21
* Time: 19:26
*/
@Component
public class Articles {
//要把当前返回的对象存储到IOC容器中
@Bean
public Article article(){
//伪代码
Article article=new Article();
article.setId(1);
article.setTitle("钢铁是怎样炼成的");
article.setContent("一个人的一生不应该这样度过,不应该因碌碌无为而......");
return article;
}
public void sayHi(){
System.out.println("hi Articles");
}
}
import com.java.demo.Articles;
import com.java.demo.model.Article;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class APP {
public static void main(String[] args) {
/* //得到容器对象
ApplicationContext context1=new
ClassPathXmlApplicationContext("spring-config.xml");
//得到bean对象*/
//User user=context1.getBean("user",User.class);
//使用bean
//user.sayHi();
ApplicationContext context=new
ClassPathXmlApplicationContext("spring-config.xml");
//得到bean对象
/*Student student=context.getBean("student",Student.class);*/
//使用bean
//
Article article=context.getBean("article",Article.class);
System.out.println(article.toString());
Articles articles=context.getBean("articles",Articles.class);
articles.sayHi();
}
}
由结果看出Articles也是被注入的.即本身的类也会存到Spring中
1.默认情况下,@Bean的默认名=方法名
比如
这里的方法名就是类名的小写形式,那么就使用方法名就行
2如果是方法名写成别的名字
那么就会报错,这个时候就要给@Bean命名了
以上是Bean的重命名三种方式
Bean支持多个名称命名
有一个问题,当这个bean设置了名称,还能用方法名获取吗?
有可能:Spring存储Bean的逻辑结构是多个名称对应同一个Bean对象
当有多个方法,bean的名称一样时,会发生什么呢?
@Order(20)
@Component
public class Articles {
//要把当前返回的对象存储到IOC容器中
@Bean(value="aaa")
public Article article(){
//伪代码
Article article=new Article();
article.setId(2);
article.setTitle("钢铁是怎样炼成的");
article.setContent("一个人的一生不应该这样度过,不应该因碌碌无为而......");
return article;
}
@Bean("aaa")
public Article article2(){
//伪代码
Article article=new Article();
article.setId(1);
article.setTitle("钢铁是怎样炼成的");
article.setContent("一个人的一生不应该这样度过,不应该因碌碌无为而......");
return article;
}
public void sayHi(){
System.out.println("hi Articles");
}
}
@Order(1)
@Controller()
public class User {
public void sayHi(){
System.out.println("hi User");
}
@Bean(value="aaa")
public Article article3(){
//伪代码
Article article=new Article();
article.setId(4);
article.setTitle("钢铁是怎样炼成的");
article.setContent("一个人的一生不应该这样度过,不应该因碌碌无为而......");
return article;
}
}
@Order代表的是执行的优先级,数字越大,优先级越高,越先执行
如果不加这个@Order,那么就按照代码执行顺序执行
如果多个Bean使用相同的名称,那么程序执行不会报错,但是第一个Bean对象之后的对象不会存放到容器中,后面再有相同名称的Bean存储的时候,容器会自动忽略,不会进行存储
获取Bean对象也叫做对象装配,就是把容器里的对象取出来放到某个类里面,有时候也叫对象注入
属性注入是使用@Autowired实现的
为了讲解这个例子,我们先创建一个标准的工程
在UserRepository中写一个方法,在UserService调用
package com.java.demo.dao;
import org.springframework.stereotype.Repository;
/**
* Created with IntelliJ IDEA.
* Description:
* User: WHY
* Date: 2023-07-22
* Time: 8:43
*/
@Repository
public class UserRepository {
public int add(){
System.out.println("执行UserRepository 方法");
return 1;
}
}
package com.java.demo.service;
import com.java.demo.dao.UserRepository;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.stereotype.Service;
/**
* Created with IntelliJ IDEA.
* Description:
* User: WHY
* Date: 2023-07-22
* Time: 8:42
*/
@Service
public class UserService {
public int add(){
//传统写法
/*UserRepository userRepository=new UserRepository();
return userRepository.add();*/
//Spring写法
//依赖查找
/* ApplicationContext context=new
ClassPathXmlApplicationContext("spring-config.xml");
UserRepository userRepository=context.getBean("userRepository",UserRepository.class);
return userRepository.add();*/
return 0;
}
}
这两种写法都是很麻烦的,我们采取属性注入来解决
@Service
public class UserService {
//属性注入
@Autowired
private UserRepository userRepository;//这里是依赖注入,从Spring当种找到UserRepository赋值给新的变量
public int add(){
return userRepository.add();
}
}
我们现在验证一下UserService是否能拿到这个类.,我们采用单元测试的方法
快捷键alt+insert点击Test,点击生成方法,并且引入Junit依赖
<!-- https://mvnrepository.com/artifact/org.junit.jupiter/junit-jupiter-api -->
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-api</artifactId>
<version>5.9.3</version>
<scope>test</scope>
</dependency>
创建单元测试代码
package com.java.demo.service;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import static org.junit.jupiter.api.Assertions.*;
class UserServiceTest {
@org.junit.jupiter.api.Test
void add() {
ApplicationContext context=new
ClassPathXmlApplicationContext("spring-config.xml");
UserService userService=context.getBean("userService",UserService.class);
userService.add();
}
}
这里的名字随便起,都可以执行
和之前的依赖查找不同,依赖查找依赖Bean名称进行查找
@Authowired
依赖注入流程:首先看类型,根据类型从容器中获取对象,如果该类型只有一个,而且只有一个,那么直接获取注入到当前对象,如果该类型有多个对象,那就根据名称进行匹配
我们来使用代码来看一下
package com.java.demo.model;
/**
* Created with IntelliJ IDEA.
* Description:
* User: WHY
* Date: 2023-07-22
* Time: 9:24
*/
public class Student {
public String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
'}';
}
}
package com.java.demo.model;
import org.springframework.context.annotation.Bean;
import org.springframework.stereotype.Component;
/**
* Created with IntelliJ IDEA.
* Description:
* User: WHY
* Date: 2023-07-22
* Time: 9:25
*/
@Component
public class Students {
@Bean("student1")
public Student student1(){
Student student1=new Student();
student1.setName("zhangsan");
return student1;
}
@Bean("student2")
public Student student2(){
Student student2=new Student();
student2.setName("wangwu");
return student2;
}
}
package com.java.demo.service;
import com.java.demo.model.Student;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
/**
* Created with IntelliJ IDEA.
* Description:
* User: WHY
* Date: 2023-07-22
* Time: 9:31
*/
@Service
public class UserService2 {
//属性注入获取
@Autowired
private Student student;
public void sayHi(){
System.out.println(student.toString());
}
}
再做一次单元测试
package com.java.demo.service;
import org.junit.jupiter.api.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import static org.junit.jupiter.api.Assertions.*;
/**
* Created with IntelliJ IDEA.
* Description:
* User: WHY
* Date: 2023-07-22
* Time: 9:34
*/
class UserService2Test {
@Test
void sayHi() {
ApplicationContext context=new
ClassPathXmlApplicationContext("spring-config.xml");
UserService2 userService2=context.getBean("userService",UserService2.class);
userService2.sayHi();
}
}
因为在Service调用的时候,命名的时候采用的是student,而我们在创建的时候命名的是student1,student2,没有student,所以报错,同类型的Bean存储到容器多个,获取时报错,怎样解决?
1.将属性的名字和Bean的名字对应上
2.采用@Qualifier注解来解决,这个注解是筛选的意思,我就可以不改属性名.
3.使用@Resource注解
@Resource里面可以设置bean的名称
@Service
public class UserService3 {
private UserRepository userRepository;
@Autowired
public void setUserRepository(UserRepository userRepository) {
this.userRepository = userRepository;
}
public void sayHi(){
System.out.println("执行了UserService3方法");
userRepository.add();
}
}
首先了解这个方式是Spring官方推荐的方式,是在Spring(4.x)之后被推荐的,这个版本之前推荐的是setter注入
这里有一个小的知识点,为什么这三种注入都需要创建这个类变量,因为创建类变量.这个类的所有方法都可以调用这个变量
标准写法
@Service
public class UserService4 {
private UserRepository userRepository;
@Autowired
public UserService4(UserRepository userRepository) {
this.userRepository = userRepository;
}
public void sayHi(){
System.out.println("执行UserService方法");
userRepository.add();
}
}
生成单元测试代码看结果
不加@Autowired,也可以执行,但是只可以在当前类只有一个构造方法的时候才能用
多个构造方法时不可以省略,因为要判断注入哪一个构造方法
1.属性注入
优点:使用简单
缺点:无法注入final修饰的变量
要想不报错,要么赋值,要么就在构造方法赋值
2.通用性问题:只能适用于IOC容器
3.设计问题:更容易违背单一设计原则
2.Setter注入
优点:通常Setter只set一个属性,所以Setter注入更符合单一设计原则
缺点:
1.无法注入一个final修饰的变量
2.setter注入的对象容易被多次调用和被修改.setter本来就是一个方法,因为是一个方法,就有可能被多次调用和修改
3.构造方法注入
优点
1.可以注入final修饰的变量了
2.注入的对象不会被修改,因为构造方法只加载一次,因为它随着JVM的启动而加载
3.保证注入的对象是被完全初始化的
4.通用性更好
缺点:
写法复杂
无法解决循环依赖的问题
进行类注入时,除了使用@Autowired以外,还能使用@Resource
1.出身不同:@Resource来自于JDK,@Autowired来自Spring框架
2.参数支持不同:@Resource支持很多参数设置,@Autowired只有一个参数设置,可以看源码
3.使用上:@Resource不支持构造方法注入,@Autowired支持
4.idea兼容性不同:@Autowired在idea专业版本下可能会报错,@Resource不会
在 Spring 项⽬中,通过 main ⽅法获取到 Controller 类,调⽤ Controller ⾥⾯通过注⼊的⽅式调⽤Service 类,Service 再通过注⼊的⽅式获取到 Repository 类,Repository 类⾥⾯有⼀个⽅法构建⼀个 User 对象,返回给 main ⽅法。Repository ⽆需连接数据库,使⽤伪代码即可
package com.java.demo.model;
/**
* Created with IntelliJ IDEA.
* Description:
* User: WHY
* Date: 2023-07-22
* Time: 13:22
*/
public class User {
private int id;
private String name;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "User{" +
"id=" + id +
", name='" + name + '\'' +
'}';
}
}
package com.java.demo.repository;
import com.java.demo.model.User;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.stereotype.Repository;
/**
* Created with IntelliJ IDEA.
* Description:
* User: WHY
* Date: 2023-07-22
* Time: 13:21
*/
@Repository
public class UserRepository {
@Bean
public User getUser(){
User user=new User();
user.setId(1);
user.setName("zhangsan");
return user;
}
}
package com.java.demo.service;
import com.java.demo.model.User;
import com.java.demo.repository.UserRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
/**
* Created with IntelliJ IDEA.
* Description:
* User: WHY
* Date: 2023-07-22
* Time: 13:28
*/
@Service
public class UserService {
@Autowired
private UserRepository userRepository;
public User getUser(){
return userRepository.getUser();
}
}
package com.java.demo.controller;
import com.java.demo.model.User;
import com.java.demo.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
/**
* Created with IntelliJ IDEA.
* Description:
* User: WHY
* Date: 2023-07-22
* Time: 13:36
*/
@Controller
public class UserController {
@Autowired
private UserService userService;
public User getUser(){
return userService.getUser();
}
}
package com.java.demo;
import com.java.demo.controller.UserController;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
/**
* Created with IntelliJ IDEA.
* Description:
* User: WHY
* Date: 2023-07-22
* Time: 13:17
*/
public class APP {
public static void main(String[] args) {
//这个静态方法是在Spring执行之前,所以拿不到注入对象,此时我们采取普通形式
ApplicationContext context=new
ClassPathXmlApplicationContext("spring-config.xml");
UserController userController=context.getBean("userController",UserController.class);
System.out.println(userController.getUser());
}
}