Spring的创建和使用
spring的创建
spring是一个IoC容器,我们创建spring容器只需要在idea中创建一个maven项目,然后再pom.xml
中添加相应的依赖,一个spring容器就创建好了
<!-- https://mvnrepository.com/artifact/org.springframework/spring-context -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.3.27</version>
</dependency>
在pom.xml中添加这段依赖就可以完成对spring的创建了
spring的使用
上面说到spring是一个容器,那么想要使用这个容器我们就得往容器里面存东西进去,这样才算是使用spring这个容器,那么我们把存进去的东西称作“bean”,这个bean可以是一个类,也可以是一个方法
关于bean的存储,我们首先需要创建一个spring的xml配置文件
我们把这段代码复制粘贴到我们新建的xml文件里面
使用配置文件我们就声明了哪一个bean需要存储到spring容器中
<?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.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd">
<content:component-scan base-package="com.java.demo"></content:component-scan>
</beans>
然后再在配置文件中添加一个bean
这里我使用我创建好的一个名称为Student的类进行存储
那么具体的代码就是
<bean id="student" class="demo.Student"></bean>
这段代码就声明了我需要将demo包底下的Student类取名为“student”存储到spring容器里面去
紧接着存完一个bean以后那么就要使用这个bean,也就是说要把bean从spring中取出来
具体的流程如下:
1.首先得到spring的上下文对象
我们可以创建 ApplicationContext 这个类或者 BeanFactory 这个类来得到一个spring容器
BeanFactory beanFactory = new XmlBeanFactory(new ClassPathResource("spring-config.xml"));
//or
ApplicationContext context = new ClassPathXmlApplicationContext("spring-config.xml");
然后我们需要从创建相应的一个类来接收从spring容器中取到的对象
Student student;
从spring容器中取出bean对象有三种方法
//根据名称取到对象以后强转类型
Student student = (Student) context.getBean("student");
Student student = (Student) beanFactory.getBean("student");
//根据类的类型进行获取
Student student = context.getBean(Student.class);
//直接设置好类型进行接收
Student student = context.getBean("student",Student.class);
由于spring容器里面可能会出现多个重名的不同类对象或多个不同名但相同的类对象
当遇到这两种情况的时候执行代码就会报错了
那么我们使用第三种方法既保证了取bean的正确性又使得代码更加的美观
最后将这个bean取出来以后就可以使用这个类里面的方法了
执行bean里面的方法就可以测试得到是否将bean取出来了
完整代码如下
public static void main(String[] args) {
//得到Spring
ApplicationContext context =
new ClassPathXmlApplicationContext("spring-config.xml");
//从bean里面获取到bean对象
Student student = (Student) context.getBean("student");
//使用bean对象
student.sayHi();
}
关于使用ApplicationContext和BeanFactory两个类来得到spring容器的区别
1.从类的关系上来说:这两类都是Spring容器的顶级接口,BeanFactory是提供了访问Spring的功能,而ApplicationContext是BeanFactory的子类,它继承BeanFactory里面的所有功能以外还拥有独特的特性,例如资源访问支持,国际化利用等等
2.从性能的角度上来说:ApplicationContext在得到Spring对象以后是将容器里面所有的bean都进行初始化操作的,比较占内存;但是BeanFactory则是需要哪个bean对象才会初始化哪个bean对象,因此BeanFactory更加轻量化的同时使用起来的效率并不高
关于如何更简单的存储bean和使用bean
事实上我们可以更加简单对bean进行存储,我们使用五大类注解就可以对类进行存储
这个五大类注解分别是:
@Controller
public class StudentController {
public void sayHi(){
System.out.println("do Student Controller Say Hi");
}
}
@Service
public class StudentService {
public void sayHi(){
System.out.println("do StudentService Say Hi");
}
}
@Component
public class StudentComponent {
public void sayHi(){
System.out.println("do StudentComponent Say Hi");
}
}
@Configuration
public class StudentConfiguration {
public void sayHi(){
System.out.println("do StudentConfiguration Say Hi");
}
}
@Repository
public class StudentRepository {
public void sayHi(){
System.out.println("do Student Repository Say Hi");
}
}
使用这五大类注解可以更加轻松将bean存取到spring容器里面
也许刚开始学习的时候会有这样的疑惑:
为什么同一种注解就可以将各种类存储到spring容器里面了,还需要五个类注解?这不是多此一举吗?
实际上并不是这样的,在JavaEE的标准分层里面我们可以发现分成了至少三层分别是控制层,服务层,数据链路层。我们使用类注解的方式就可以很明确的知道这个类是属于哪一个分层里面的类,知道这个类的作用是什么
同时我们点开除了@Component外的所有注解我们会发现,实际上这些注解都是被@Component所注解的
那么我们在从spring容器里面把他们取出来的时候,获取的方式是和使用xml来进行注册的bean获取方式是一样的
只不过使用五大类注解进行存储的时候,bean的名称是与类的名称相关的
例如上面说到的 UserController 这个类,那么将他存储到Spring容器里面以后,这个类的名称就是“userController”,也就是说使用注解进行存储以后,spring里面bean的名称和原来类的名称是一致的,但是第一个字母会自动变成小写!
当然也有特殊例子,当我们将 SController 这个类存储到Spring容器里面我们会发现他的名称还是SController
翻看源码我们可以发现
public static String decapitalize(String name) {
if (name == null || name.length() == 0) {
return name;
}
if (name.length() > 1 && Character.isUpperCase(name.charAt(1)) &&
Character.isUpperCase(name.charAt(0))){
return name;
}
char chars[] = name.toCharArray();
chars[0] = Character.toLowerCase(chars[0]);
return new String(chars);
}
当第一个字母以及第二个字母都是大写的时候那么就直接返回这个类名作为bean的名称
如果不满足这个条件的话那么就会创建一个char类型的数组然后将第一个字母进行小写以后再将这个数组转换成字符串的形式给bean进行命名
这里的五大类注解实际上还是依赖于配置文件的(前面提到的),我们通过配置文件里面的这个段代码对指定的包里面进行扫描,查看哪些类要通过注解的方式存储到了spring里面,对于不在这个包里面的类就无法通过类注解的方式进行存储了
这样做的好处就是提高了spring的性能,否则通篇的扫描项目里面的类会大大提高程序执行的时间
<content:component-scan base-package="com.java"></content:component-scan>
前面提到的我们同样可以将方法存储到Spring容器里面去,我们使用@Bean这个注解就可以将方法存储到Spring容器里了
但是需要注意的是:这个方法必须要有返回值,同时这个方法是在被五大类注解存储的类里面实现的,否则无法进行存储!存进去以后这个bean的名称默认是与方法名相同的
@Bean
public User user(){
User user = new User();
user.setUserId(1);
user.setUserName("张三");
user.setUserGender("男");
return user;
}
那么我们取bean的时候也同样的需要创建一个User类进行接收
User zhangsan = context.getBean("user",User.class);
System.out.println(zhangsan.getUserGender());
当然我们也这个bean的名称也是可以自定义的,我们只需要在注解的后面添加即可,下面给出一个例子
@Bean(name = "userZhang")
public User user(){
User user = new User();
user.setUserId(1);
user.setUserName("张三");
user.setUserGender("男");
return user;
}
要注意的是,当我们使用了重命名以后,再次使用原本方法名进行获取那么获取不到这个bean了
Exception in thread "main" org.springframework.beans.factory.BeanNotOfRequiredTypeException: Bean named 'user' is expected to be of type 'com.java.User' but was actually of type 'com.demo.UserController'
at org.springframework.beans.factory.support.AbstractBeanFactory.adaptBeanInstance(AbstractBeanFactory.java:417)
at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:398)
at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:213)
at org.springframework.context.support.AbstractApplicationContext.getBean(AbstractApplicationContext.java:1162)
at App.main(App.java:43)
(运行结果)
说完了如何存储bean对象再说说如何更简单的获取bean对象
我们可以通过@Autowired这个注解来更简单的获取到bean对象
通过@Autowired有三种方式进行获取
第一种是属性注入的方式
@Autowired
private UserService userService;
第二种是通过构造一个setter方法来进行注入
@Autowired
public void setUserService(UserService userService) {
this.userService = userService;
}
第三种是通过构造方法的形式来进行注入
public UserController(UserService userService){
this.userService = userService;
}
属性注入的优点在于简单有效,但由于使用简单所以很有可能会违背单一性原则,同时不能够对final修饰的对象进行注入
使用setter方法进行注入的通用性不如构造方法进行注入的通用性好,同时也不能够对final修饰的对象进行注入
通过构造方法进行注入的方式是spring推荐的方式,同时它可以对final修饰的对象进行注入(根据Java的语法规定,final修饰的对象只能通过直接赋值的方式或者构造方法的形式进行赋值),通用性也更好
另一种方式是通过@Resource这个注解进行注入
@Resource
private UserService userService;
//使用setter来进行注入
@Resource
public void setUserService(UserService userService) {
this.userService = userService;
}
两者在Spring中的查找方式并不同,但是他们都是根据类名来作为bean的名称进行查找的
比如说此时的类名是 userService ,那么查找bean的名称时,会根据userService来匹配对应的bean名称
关于@Autowired和@Resource这两者的区别在于
1.@Autowired先根据类型进行查找然后再根据名称进行查找
@Resouce则是先根据名称进行查找然后再根据类型进行查找
2.@Resouce可以添加更多的参数例如设置名称而@Autowired却不行
3.@Resouce并不支持通过构造方法的方式进行构建
下面举一个例子来说名两者的区别
@Setter
@Getter
@ToString
public class User {
private String name;
private int id;
}
@Component
public class UserController {
@Bean
public User user1(){
User user = new User();
user.setId(1);
user.setName("张三");
return user;
}
@Bean
public User user2(){
User user = new User();
user.setId(1);
user.setName("张三");
return user;
}
}
@Component
public class UserUsing {
@Resource
private User user;
public User getUser() {
return user;
}
}
public class Main {
public static void main(String[] args) {
//得到Spring
ApplicationContext context =
new ClassPathXmlApplicationContext("spring-config.xml");
//从bean里面获取到bean对象
UserUsing userUsing = context.getBean("userUsing",UserUsing.class);
//使用bean对象
userUsing.getUser();
}
}
这里我将两个都为User的对象存储到Spring容器里面了,但是当我不管是使用哪一种注解,都是取不到相应的对象的
给出的运行结果可以很清楚的看到,Spring想要取User这个类但是却有两个,不知道应该取哪一个
Caused by: org.springframework.beans.factory.NoUniqueBeanDefinitionException: No qualifying bean of type 'demo.User'
available: expected single matching bean but found 2: user1,user2
所以这个时候当我们使用Resouce指定取得是哪一个bean对象就可以解决这个问题
@Resource(name = "user1")
private User user;
public User getUser() {
return user;
}
或者使用@Autowired+@Qualifier得方式也可以解决
@Autowired
@Qualifier(value = "user2")
private User user2;