在上一篇blog里边我介绍了spring项目的创建以及Bean对象的存储和读取。
存储:1.首先创建Bean对象 2.将Bean对象注册到Spring容器中【Bean标签】
读取:1.获取Spring上下文对象 2.获取指定的Bean对象 3.使用Bean对象
但是随着Bean对象的增多以及使用频率的提高,上述过程难免有点效率低,所以需要学习下边更为简单的存取Bean对象的方式——使用注解,这也是Spring中更为简单存取Bean对象的核心。
一、存储Bean对象
0.准备工作:配置扫描路径(关键)
只有正确配置存储对象的扫描包路径,对应包下的类才能被正确识别并保存。
配置方法:
在spring-config.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:content="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.spring
framework.org/schema/context https://www.springframework.org/schema/contex
t/spring-context.xsd">
<content:component-scan base-package="com.bit.service"></content:component-scan>
</beans>
1.添加注解存储Bean对象
将对象注册到Spring中,有两种注解类型可以实现。分别是
1)五大类注解:@Controller、@Service、@Respository、@Component、@Configuration
2)一个方法注解:@Bean
1.1类注解
1.1.1@Controller
@Controller其实也叫做控制器注解,它的作用是验证用户请求数据的正确性,类似于安保系统。当认定当前访问者为非法用户黑客,就把他们加入黑名单。
下边是利用此注解存储Bean的代码示例:
@Controller // 将对象存储到 Spring 中
public class UserController {
public void sayHi(String name) {
System.out.println("do UserController sayHi()");
}
}
将这个注解加到类上边,当需要用到这个类的对象时,spring就会自动获取。
这里为了演示效果,我们先使用之前获取Bean的方法读取上述对象:
public class Application {
public static void main(String[] args) {
ApplicationContext context =
new ClassPathXmlApplicationContext("spring-config.xml");
UserController userController = (UserController) context.getBean("userController",UserController.class);
userController.sayHi();
}
}
这样我们就拿到了这个对象
1.1.2@Service
@Service注解其实也叫做服务注解。它的作用就是编排和调度具体执行方法,在服务层,相当于导医台/服务中心,完成引导工作,但是不执行业务。
下边是使用@Service注解存储对象的代码示例:
@Service
public class UserService {
public void sayHi(String name) {
System.out.println("do UserService sayHi()");
}
}
读取Bean对象(老方法):
public class App {
public static void main(String[] args) {
ApplicationContext context=new ClassPathXmlApplicationContext("spring-config.xml");
// UserController userController=context.getBean("userController",UserController.class);
// userController.sayHi();
UserService userService=context.getBean("userService",UserService.class);
userService.sayHi();
}
}
1.1.3剩余的类注解
剩余的三个注解使用方法与之类似,这里一并演示
@Repository也叫做仓库存储,是在数据持久层即和数据库进行交互的一层,其中数据库在程序中就叫做仓库。
@Component其实也叫做组件存储,是操作数据的工具类。
@Configuration其实也叫做配置存储,是用来存项目中的一些配置的。
存对应Bean方法
@Configuration
public class UserConfiguration {
public void sayHi(){
System.out.println("do UserConfiguration sayHi()");
}
}
@Repository
public class UserRepository {
public void sayHi(){
System.out.println("do UserRepository sayHi()");
}
}
@Component
public class UserComponent {
public void sayHi(){
System.out.println("do UserComponent sayHi()");
}
}
取对象方法:
public class App {
public static void main(String[] args) {
ApplicationContext context=new ClassPathXmlApplicationContext("spring-config.xml");
// UserController userController=context.getBean("userController",UserController.class);
// userController.sayHi();
// UserService userService=context.getBean("userService",UserService.class);
// userService.sayHi();
UserRepository userRepository=context.getBean("userRepository",UserRepository.class);
UserComponent userComponent=context.getBean("userComponent",UserComponent.class);
UserConfiguration userConfiguration=context.getBean("userConfiguration",UserConfiguration.class);
userComponent.sayHi();
userConfiguration.sayHi();
userRepository.sayHi();
}
}
1.2 五大类注解之间的关系
1.2.1.为什么需要这么多种类注解
为了让程序员看到类注解之后就能直接知道当前类的用途。
例如:@Controller:表示业务逻辑层
@Service:表示服务层
@Repository:表示持久层
@Configuration:表示配置层
了解程序的工程分层会对这个作用有进一步的认识:
JavaEE标准分层至少三个:控制层、服务层、数据持久层,一般公司会在此基础上进行扩展。
这里有配置层是因为有时候感知不到配置文件,所以通过注解告诉他。所以一个项目的配置可能在配置文件或者配置层中。
1.2.2 类注解之间的关系
通过ctrl+左键,我们可以看到五大类注解的源码是这样的:
通过观察,我们不难发现,@Controller / @Service / @Repository / @Configuration 这四个类注解都是基于@Component这个注解实现的。
1.3 方法注解
见名知义,类注解是加在类上的,方法注解是加在方法上的。
使用方法注解,其实就是把表示当前方法的返回值将被装配到容器中。
使用方法:在当前方法上加上@Bean 注解,在方法所属类上边加上对应的类注解。即方法注解必须结合类注解使用。
下边是代码示例:
数据准备:创建实体类User。其中@Setter和@Getter以及@ToString注解是lombok插件提供的自动生成对应类的get、set和toString方法的注解(可略)。
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
@Setter
@Getter
@ToString
public class User {
private int id;
private String name;
public void sayHi(){
System.out.println("do User sayHi()");
}
}
实体类:数据库中有一张表,代码中就会有一个对应的实体类,有一个容器去承接对应的实体类,这个实体类中的属性就对应表中的字段。
对应的包名:entity/model
实体类命名规则:
1.基本对象对应数据库中的一张表:与表名一致
2.扩展对象:xxxinfo/xxxinfoVO
组件层的代码:
@Component
public class UserComponent {
@Bean
public User user1() {
User user = new User();
user.setId(1);
user.setName("Java");
return user;
}
public void sayHi(){
System.out.println("do UserComponent sayHi()");
}
}
启动类:
public static void main(String[] args) {
ApplicationContext context=new ClassPathXmlApplicationContext("spring-config.xml");
User user = (User) context.getBean("user1");
System.out.println(user.toString());
}
1.4 Bean命名规则
不难发现,通常来讲Bean,我们使用的都是标准的大驼峰命名,而使用getBean读取的时候则是使用首字母小写+类类型获取的:
当我们将首字母也写成大写时就会发现有如下错误:NoSuchBeanDefinationException即没有找到Bean异常其实也就是注入失败
因为我们是使用的注解的方式自动注入的,所以Bean的命名是由Spring决定的,根据报错我们不难想到,需要查看Spring关于bean存储生成的规则:
查看方法:双击shift出发全局搜索,输入BeanName,点击AnnotationbeanName…
观察并分析:
结论:默认情况下是首字母小写,如果类名是首字母和次字母都是大写,那么Bean名称就是它的类型名。
二、获取Bean对象
获取Bean对象也叫做对象装配或者对象注入(从Spring容器中拿出来给当前类装配上/注入,相当于一个完成工作必须的部件)。
对象装配实现方法有三种:
- 属性注入
- 构造方法注入
- Setter注入
其中属性注入最简单,实际开发中使用的也最多。构造方法是官方推荐的注入方式,官方给予的支持也很大,稍后详细说一下。
1.属性注入
属性注入实现方法:在对象上添加@Autowired注解。其中autowired是自动连线的意思。
将Service类注入到Controller类中,其中Service连接着数据持久层操作数据库即将Repository类注入到Service类中。
示例:
数据准备:UserRepository类代码:
@Repository
public class UserRepository {
@Autowired
private User user=new User();
@Bean
public User getUser(){
return user;
}
public void sayHi(){
System.out.println("do UserRepository sayHi()");
}
}
UserService类代码:
@Service
public class UserService {
@Autowired
private UserRepository userRepository;
public User getUser(Integer id) {
return userRepository.getUser();
}
// public void sayHi(){
// System.out.println("do UserService sayHi()-->"+userRepository.getUser());
// }
}
UserController类代码:
@Controller
public class UserController {
@Autowired
private UserService userService;
public User getUser(Integer id) {
return userService.getUser(id);
}
// public void sayHi(){
// System.out.println("do UserController sayHi()");
// }
}
获取数据:
public static void main(String[] args) {
ApplicationContext context=new ClassPathXmlApplicationContext("spring-config.xml");
UserController userController = context.getBean(UserController.class);
System.out.println(userController.getUser(1).toString());
}
2.构造方法注入
实现方法:在类的构造方法中实现注入,@Autowired注解加载构造方法上
示例:
@Controller
public class UserController2 {
private UserService userService;
@Autowired
public UserController2(UserService userService) {
this.userService = userService;
}
public User getUser(Integer id) {
return userService.getUser(id);
}
}
public class App {
public static void main(String[] args) {
ApplicationContext context=new ClassPathXmlApplicationContext("spring-config.xml");
UserController2 userController = context.getBean(UserController2.class);
System.out.println(userController.getUser(1).toString());
}
}
注意:当只有一个构造方法时,@Autowired注解可以省略(官方开的绿灯)
当多个构造方式时,必须明确指定到底哪一个构造方法使用@Autowired,否则程序会报错。如下所示:
通过观察我们还可以发现:无参构造很重要,如果我们自己写了一个或多个有参构造方法,就必须要在其中一个上边加@Autowired注解,指定使用哪个进行实例化。
或者说如果我们没标,它回去找无参构造没有就报错注入失败;如果我们标了,就必须是唯一的。
3.Setter方法注入
实现方法:与属性的Setter方法类似, 需要在设置 set ⽅法的时候需要加上 @Autowired 注解。
@Controller
public class UserController3 {
private UserService userService;
@Autowired
public void setUserService(UserService userService) {
this.userService = userService;
}
public User getUser(Integer id) {
return userService.getUser(id);
}
}
思考:set ⽅法如果不加 @Autowired 注解能注⼊成功 吗?
答:不能。注意这里不是注入成功了得到空对象了,因为如果是得到空对象了,返回的就是NULL而不是报错。
三种注入方法的对比
类别 | 优点 | 缺点 |
---|---|---|
属性注入 | 简洁,使用方便 | 1.功能上,没办法实现final修饰变量注入 2.兼容性上,兼容性不好,只适用于IoC容器 3.风险性上,写法简单,违背单一设计原则概率更大 |
构造方法注入 | 1.可注入一个不可变对象 2.注入的对象不会被改变,构造方法只执行一次 3.通用性更好,所有的框架都支持构造方法 4.构造方法保证注入对象完全被初始化 | 当有多个注入时会比较臃肿 |
Setter注入 | 符合单一设计原则,因为只会赋值给一个对象 | 1.不可注入不可变对象 2.注入的对象可能会被修改重置 |
从上边可以看出,对象的注入都是基于@Autowired这个注解实现的
另一种注入关键字:@Resource
@Resource是由jdk提供的注入关键字,@Autowired是由spring提供的。
使用方法:与autowired类似,放在属性上或者方法上,但是只能放在autowired上,不能用于构造方法,会直接报错。
使用示例:
@Controller
public class UserController4 {
private UserService userService;
@Resource//Setter注入
public void setUserService(UserService userService) {
this.userService = userService;
}
public User getUser(Integer id) {
return userService.getUser(id);
}
}
@Controller
public class UserController4 {
@Resource//属性注入
private UserService userService;
public void setUserService(UserService userService) {
this.userService = userService;
}
public User getUser(Integer id) {
return userService.getUser(id);
}
}
@Autowired和@Resource区别
相同点 | 功能上都只能完成对象装配的工作 |
---|---|
不同点 | 1.来源不同,@Autowired来自spring,@Resource来自 2.支持用法不同,@Autowired支持属性注入、构造方法注入和Setter注入三种方式,而@Resource只支持属性注入和Setter方法注入两种方式 3.参数设置上不同,@Resoure支持更多的参数设置 |
实际工作,大部分场景下,两个没有区别,除非特殊情景。
同一类型多个Bean报错
在同一个类中,出现多个Bean,返回同一对象类型时,或者不同类中向Spring中注入相同类型的Bean时,程序可能会报错NoUniqueBeanDefinitionException
即非唯一的Bean对象。
解决办法:
方案1:使用@Resource(name=“xxx”)进行定义(即起别名)
方案2:使用@Qualifier注解定义名称
方案一代码示例:
@Controller
class UserController5 {
@Resource(name = "user1")
private User user;
public User getUser() {
return user;
}
}
方案二代码示例:
@Controller
public class UserController6 {
@Autowired
@Qualifier(value = "user2")
private User user;
public User getUser() {
return user;
}
}
@Autowired、@Resource以及Bean在重命名方面区别
通过看源代码很容易发现,这三个注解都可以通过属性的设置实现对Bean重命名的效果。
默认情况下,spring的标识符要么是默认的,要么是指定Bean的name属性定义的,要么是Qualifier使用value定义的,要么是Resource的name指定的。
注意:1.重命名后,原来的默认方法名就不可以用了
@Order注解的作用
@Order 是用来控制Bean的执行顺序/优先级的而并非加载顺序。
注解@Order或者接口Ordered的作用是定义Spring IOC容器中Bean的执行顺序的优先级,而不是定义Bean的加载顺序,Bean的加载顺序不受@Order或Ordered接口的影响;
扫描路径与注解读取相关问题
-
bean标签能不能与component-scan(组件扫描和路径)一起使用
能
-
五大类注解能不能不再component-scan下
不能,。 -
在component-scan下,但是如果没有加五大类注解,能不能把当前对象加到spring容器中的
不能。
-
不同包下的同名类:
不可以(存储/load时就不行)
两种解决方法:-
直接改名字
-
起别名value选项定义同名类名(默认时null,其他的也是可以设置的)
取对象的时候导包也要改
认情况下,spring的标识符要么是默认的,要么是指定Bean的name属性定义的,要么是Qualifier使用value定义的,要么是Resource的name指定的。 -
注意:1.重命名后,原来的默认方法名就不可以用了
@Order注解的作用
@Order 是用来控制Bean的执行顺序/优先级的而并非加载顺序。
注解@Order或者接口Ordered的作用是定义Spring IOC容器中Bean的执行顺序的优先级,而不是定义Bean的加载顺序,Bean的加载顺序不受@Order或Ordered接口的影响;
扫描路径与注解读取相关问题
-
bean标签能不能与component-scan(组件扫描和路径)一起使用
能
-
五大类注解能不能不再component-scan下
不能,。 -
在component-scan下,但是如果没有加五大类注解,能不能把当前对象加到spring容器中的
不能。
-
不同包下的同名类:
不可以(存储/load时就不行)
两种解决方法:-
直接改名字
-
起别名value选项定义同名类名(默认时null,其他的也是可以设置的)
取对象的时候导包也要改
建议:尽量避免起相同类名 -