在上一篇文章当中,我们提到了怎样使用spring来创建一个bean对象。下面,我们继续来研究一下,更加优胜的开发方式:基于注解开发【JavaEE进阶篇1】认识Spring、认识IoC、使用spring创建对象_革凡成圣211的博客-CSDN博客springIoc、使用spring创建对象https://blog.csdn.net/weixin_56738054/article/details/129540402?spm=1001.2014.3001.5502
目录
第一步:在pom.xml当中导入依赖、并且在xml文件当中添加如下内容
为什么要使用 并且指定base-package的目录?
第二步:把bean存放到IoC容器当中
类注解(作用于类上面)
@Controller:把一个类标记为"控制器"
spring给类命名的规则
@Service
@Repository
@Component
@Configuration
为什么作用都一样,但是还是要这么多注解
5大类注解之间的关系
方法注解(作用在方法上面的注解)
第一步:新建一个User类
第二步:在另外一个类当中自定义一个返回User的方法
第三步:通过getBean方法获取User对象
第三步注意事项:尽量不要使用只传入一个参数的getBean方法
总结一下:@Bean的使用规则:
第三步:从spring容器当中获取bean(对象装配/对象注入)
方式1:属性注入
@Autowired注解
使用@Autowired的注意事项
方式2:构造方法注入对象(官方推荐)
注意事项
方式3:setter注入
@Resource注解以及它和@Autowired的区别
区别1:提供的级别不一样:
区别2:支持注入的方式存在差异:
区别3:支持的参数不一样:
以上三种获取bean的方式的区别
对于属性注入(写法简单,但是注入的bean仅限于IOC容器):
对于setter注入(写法稍微复杂,通用性好:支持非IOC容器,方便单元测试):
对于构造方法注入(安全性提升,bean不可变,通用性良好,支持非IOC容器):
在maven项目当中导入(pom.xml)依赖:
org.springframework
spring-context
5.2.15.RELEASE
org.springframework
spring-beans
5.2.15.RELEASE
在spring配置文件当中,复制以下的内容即可。
所有要存放到spring中bean的根路径,在此处就指定为"Beans"目录及其子目录下面的所有文件。
在spring当中的类分为两大类,一大类是在spring当中的,另外一大类是不在spring当中的。
如果使用了这个注解,那么也就意味着:Beans目录下面的类如果被注解作用了,那么就会被放入spring容器当中。
这样设计,可以有效帮助spring减少扫描的次数,只扫描指定目录的类,提升查找的效率。
一般情况下面,把bean放入到IoC容器当中,需要使用到下面的5大类注解:
类注解作用于类上面之后,都会为这个类在spring容器当中注入一个对象。
控制器的含义就是:三层架构当中的Controller对应的类。
其中,@Controller注解当中传入的参数,就是这一个类对象的名称。
@Controller("User")
public class User {
public void say(){
System.out.println("user say...");
}
}
如果想要获取这个bean,可以这样获取:
public static void main(String[] args) {
ApplicationContext applicationContext=new ClassPathXmlApplicationContext("spring-config.xml");
//在容器当中指定对象的名称,指定User.class的内容
User user=applicationContext.getBean("User",User.class);
user.say();
}
如果@Controller当中没有指定名称。那么,getBean的时候,传入的id就应当默认为User类名称的小驼峰:user。
获取APIContext类的对象:
public static void main(String[] args) {
ApplicationContext applicationContext=new ClassPathXmlApplicationContext("spring-config.xml");
//在容器当中指定对象的名称,此处假设一个类不按照小驼峰的方式开命名
APIContext aPIContext=applicationContext.getBean("aPIContext", APIContext.class);
aPIContext.say();
}
运行就会发现:
下面,来看一下spring给bean命名的潜规则:
来看一下源码:
然后,在这个类的内部,往下拉动,找到这两个方法buildDefaultBeanName方法:
最后,跳转到这个方法:decapitalize。下面,重点来分析一下这个方法:
因此,总结一下spring在没有指定类的名称的时候,是怎样转化的:
当类名称的首字母和第二个字母都是大写的时候:那么对象名称(bean的名称)就是类名。
如果类名称的其余情况:对象名称=类名称的第一个字母转为小写+后面内容一致。
下面,来试验一下这个命名规则:(调用Introspector.decapitalize(String name)这个方法)
public static void main(String[] args) {
String name="UserName";
System.out.println(Introspector.decapitalize(name));
}
运行的结果是:
再实验一下API这样的形式:
public static void main(String[] args) {
String name="APIContext";
System.out.println(Introspector.decapitalize(name));
}
然后观察一下运行的结果:可以看到,此时bean的名称就是类名称了。
作用与Controller一样,用于标注"业务逻辑层"的对象。
作用与Controller一样,用于标注"持久层"的对象
不属于前面的任意3层,那么这个注解就可以认为是一个"工具"。
作用与Controller一样,用于标注"配置类"。
这就涉及到"软件开发"的模型了。为了实现一个软件功能的解耦合,软件开发一般要至少分为4个层次:
层次1:前端的页面展示;
层次2:接口层,用于接收并且校验前端提交的参数,,调用逻辑层,并且作出响应(一般这个层的类需要使用@Controller来标注);
层次3:逻辑层,用于处理接口层传来的数据,并且处理业务逻辑。如果一些业务需要和数据库层打交道,那么逻辑层就会调用下一层。(使用@Service注解作用)
层次4:持久层,用于和数据库打交道的层面。(使用@Repository来作用)
分开了5大类注解,令代码的可读性提高了,让程序员能够直观地判断当前类的业务用途。
当我们点开各个注解的时候,可以看到:除了@Component注解以外的注解,都是基于@Component来实现的。也就是说,@Component是上述所有注解的父类。
这个注解的作用,也是把bean给注入到spring容器当中,但是这个bean是作为方法的返回值。
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 + '\'' +
'}';
}
}
第二步由两个比较重要的部分构成:
第一部分:需要在这个方法上面加一个注解:@Bean。
第二部分:并且还需要在这个方法所在的类上面再加一个五大类注解当中的一个。
为什么spring规定不可以单独把@Bean注解作用于方法上,然后把这个方法的返回值放入到spring容器当中
提高效率!
在类上面增加了注解之后,可以有效降低spring组件扫描的范围。当且仅当一个类被5大注解作用的时候,才会扫描这个类当中@Bean方法返回值注入的对象。
/**
* @author 25043
*/
//这个注解不可以少
@Service
public class UserBeans {
//把方法返回的值作为对象存储到Ioc容器当中
@Bean
public User user1(){
//创建一个User对象
User user=new User();
//设置属性的值
user.setId(1);
user.setName("你好");
//返回user对象
return user;
}
}
此外,还可以在@Bean注解当中指定需要存放对象的名称(通过name属性指定注入的bean的名称):
//在注解当中指定name属性,就是返回值在spring容器当中的bean
@Bean(name = {"user2","userInfo"})
public static User getUser2(){
User user=new User();
user.setId(2);
user.setName("你好2");
return user;
}
这个时候,通过两个不同的key,也可以找到同一个user了。因为此时在ioC容器当中,有两个相同的key指向了同一个user。
getBean方法当中,传入的两个参数分别是:
@Bean注解作用的方法的名称
User类的class对象
public static void main(String[] args) {
//获取spring上下文对象
ApplicationContext applicationContext=new ClassPathXmlApplicationContext("spring-config.xml");
//传入两个参数,一个是方法名称:user1,另外一个是User的class对象
User user=applicationContext.getBean("user1",User.class);
System.out.println(user);
}
运行的结果:
如果getBean()方法当中只传入一个参数:User.class的话,那么此时默认IoC容器当中只有一个User类型的bean。如果有两个User类型的bean的话,那么就会报错。
下面演示一下出错的情况:
①放入两个不同的bean,但是都是User类型
②调用getBean方法,尝试获取user对象,但是传入的参数只是User.class
此时,在spring的IoC容器当中,有两个同一类型,但是名字不同的bean。
可以看到,一运行程序就报了个异常:NoUniqueBeanDefinitionException
规则1:@Bean作用于方法上面,并且这个方法所在的类一定要被5大类注解注释;这样才可以把这个方法的返回值注入到spring的IoC容器当中。
规则2:
如果没有指定bean的name属性,那么默认这个方法返回值对应的bean的名称为方法的名称。
如果指定了name的名称,那么bean的名称就是方法的名称;一个@Bean注解可以指定多个name
规则3:
如果@Bean注解同时作用在多个方法上面,并且注入了多个相同类型的bean的时候
不可以使用单独传入类名称.class的那一额给getBean方法类获取bean。因为这个getBean方法默认此时spring容器当中只有一个bean。
在第二步当中,我们提到了如何使用五大类注解和@Bean注解把对象放入到spring容器当中。
那么,下面,我们再来聊聊怎样使用注解把这些bean给从spring容器当中取出来。(3种方式)
通过一个类的属性来获取对象。(也就是把需要获取的对象作为类的属性)
这个注解作用于类的属性上面的时候,相当于告诉spring容器:在加载当前类的时候,首先需要考虑为这个属性注入一个容器当中存在的bean(对象)。
代码实现:
步骤1:属性被注入的类:
/**
* @author 革凡成圣211
*/
@Controller
public class UserController {
/**
* 这个注解的含义:在加载UserController这个类的时候
* 首先需要为user这个属性注入它对应的bean(对象)
*/
@Autowired
private User user;
public void sayHi(){
System.out.println(user);
}
}
步骤2:存放bean的类(此处的bean为user对象)
在下面的代码当中,通过@Bean注解往spring的IoC容器当中存放两个bean(两个user对象)。
留意一下一会儿会发生什么问题
/**
* 这个类用于测试存放userBean的效果
* @author 25043
*/
@Component
public class UserBeans {
//在注解当中指定name属性,就是返回值在spring容器当中的bean
@Bean(name = {"user2","userInfo"})
public static User getUser2(){
User user=new User();
user.setId(2);
user.setName("你好2");
return user;
}
@Bean
public User user1(){
User user=new User();
user.setId(1);
user.setName("你好");
return user;
}
}
步骤3:编写main方法的测试类
从spring上下文容器当中获取userController,然后调用userController的sayHi方法。看看userController类的User属性是否被注入成功
public static void main(String[] args) {
//获取spring上下文对象
ApplicationContext applicationContext=new ClassPathXmlApplicationContext("spring-config.xml");
//获取spring容器当中的userController
UserController userController=applicationContext.getBean("userController",UserController.class);
userController.sayHi();
}
可以看到,此时报错提示:spring容器当中有两个bean,一个user1另外一个user2,因此无法正确注入。
因此,可以得出结论:
使用@Autowired注解给属性注入bean的时候,一定要确保此时spring容器当中只有唯一一个和@Autowired注入的属性相同的bean。
所以:如果把步骤2当中的两个bean去掉一个,那么程序就可以正常运行了。
需要把一个@Autowired注解作用在构造方法上面,然后赋值。
/**
* @author 革凡成圣211
*/
@Controller
public class UserController {
private User user;
/**
* 当@Autowired作用在构造方法上面的时候
* 传入的参数@param user
*/
@Autowired
public UserController(User user){
this.user=user;
}
public void sayHi(){
//可以正常输出内容
System.out.println(user);
}
}
如果只有一个构造方法的时候,作用在构造方法上面的@Autowired注解可以省略。但是如果有多个构造方法的时候,不可以省略。
并且只能给一个构造方法添加@Autowired注解。不可以同时给多个构造方法加上@Autowired
private User user;
/**
* 当@Autowired作用在构造方法上面的时候
* 传入的参数@param user
*/
@Autowired
public UserController(User user){
this.user=user;
}
/**
* 如果想给user注入bean
* 那么这个构造方法就不可以加上@Autowired
*/
public UserController(){
}
在一个成员方法上面加上@Autowired注解
然后赋值给user属性。
其他成员方法也可以加上@Autowired注解,执行相同的操作。
/**
* @author 革凡成圣211
*/
@Controller
public class UserController {
private User user;
@Autowired
public void setUser(User user) {
this.user = user;
}
public void sayHi(){
//可以正常输出内容
System.out.println(user);
}
}
这个注解的作用和@Autowired的作用类似,也可以为类的属性直接注入springIoC容器当中的bean。
/**
* @author 革凡成圣211
*/
@Controller
public class UserController {
@Resource
private User user;
public void sayHi(){
//可以正常输出内容
System.out.println(user);
}
}
但是,@Autowired注解仍然在一些语法上面和@Resource有区别:
@resource是jdk提供的注解,而@Autowired是spring框架提供的。
@Resource注解支持属性注入和setter注入,但是不支持构造方法注入。
@Resource支持的参数更多,例如name参数,type参数等设置。
name参数:指定需要获取springIOC容器当中的具体哪一个bean。
如果没有在@Resource注解当中指定name属性,那么它的作用就和@Autowired一样了。出现多个同一类型的bean就会报错。
但是,@Autowired仅仅只支持一个参数:required。
如果required参数设置为true,那么spring容器当中一定要存在对应的bean。否则会抛出异常:NoSuchBeanDefinitionException异常。
如果为false,那么没有bean定义的的时候,就会在获取这个bean的时候返回null。
属性注入的特点是写法简单,通用性不好。写法简单就是直接在类的属性上面注入一个bean对象即可。但是如果这个属性对应的bean不在IoC容器当中的时候,这种写法就会失效。
这个是早期的spring版本推荐的写法。它的特点是:通用性提高了,支持非IOC容器的写法。程序员可以通过调用setter方法进行传参。
同时,这一种方式还方便单元测试。
它可以保证在使用这个类的其他方法之前,这个属性已经被注入过了,从而有效避免了NPE。
但是,当构造方法当中参数过多的时候,就需要程序员自己检查一下这种做法是否符合单一设计原则规范了。
总结: