Spring存对象
在Spring的创建和使用篇章里,提到了用bean标签来将对象标识到Spring里面,但是这样的方法过于麻烦,下面我们来介绍使用类注解来存储对象。
五大类注解存对象
Spring里面有五大类注解:
@Controller、@Service、@Repository、@Component、@Configuration
在介绍类注解的使用之前,要先说明一下bean的自动命名规则,为后面使用对象做准备。
五大类注解中bean的命名规则
我们在idea中的搜索里,搜索 AnnotationBeanNameGenerator 这个类,里面有一个 buildDefaultBeanName 方法,如下:
在这个红框的方法里面描述了一个命名方式,并且这个方法是在jdk里面的,如下:
字符串为 null 和 空字符串 的情况会返回本身;
当字符串超过1个字符时,如果首字符和第二个字符都为大写,此时返回原字符串;
除上述情况之外,将字符串的首字符变为小写,返回字符串。
总结:如果类名的首字符和第二个字符都为大写,bean的名字为类名;
除此之外,bean的名字为,首字母变小写的类名。
我们用个例子展示一下:
public static void main(String[] args) {
String className1 = "UserName";
String className2 = "UClass";
System.out.println("UserName -> " + decapitalize(className1));
System.out.println("UClass -> " + decapitalize(className2));
}
此时它的输出为:
和我们前面的结论相同。
Content标签
Spring为了提高访问类的效率,提供了一个Content标签(Content标签可以有多个),在标签里面,将使用类注解的类,所对应的包名写入其中,如下:
<?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">
<!-- 在 base-package 里填入包名 -->
<content:component-scan base-package="Demo"></content:component-scan>
</beans>
加入标签后,里面的包及其子包里面的类,只要加了类注解,就都被存储到Spring里面了。
使用类注解存储对象
上述的五个类注解的使用方法相同,此处就展示一个。
//该类属于 Demo 包,和前面的Content标签的包名相对应。
@Controller
public class StudentController {
public void sayHi() {
System.out.println("do StudentController sayHi");
}
}
然后我们使用这个类(此处的使用为复杂版本),bean的名字规范前面已经说过,此处不说明为什么是“studentController”了。
public static void main(String[] args) {
ApplicationContext context =
new ClassPathXmlApplicationContext("spring-config.xml");
StudentController studentController =
context.getBean("studentController", StudentController.class);
studentController.sayHi();
}
输出效果:
输出和我们的预期一样。
不过在使用类注释的时候要注意几个点:
1. 在同一个包下(包括子包),在加类注释时,最好不要有两个类名相同的类,如果一定要有,要在注释后面加上value来区分。
如下:
@Controller(value = "UserController2")
public class UserController {
public void sayHi() {
System.out.println("Demo.Controller -> do UserController sayHi");
}
}
这样在使用该对象时,在id栏填入这个value的值即可。
但是要注意:相同的类在使用时不能导入多个包,要至少有一个是使用包名.类名的方式来获取该类的对象。
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("spring-config.xml");
UserController userController =
context.getBean("userController", UserController.class);
userController.sayHi();
//使用 包名.类名 的方式获取重名的类的对象
Demo.Controller.UserController userController1 =
context.getBean("UserController2", Demo.Controller.UserController.class);
userController1.sayHi();
}
2. bean标签和Content标签可以同时使用。
3. 没有加类注解的类,即使属于Content的包名下,也不会存储到Spring里。
4. 使用了类注解,但是该类不属于Content标签的包名下,也不会存储到Spring里。
五大类注解的区别和规范
我们来看一下这个五个类注解的源码:
@Controller
@Service
@Repository
@Configuration
可以看到 @Controller、@Service、@Repository、@Configuration这四个类注解都实现了@Component 注解。
也就说明,这五个注解的功能其实相差不大,但是为什么要分为五个注解呢?
因为需要类注解标识自己对应的类的功能。
@Controller -- 验证用户请求的正确性(安保)
@Service -- 编排和调度具体的执行方法(分配)
@Repository -- 和数据库做交互
@Component -- 组件,工具类
@Configuration -- 配置项,项目中的一些配置
在JavaEE的标准分层中规定,至少要有三层:
1. 控制层、2. 服务层、3. 数据持久层
如下图:
上述的流程在Java开发手册里面也有说明,和上面基本相同,如下:
总结:每个类注解都可以将对象存储到Spring里,分出五大类注解的原因是为了让程序员更好区分每个类的功能。
使用@Bean去存储对象
和前面的五大类注解不同,@Bean是一个方法注解,当这个方法返回了一个对象,并且该方法使用了@Bean注解,此时这个返回的对象就存储到了Spring里面。
重点一、只有当这个类使用了五大类注解时,里面的@Bean注解才会生效。
如下:
@Controller
public class UserBeans {
@Bean
public User UserById() {
User user = new User();
user.setUid(1);
user.setUsername("张三");
user.setPassword("123456");
user.setAge(18);
return user;
}
}
@Bean的命名规则和五大类注解的命名规则不同,它的bean名字默认是方法名,他还可以起别名/起多个别名,并且起了别名之后就无法使用方法名了。
@Controller
public class UserBeans {
//多个别名 name/value都可以
@Bean(name = {"user1", "u1"})
public User UserById() {
User user = new User();
user.setUid(1);
user.setUsername("张三");
user.setPassword("123456");
user.setAge(18);
return user;
}
//单个别名 name/value都可以
@Bean(value = "user")
public User UserByName() {
User user = new User();
user.setUid(2);
user.setUsername("李四");
user.setPassword("123456");
user.setAge(20);
return user;
}
}
重点二、如果多个类里面有多个方法,他们的返回类型、方法名相同,此时会发生方法覆盖,也就是后存储的类会覆盖掉前一个存储的类,若不想被覆盖就进行@Bean的重命名(起别名)。
如果想控制存储的顺序,可以使用@Order注解
@Order(1)
@Controller
public class UserBeans {
@Bean
public User UserByName() {
User user = new User();
user.setUid(2);
user.setUsername("李四");
user.setPassword("123456");
user.setAge(20);
return user;
}
}
//另一个类中
@Order(10)
@Controller
public class UserBeans2 {
@Bean
public User UserByName() {
User user = new User();
user.setUid(100);
user.setUsername("abcdefg");
user.setPassword("123456");
user.setAge(999);
return user;
}
}
@Order注解中,值越小,存储的优先级越高,也就是说,在上面提到的情况下,Order里面的值越大,该类的方法就可以覆盖掉其他类的方法。
重点三、如果方法里面有参数是无法进行存储的,因此,方法重载也是无法被存储的。
Spring取对象
@Autowired注解
@Autowired用于将对象注入,它有三种注入方式:
属性注入(常用)
使用方法:在属性上面加上进行注入@Autowired注解
被注入的类(后面不再展示):
@Service
public class UserService {
public void sayHi() {
System.out.println("do UserService sayHi");
}
}
使用类:
@Controller
public class UserController {
//属性注入
@Autowired
private UserService userService;
public void sayHi() {
System.out.println("do UserController sayHi");
userService.sayHi();
}
}
不过这个注入方式要注意:不能给被final修饰的属性注入。
如下:
总结:
属性注入的优点:方便快捷。
属性注入的缺点:
1. 无法给不可变(final修饰)对象注入
2. 兼容性差,只适用于IoC容器
3. 有一定风险,由于使用比较简单,所以容易违背 单一设计原则
Setter注入
使用方法:在Setting方法上加 @Autowired 注解。
@Controller
public class UserController {
//setter 注入
private UserService userService;
@Autowired
public void setUserService(UserService userService) {
this.userService = userService;
}
public void sayHi() {
System.out.println("do UserController sayHi");
userService.sayHi();
}
}
同时这个注入方式也是无法给 final 修饰的属性注入的。
Setter注入的优点:符合单一设计原则。
Setter注入的缺点:
1. 无法给不可变类(被final修饰)注入
2. 使用 Setter注入 的对象可能被修改成其他对象。因为Setting是一个方法,它可以在任何时候被调用,然后传入其他的对象。
构造方法注入(Spring官方推荐)
使用方法:在构造方法上加 @Autowired 注解
@Controller
public class UserController {
//构造方法注入
private UserService userService;
@Autowired
public UserController(UserService userService) {
this.userService = userService;
}
public void sayHi() {
System.out.println("do UserController sayHi");
userService.sayHi();
}
}
如果只需要注入一个对象,并且只有一个构造方法的情况下,可以不加 @Autowired 注解
@Controller
public class UserController {
//构造方法注入
private UserService userService;
public UserController(UserService userService) {
this.userService = userService;
}
public void sayHi() {
System.out.println("do UserController sayHi");
userService.sayHi();
}
}
构造方法注入的优点:
1. 可以对不可变对象(被final修饰)进行注入。
因为被final修饰的对象要么直接赋值,要么在构造方法赋值。
2. 不会在其他地方被修改。(构造方法调用一次就会创建一次对象)
3. 可以保证注入的对象被完全初始化。
4. 通用性更好,其他的框架也可以使用
@Resoutce注解
@Resoutce 注解和 @Autowired注解都可以实现对象的注入,但是他们是有一定区别的。
@Resoutce 和 @Autowired 的区别
1. @Autowired注解属于Spring,@Resoutce注解属于jdk。
2. @Autowired注解支持构造方法注入,@Resoutce注解不支持构造方法注入。
3. @Resoutce注解有更多的功能,比如:name、value...,@Autowired注解功能较少。
4. @Autowired是先查找类,再查找名称;@Resoutce是先查找名称,后查找类。