✏️作者:银河罐头
📋系列专栏:JavaEE
🌲“种一棵树最好的时间是十年前,其次是现在”
前面介绍了通过配置文件的方式来存储 Bean 对象,那么有没有更简单的方式去存储 Bean 对象?
有以下 2 种方式:
1.类注解(五大类注解) : @Controller、@Service、@Repository、@Component、@Configuration.
2.方法注解:@Bean
存储 Bean 对象
前置工作:配置扫描路径
五大类注解
@Controller
控制器,验证用户请求的数据是否正确。
@Controller //将当前类存储到 spring 中
public class StudentController {
public void sayHi(){
System.out.println("do studentController sayHi()");
}
}
public class App {
public static void main(String[] args) {
//1.得到 spring 对象
ApplicationContext applicationContext =
new ClassPathXmlApplicationContext("spring-config.xml");
//2.得到 Bean 对象
StudentController studentController =
applicationContext.getBean("studentController",StudentController.class);
//3.使用 Bean
studentController.sayHi();
}
}
感觉小驼峰才行。
再看一个例子。
@Controller
public class SController {
public void sayHi(){
System.out.println("do SController sayHi");
}
}
public class App {
public static void main(String[] args) {
//1.得到 spring 对象
ApplicationContext applicationContext =
new ClassPathXmlApplicationContext("spring-config.xml");
SController sController = applicationContext.getBean("sController",SController.class);
sController.sayHi();
}
}
当类名的首字母是大写,第二个字母是小写时,Bean 的名称使用首字母小写,如:StudentController;
(特例)当类名的首字母和第二个字母都是大写时,Bean 的名称使用原类名。如:SController.
Spring 设计理念:约定大于配置。
@Service
服务,编排和调度具体执行方法。
@Service
public class StudentController2 {
public void sayHi(){
System.out.println("do studentController2 sayHi()");
}
}
public class App {
public static void main(String[] args) {
//1.得到 spring 对象
ApplicationContext context =
new ClassPathXmlApplicationContext("spring-config.xml");
StudentController2 studentController2 =
context.getBean("studentController2",StudentController2.class);
studentController2.sayHi();
}
}
@Repository
持久层,和数据库交互。
@Repository
public class StudentController3 {
public void sayHi(){
System.out.println("do studentController3 sayHi()");
}
}
public class App {
public static void main(String[] args) {
ApplicationContext context =
new ClassPathXmlApplicationContext("spring-config.xml");
StudentController3 studentController3 =
context.getBean("studentController3",StudentController3.class);
studentController3.sayHi();
}
}
@Component
组件
@Component
public class StudentController4 {
public void sayHi(){
System.out.println("do studentController4 sayHi()");
}
}
public class App {
public static void main(String[] args) {
ApplicationContext context =
new ClassPathXmlApplicationContext("spring-config.xml");
StudentController4 studentController4 =
context.getBean("studentController4", StudentController4.class);
studentController4.sayHi();
}
}
@Configuration
配置项。
@Configuration
public class StudentController5 {
public void sayHi(){
System.out.println("do studentController5 sayHi()");
}
}
public class App {
public static void main(String[] args) {
ApplicationContext context =
new ClassPathXmlApplicationContext("spring-config.xml");
StudentController5 studentController5 =
context.getBean("studentController5", StudentController5.class);
studentController5.sayHi();
}
}
问题
bean 标签是否可以和 component-scan 一起使用?
答案是可以。
public class UserService {
public void sayHi(){
System.out.println("do userService sayHi()");
}
}
<content:component-scan base-package="com.java.demo"></content:component-scan>
<bean id="userService" class="com.java.service.UserService"></bean>
public class App {
public static void main(String[] args) {
ApplicationContext context =
new ClassPathXmlApplicationContext("spring-config.xml");
UserService userService =
context.getBean("userService",UserService.class);
userService.sayHi();
}
}
五大类注解是否可以加到非 base-package 里?
@Service
public class StudentService {
public void sayHi(){
System.out.println("do studentService sayHi()");
}
}
public class App {
public static void main(String[] args) {
ApplicationContext context =
new ClassPathXmlApplicationContext("spring-config.xml");
StudentService studentService =
context.getBean("studentService",StudentService.class);
studentService.sayHi();
}
}
答案是: 不可以!!!
在 component-scan 包里但不加五大类注解是否能读取到?
答案是不可以!!!
//@Controller
public class StudentController {
public void sayHi(){
System.out.println("do studentController sayHi()");
}
}
public class App {
public static void main(String[] args) {
ApplicationContext context =
new ClassPathXmlApplicationContext("spring-config.xml");
StudentController studentController
= context.getBean("studentController",StudentController.class);
studentController.sayHi();
}
}
component-scan 子包的类能读取到吗?
package com.java.demo.controller.bbb;
import org.springframework.stereotype.Controller;
@Controller
public class UserController {
public void sayHi(){
System.out.println("do userController 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();
}
}
component-scan 下的所有子包里的类加了五大类注解都能存储到 spring 中。
不同包的同名类是否能正确读取?
package com.java.demo;
import org.springframework.stereotype.Controller;
@Controller
public class UserController {
public void sayHi(){
System.out.println("com.java.demo -> do userController sayHi()");
}
}
package com.java.demo.controller.bbb;
import org.springframework.stereotype.Controller;
@Controller
public class UserController {
public void sayHi(){
System.out.println("com.java.demo.controller.bbb -> do userController 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();
}
}
答案是不可以!!!
可以把其中一个重命名来解决冲突。
package com.java.demo;
import org.springframework.stereotype.Controller;
@Controller
public class UserController {
public void sayHi(){
System.out.println("com.java.demo -> do userController sayHi()");
}
}
package com.java.demo.controller.bbb;
import org.springframework.stereotype.Controller;
@Controller(value = "UserController2")
public class UserController {
public void sayHi(){
System.out.println("com.java.demo.controller.bbb -> do userController 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();
com.java.demo.controller.bbb.UserController userController2 =
context.getBean("UserController2",
com.java.demo.controller.bbb.UserController.class);
userController2.sayHi();
}
}
五大类注解之间有什么关系?
Controller 是 基于 Component 实现的.
Service 是基于 Component 实现的.
Repository 是基于 Component 实现的.
Configuration 是基于 Component 实现的.
@Controller / @Service / @Repository / @Configuration 都是 @Component 的"子类"。
为什么要这么多类注解?
JavaEE 标准分层,至少是三层:1.控制层 2.服务层 3.数据持久层
需要这么多注解的作用就是让人看到注解之后能知道这个类的作用。
下图出自《阿里巴巴 Java 开发手册-嵩山版》应用分层:
Bean 命名规则
而 decapitalize 是来自于 jdk.
Bean 命名规则:
默认情况下是首字母小写;特殊情况如果类名首字母和第二个字母都是大写,Bean 名称为原类名。
下面测试一下 Bean 的命名规则:
import java.beans.Introspector;
public class BeanNameTest {
public static void main(String[] args) {
String className = "UserClass";
String className2 = "UClass";
System.out.println("UserClass -> " + Introspector.decapitalize(className));
System.out.println("UClass -> " + Introspector.decapitalize(className2));
}
}
方法注解@Bean
要求这个方法必须有返回值。
@Bean 将方法返回的对象存到 spring 中。
方法注解要配合类注解使用。
实体类命名规则:
package com.java.demo.entity;
public class User {
private Integer uid;
private String username;
private String password;
private Integer age;
public Integer getUid() {
return uid;
}
public void setUid(Integer uid) {
this.uid = uid;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
}
package com.java.demo.component;
import com.java.demo.entity.User;
import org.springframework.context.annotation.Bean;
public class UserBeans {
@Bean
public User user1(){
User user = new User();
user.setUid(1);
user.setUsername("张三");
user.setPassword("123456");
user.setAge(18);
return user;
}
}
public class App {
public static void main(String[] args) {
ApplicationContext context =
new ClassPathXmlApplicationContext("spring-config.xml");
User user = context.getBean("user", User.class);
System.out.println(user.getUsername());
}
}
如果按照之前五大类注解 Bean 的名称命名规则去取 User , 结果报错:
造成报错的原因有 2 个:
1.@Bean 的命名规则和五大类注解的命名规则不同,Bean 的名称 = 方法名
2.@Bean 必须要配合五大类注解使用.(Spring 为了提升性能)
重命名 Bean
@Bean 重命名用 “name” 或 “value” 都行。并且重命名可以起多个名字(String[])。
问题:@Bean 重命名之后,用 方法名还能成功获取到 对象吗?
答案是不行!!!
问题:如果有多个 User 对象存在 Spring 当中,还能成功获取到吗?
@Component
public class UserBeans {
@Bean(name = {"user1","u1"})
public User getuserbyid(){
User user = new User();
user.setUid(1);
user.setUsername("张三");
user.setPassword("123456");
user.setAge(18);
return user;
}
@Bean
public User getuserbyname(){
User user = new User();
user.setUid(2);
user.setUsername("李四");
user.setPassword("654321");
user.setAge(20);
return user;
}
}
public class App {
public static void main(String[] args) {
ApplicationContext context =
new ClassPathXmlApplicationContext("spring-config.xml");
User user = context.getBean("getuserbyname", User.class);
System.out.println(user.getUsername());
}
}
答案是可以。
Spring 容器允许将同一个类型的多个对象存到容器中。
问题:如果在不同类中有同名的方法,会出现什么情况?
@Component
public class UserBeans2 {
@Bean
public User getuserbyname(){
User user = new User();
user.setUid(2);
user.setUsername("王五");
user.setPassword("654321");
user.setAge(20);
return user;
}
}
public class App {
public static void main(String[] args) {
ApplicationContext context =
new ClassPathXmlApplicationContext("spring-config.xml");
User user = context.getBean("getuserbyname", User.class);
System.out.println(user.getUsername());
}
}
@Component
@Order(20)
public class UserBeans {
@Component
@Order(10)
public class UserBeans2 {
如果在 类前面加个 @Order 注解,就会设置顺序。@Order可以控制注入的顺序。@Order 值越大,优先级越大。
获取 Bean 对象(对象装配)
更加简单的从 spring 容器中读取 Bean.
属性注入
package com.java.demo.controller;
import com.java.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
@Controller
public class UserController {
@Autowired
private UserService userService;
public void sayHi(){
System.out.println("com.java.demo -> do userController sayHi()");
userService.sayHi();
}
}
@Service
public class UserService {
public void sayHi(){
System.out.println("do userService sayHi()");
}
}
运行报错。因为 main 方法是 static , 执行早于 spring. 所以这样是无法从 spring 中获取到 Bean.
属性注入:
优点:简单。
缺点:
1.不能实现 final 属性的注入。
2.兼容不好,只适用于 IoC 容器.
3.因为使用简单,所以违背单一设计原则的概率更大。
setter 注入
package com.java.demo.controller;
import com.java.demo.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
@Controller
public class UserController {
// 1.属性注入
// @Autowired
// private UserService userService;
//2.Setter 注入
private UserService userService;
@Autowired
public void setUserService(UserService userService) {
this.userService = userService;
}
public void sayHi(){
System.out.println("com.java.demo -> do userController sayHi()");
userService.sayHi();
}
}
Setter注入
优点:
1.符合单一设计原则,一个 Setter 只针对一个对象。
缺点:
1.不能注入不可变对象(final 修饰的对象)
2.注入的对象可以被修改。
因为 setUserService() 方法它是支持被反复调用的。
构造方法注入
spring 官方推荐的注入方式。
package com.java.demo.controller;
import com.java.demo.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
@Controller
public class UserController {
//3.构造方法注入
private UserService userService;
@Autowired
public UserController(UserService userService) {
this.userService = userService;
}
public void sayHi(){
System.out.println("com.java.demo -> do userController sayHi()");
userService.sayHi();
}
}
构造方法注入有个特别的地方在于:它不加 @Autowired 注解也能运行成功!
但是,如果一个类里有多个构造方法的时候,@Autowired 注解不能省略。
优点:
1.能注入不可变对象(final 修饰的对象)
问题来了:为什么构造方法能够诸如一个不可变对象,而属性注入和 setter 注入不行?
final 修饰的对象,要么是直接赋值;要么是在构造方法中被赋值。
//1.
private final Integer num;
public App2(Integer num) {
this.num = num;
}
//2.
private final Integer num2 = 10;
2.注入对象不会被修改,因为构造方法只能执行一次。
3.构造方法注入能够保证注入对象已经完全初始化。
4.兼容性更好。
@Resource:另⼀种注入关键字
@Autowired 来自 spring,
@Resource 来自 JDK.
@Resource 支持 属性注入和setter 注入,但是不支持 构造方法注入。
@Autowired 和 @Resource 的区别:
1.@Autowired 来自 spring, @Resource 来自 JDK.
2.@Autowired 支持 3 种注入方式,而 @Resource 只支持 2 种注入方式。
3.@Resource 支持更多的参数设置,而@Autowired 只支持 1 种
如果一个类的多个对象放到 spring 当中,此时使用 @Autowired 就不行,因为 @Autowired 不支持重命名。而
@Resource 可以。
下面通过代码来验证关于参数这一点,如果把一个类的多个对象存到 spring 当中,去取 Bean 用@Autowired 会报错。
@Component
@Order(1)
public class UserBeans {
@Bean(name = {"user1","u1"})
public User getuserbyid(){
User user = new User();
user.setUid(1);
user.setUsername("张三");
user.setPassword("123456");
user.setAge(18);
return user;
}
@Bean
public User getuserbyname(){
User user = new User();
user.setUid(2);
user.setUsername("李四");
user.setPassword("654321");
user.setAge(20);
return user;
}
}
@Component
@Order(10)
public class UserBeans2 {
@Bean
public User getuserbyname(){
User user = new User();
user.setUid(2);
user.setUsername("王五");
user.setPassword("654321");
user.setAge(20);
return user;
}
}
前面写过的 User 类的多个对象存到 spring 当中。
@Controller
public class UserController2 {
@Autowired
private User user;
public void sayHi(){
System.out.println("com.java.demo -> do userController2 sayHi()");
System.out.println(user.getUsername());
}
}
public class App {
public static void main(String[] args) {
ApplicationContext context =
new ClassPathXmlApplicationContext("spring-config.xml");
UserController2 userController2 =
context.getBean("userController2",UserController2.class);
userController2.sayHi();
}
}
在 spring 容器中查找 Bean,有 2 种方式:1.根据类查找,2.根据名称查找。
@Autowired 会先根据类型(byType)查找,再根据名称(byName)查找。如果这样查了之后还找不到一个唯一的 Bean 就会报错。
而@Resource 相反,它是先根据名称去查,然后再根据类型去查。
这个查找顺序是 @Autowired 和 @Resource 的第四个区别。
上面User 那个例子 把 @Autowired 改成 @Resource 还是会报错,原因还是 找不到 唯一的 Bean.
不过,@Resource 可以设置 name 参数,起别名。
那如果我非要用 @Autowired 去取呢?
可以多加一个 @Qualifier 起到一个筛选的作用。
小练习
在 Spring 项目中,通过 main 方法获取到 Controller 类,调用 Controller 里面通过注⼊的⽅式调⽤ Service 类,Service 再通过注入的方式获取到 Repository 类,Repository 类里面有⼀个⽅法构建⼀ 个 User 对象,返回给 main 方法。Repository 无需连接数据库,使用伪代码即可。
import com.java.demo.controller.MyController;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class MyApp {
public static void main(String[] args) {
ApplicationContext context =
new ClassPathXmlApplicationContext("spring-config.xml");
MyController myController = context.getBean("myController", MyController.class);
myController.sayHi();
}
}
package com.java.demo.controller;
import com.java.demo.service.MyService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
@Controller
public class MyController {
@Autowired
private MyService myService;
public void sayHi(){
System.out.println("do MyController sayHi()");
myService.sayHi();
}
}
package com.java.demo.service;
import com.java.demo.repository.MyRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
public class MyService {
@Autowired
private MyRepository myRepository;
public void sayHi(){
System.out.println("do MyService sayHi()");
System.out.println(myRepository.getUser().getUsername());
}
}
package com.java.demo.repository;
import com.java.demo.entity.User;
import org.springframework.stereotype.Repository;
@Repository
public class MyRepository {
public User getUser(){
User user = new User();
user.setUsername("周八");
return user;
}
}