目录
前言
注解
存储Bean
通过类注解
配置扫描路径
添加类注解存储Bean对象
@Controller(控制器存储)
@Service(服务存储)
@Repository(仓库存储)
@Component(组件存储)
@Configuration(配置存储)
类注解之间的关系
Bean的命名规则
通过方法注解
重命名Bean
方式一
方式二
方式三
方式四
获取Bean(对象装配)
属性注入
属性注入的优缺点
Setter注入
Setter注入优缺点
构造方法注入
构造方法优点
前言
上篇文章介绍了spring的创建和基本的存储、读取对象的方式,但是在操作过程中我们发现过程其实是很麻烦的,所以接下来我们将学习更加简单的读取和存储对象的方式。
注解
在Spring框架中,注解是一种用于配置和管理应用程序组件的特殊标记,通过使用注解,可以简化配置和开发过程,使整体代码更加的清晰,易读和易于维护。
在Spring中想要更加简单的存储和使用对象的核心就是使用注解。
存储Bean
通过类注解
其实当我们在学习JavaEE这块的时候,我们的思想首先应该转变一下,回忆之前的代码,很多时候我们都是自己写功能,自己写实现方式,其实在spring部分,我们想要实现一个功能,我们首先应该想到有没有对应实现该功能的注解。
我们在之前进行Bean的存储时,需要在spring-config.xml这个文件中添加一个bean标签用于注册Bean才行。
这是之前我们的写法,如果有多个Bean对象需要存储时,此时就很麻烦了,就需要一个Bean写一个bean标签了,而现在我们只需要一个注解就可以解决之前的尴尬了。
配置扫描路径
想要将对象存储到spring容器中去,我们就需要配置一下存储对象的扫描包路径,只有在我们配置了扫描包路径下面的类,添加了注解之后,才会正确的存储到spring容器中去。
和之前创建spring的方式一致,我们需要有spring-config.xml这个配置文件。
在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.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd">
<content:component-scan base-package="com.java.domo">
</content:component-scan>
</beans>
也就是说,只有在这个路径下,同时使用了注解才能被spring存储。
添加类注解存储Bean对象
想要将对象存储到Spring中,有两种注解可以实现:
- 类注解 @Controller、@Service、@Repository、@Component、@Configuration
- 方法注解 @Bean
@Controller(控制器存储)
使用@Controller注解存储Bean对象:
@Controller
public class UserController {
public void sayHi() {
System.out.println("hello Controller");
}
}
然后我们在通过之前获取对象的方式来读取上面的UserController对象。
public class Application {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("Spring-config.xml");
UserController userController = context.getBean("userController", UserController.class);
userController.sayHi();
}
}
可以看到对象是成功的获取到了,并且成功的调用了里面的方法。
@Service(服务存储)
使用@Service注解存储Bean对象:
@Service
public class UserService {
public void sayHi(String name) {
System.out.println("name=" + name);
}
}
读取Bean:
public class Application {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("Spring-config.xml");
UserService userService = context.getBean("userService", UserService.class);
userService.sayHi("hello");
}
}
@Repository(仓库存储)
使用@Repository注解存储Bean对象:
@Repository
public class UserRepository {
public void sayHi(String name) {
System.out.println("name="+ name);
}
}
获取Bean:
public class Application {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("Spring-config.xml");
UserRepository userRepository = context.getBean("userRepository", UserRepository.class);
userRepository.sayHi("Repository");
}
}
@Component(组件存储)
使用@Component注解存储Bean对象:
@Component
public class User01 {
public void sayHi(String name) {
System.out.println("name="+name);
}
}
获取Bean:
public class Application {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("Spring-config.xml");
User01 user01 = context.getBean("user01", User01.class);
user01.sayHi("Component");
}
}
@Configuration(配置存储)
使用@Configuration存储Bean对象:
@Configuration
public class UserConfiguration {
public void sayHi(String name) {
System.out.println("name="+name);
}
}
获取Bean:
public class Application {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("Spring-config.xml");
UserConfiguration userConfiguration = context.getBean("userConfiguration", UserConfiguration.class);
userConfiguration.sayHi("Component");
}
}
类注解之间的关系
从上述的代码可以看出来的是,上述的5大类注解的功能是一样。
既然功能是一样,但是还是有5个,不是一个,这是因为当程序员看到了当前类注解之后,就能快速的明白当前类的主要用途。
比如:
@Controller 使用了这个注解的类表示的是业务逻辑层
@Service 使用这个类注解的类表示的服务层
@Repository 使用了这个类注解的类表示数据持久层
@Component 使用了这个类注解的类表示工具类层
@Configuration 使用了这个类注解的类表示配置层
这里我们需要知道程序整体之间的调用逻辑:
5个类注解之间关系则是继承关系:
可以看到 @Controller、@Service、@Repository、@Configuration则是全部继承与@Component。
Bean的命名规则
上述代码我们可以看出,通常我们Bean使用的都是大驼峰命名,而读取的时候则是首字符小写的方式读取。如下图:
如果我们把获取Bean时的名称不按照上述名称的方式进行读取时,就会出现错误。
我们接下来就可以看看Spring存储bean时的命名规则:
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也是按照首字符大写的方式存储了,第二个字符也是大写。
如果不是第一个和第二个都是大写的情况下,就是安装首字符小写的方式进行存储了。
通过方法注解
类注解是添加到某个类上的,而方式注解则是添加到某个方法上的。
需要注意的是,这些类都是在我们前面在spring-config.xml配置文件中配置的扫描包路径下的。
User类:
public class User {
private String name;
@Override
public String toString() {
return "User{" +
"name='" + name + '\'' +
'}';
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
Users类:在Users类中通过方法注解获取User对象。
@Component
public class Users {
@Bean
public User user1() {
User user = new User();
user.setName("张三");
return user;
}
}
将User对象返回并打印信息:
public class Application {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("Spring-config.xml");
User user = context.getBean("user1", User.class);
System.out.println(user.toString());
}
}
上述代码我们可以看出,方法注解是配合类注解一起使用的。如果没有配合类注解的话,是不能正确的存储和获取bean对象的。
从上述代码我们可以看到,在获取Bean时的命名是和方法名一致的。也就是说在获取bean时的默认命名则是方法名。
重命名Bean
方式一
可以通过@Bean注解中直接赋值的方式给对象进行重命名的操作:
@Component
public class Users {
@Bean("u1")
public User user1() {
User user = new User();
user.setName("张三");
return user;
}
}
此时我们就可以使用u1来获取User对象了:
public class Application {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("Spring-config.xml");
User user = context.getBean("u1", User.class);
System.out.println(user.toString());
}
}
方式二
可以通过@Bean注解中的name属性给Bean对象进行重命名操作:
@Component
public class Users {
@Bean("u2")
public User user1() {
User user = new User();
user.setName("张三");
return user;
}
}
public class Application {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("Spring-config.xml");
User user = context.getBean("u2", User.class);
System.out.println(user.toString());
}
}
方式三
通过@Bean注解中的value属性给Bean对象进行重命名操作:
@Component
public class Users {
@Bean(value = "u3")
public User user1() {
User user = new User();
user.setName("张三");
return user;
}
}
public class Application {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("Spring-config.xml");
User user = context.getBean("u3", User.class);
System.out.println(user.toString());
}
}
方式四
通过重命名扩展的方式给Bean设置名称:
@Component
public class Users {
@Bean(value={"u4","u5","u6"})
public User user1() {
User user = new User();
user.setName("张三");
return user;
}
}
通过上述的这种方式设置名称后,就可以通过任意一个名称进行Bean对象的获取了。
注意:在@Bean重命名后,那么默认的根据方法名获取Bean对象的方式就不能使用了。
获取Bean(对象装配)
获取Bean对象也叫对象装配,是把某个对象取出来放在某个类中,也称为对象注入。
对象装配(对象注入)的方式有3种:
- 属性输入
- Setter注入
- 构造方法注入
属性注入
属性注入是通过@Autowired注解实现的。
下面我们按照实际开发的方式进行注入:
整个项目结构。
我们在UserRepository里面假设将获取数据库中的数据,并封装了一个方法进行查询数据库操作。然后将查询到的数据库放入User对象中,进行返回。
然后在UserService里面通过属性注入的方式注入,再调用UserRepository里面的获取数据库的方法。
然后在UserController里面将UserService通过属性注入的方式注入,再调用UserService里面的方法。
整体流程是UserController——>UserService——>UserRepository(进行数据库查询,返回对象)。
UserRepository类具体实现:
@Repository
public class UserRepository {
public User getUser() {
//伪代码 具体业务的实现
User user = new User();
user.setId(1);
user.setName("张三");
return user;
}
}
UserService类具体实现:
@Service
public class UserService {
@Autowired //通过注解的方式将对象注入到这里
private UserRepository userRepository;
public User getUser() { //在通过注入的对象进行业务的实现
return userRepository.getUser();
}
}
UserController类具体实现:
@Controller
public class UserController {
@Autowired //通过属性注入的方式将UserService注入
private UserService userService;
public User getUser() { //在通过注入的对象进行业务的实现
return userService.getUser();
}
}
可以看到比之前下面这种获取对象的方式更加方便了。
// Spring V1.0 依赖查找
ApplicationContext context = new ClassPathXmlApplicationContext("Spring-config.xml");
UserRepository userRepository = context.getBean("userRepository",UserRepository.class);
return userRepository.getUser();
执行代码能成功的获取到从数据库返回的对象。
属性注入也称为依赖注入,我们之前用到的获取Bean对象的方式是属于依赖查找。
依赖注入VS依赖查找:
依赖查找时根据Bean名称的
依赖注入则是先根据类型从容器中获取对象,如果只能获取一个,那么就直接将此对象注入到当前属性上,如果获取到多个对象,才会使用名称进行匹配。
属性注入的优缺点
优点:使用简单
缺点:
- 无法注入通过final修饰的变量
- 只适用于IoC容器
- 更容易违背单一设计原则,因为使用起来简单,更容易注入其他类型的对象
Setter注入
Setter注入和属性Set方法实现类似,只不过在设置Set方法时需要添加上@Autowired注解。
@Controller
public class UserController {
//Setter注入
private UserService userService;
@Autowired
public void setUserService(UserService userService) {
this.userService = userService;
}
/*@Autowired 属性注入
private UserService userService;*/
public User getUser() {
return userService.getUser();
}
}
Setter注入优缺点
优点:完全符合单一设计原则,因为setter注入每次都只能设置一个对象。
缺点:
- 不能注入final修饰的不可变对象
- 注入的对象可以被随时修改
因为final修饰的属性只能在定义的时候进行初始化,或者在构造方法中进行初始化。
注入的对象可以被修改是因为提供了setXXX方法,意味着我随时可以调用这个方法来改变注入对象。
构造方法注入
构造方法注入是在类的构造方法中进行注入:
@Controller
public class UserController {
//构造方法注入
private UserService userService;
@Autowired
public UserController(UserService userService) {
this.userService = userService;
}
/* //Setter注入
private UserService userService;
@Autowired
public void setUserService(UserService userService) {
this.userService = userService;
}*/
/*@Autowired 属性注入
private UserService userService;*/
public User getUser() {
return userService.getUser();
}
}
注意:如果一个类中只有一个构造方法,那么不加@Autowired注解也是可以的,但是如果多个构造方法,就需要加上注解,用来表示到底使用那个构造方法进行注入。
构造方法优点
- 可以注入不可变对象,也就是final修饰的对象,因为final修饰的属性只能在初始化或者构造方法中赋值。
- 注入的对象不会被修改,因为构造方法只执行一次。
- 注入的对象会完成被初始化,因为在对象创建之前,被注入的对象会被完成的初始化,在进行注入。
- 通用性更好,可以适用于非IoC的框架。