Spring IOC在应用程序启动的时候创建并初始化所有单例bean。这种默认行为可以确保立即捕获任何可能的错误。
此功能非常适合避免任何运行时的错误,但是在一些场景中,我们希望Spring IOC在启动时不创建bean,而是在应用程序请求时创建它。
Spring 中@Lazy注解可用于防止单例bean在启动时预初始化。
@Lazy的作用
@Lazy注解只对单例有用(懒加载是针对单实例来说的),它让bean在Spring启动时不会加载到容器中,只有在代码第一次被调用时才创建一次,
加载到容器中,以后再次调用时,不会再次创建实例,而是直接从容器中拿。
在用Spring开发时,我们常用修饰bean的注解都是单例的,比如@Component、@Service、@Bean。这些单例默认在Spring启动时加载到容器中,以后再调用时不会重新创建,而是直接从容器中拿。
例子1:利用@ configuration + @Bean创建单例
(1)先创建简单的实体类Use、Person
@Data
public class User {
private String name;
private int age;
public User(){
System.out.println("初始化:User()");
}
public User(String name, int age) {
System.out.println("初始化:User(String name, int age)");
this.name = name;
this.age = age;
}
}
@Data
public class Person {
private String name;
private int age;
public Person(){
System.out.println("初始化:Person()");
}
public Person(String name, int age) {
System.out.println("初始化:Person(String name, int age)");
this.name = name;
this.age = age;
}
}
(2)利用@Configuration+@Bean创建单例
/**
* 默认@Configuration下@Bean定义的是单实例
* 也就是Spring启动时,就已经加载到Spring容器中了
* @Lazy是延迟加载,只有第一次调用Bean时才加载到容器中
*/
@Configuration
public class MyConfig {
@Lazy
@Bean(name = "myBeanPerson")
public Person myBeanPerson(){
return new Person("zoe", 20);
}
@Bean(name = "myBeanUser")
public User myBeanUser(){
return new User("top", 21);
}
}
运行项目,只有User的构造器的输出,说明只创建了User的实例
初始化:User(String name, int age)
(3)我们可以选择在bean级别定义/声明@Lazy注解。@Lazy存在且在用@Lazy注解的@Configuration类中的@Bean方法上为false,这表示覆盖’默认懒加载’行为和bean预初始化。
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Lazy;
@Configuration
public class MyConfig {
@Lazy
@Bean(name = "myBeanPerson")
public Person myBeanPerson(){
return new Person("king", 21);
}
@Bean(name = "myBeanUser")
@Lazy(false)
public User myBeanUser(){
return new User("king", 22);
}
}
运行项目,只有User的构造器的输出,说明只创建了User的实例,也说明@Lazy通过配置false,使得延迟加载失效了
(4)@Autowired导致@Lazy失效的解决方案
@Service
public class BaseServiceImpl implements BaseService {
public BaseServiceImpl(){
System.out.println("--- baseServiceImpl ---");
}
@Override
public void myBase() {
}
}
上述代码在工程启动的时候,会打印:— baseServiceImpl —,说明启动的时候创建了实例,并放入容器中。
如果在类上添加@Lazy,如下图:
@Service
@Lazy
public class BaseServiceImpl implements BaseService {
public BaseServiceImpl(){
System.out.println("--- baseServiceImpl ---");
}
@Override
public void myBase() {
}
}
上图中代码,在工程启动的时候,不会执行构造函数,也就不会打印:— baseServiceImpl —,此时只是创建了代理对象,并没有真正的实例化。
原因就在于添加了@Lazy,使得实例化操作延迟了。
但是,如果此时其他地方引用了该,如下图:
@RestController
@RequestMapping("/api/{edition}/page")
public class MyPageHelperController {
@Autowired
private Map<String, IPrint> studentServiceMap;
@Autowired
private BaseService baseService;
@GetMapping("/getStudentName")
public void getStudentById(String studentId, String source) {
try {
//String key = ServiceMapConstants.STUDENT_SERVICE_PREFIX + source;
String key = source;
IPrint teacherService = (IPrint) ServiceBeanContext.getProvider(key);
System.out.println(teacherService);
if (teacherService != null) {
teacherService.print(studentId);
}
} catch (Exception e) {
}
}
}
此时,启动程序的时候,依然会执行构造函数,并打印:— baseServiceImpl —,原因在于MyPageHelperController中引入了BaseService,导致BaseServiceImpl中的延迟加载失效,如果希望此场景下保持延迟加载,需要在MyPageHelperController中继续使用@Lazy,如下图:
@RestController
@RequestMapping("/api/{edition}/page")
public class MyPageHelperController {
@Autowired
private Map<String, IPrint> studentServiceMap;
@Autowired
@Lazy
private BaseService baseService;
@GetMapping("/getStudentName")
public void getStudentById(String studentId, String source) {
try {
//String key = ServiceMapConstants.STUDENT_SERVICE_PREFIX + source;
String key = source;
IPrint teacherService = (IPrint) ServiceBeanContext.getProvider(key);
System.out.println(teacherService);
if (teacherService != null) {
teacherService.print(studentId);
}
} catch (Exception e) {
}
}
}
再启动程序,由于MyPageHelperController中添加了@Lazy,BaseServiceImpl中也添加了@Lazy,此时延迟加载生效,不会执行BaseServiceImpl的构造方法,也就不再打印— baseServiceImpl —
常用注解功能介绍:
@Configuration:配置类注解,类似xml中的注解,生成的配置类bean 实例是代理,执行@bean 的方法之前 先判断单例池中是否已有该对象实例,确保@bean 注解的对象单例,也就是说是针对单例的。
@Bean:跟@Configuration配合使用,标注在方法上,实例化一个bean,默认以方法名称做bean的id,可以指定别名,初始化方法和销毁方法
@Scop:跟@bean配置使用,标注在方法上,调整作用域,指定bean创建的定义
prototype:多实例的:ioc容器启动并不会去调用方法创建对象放在容器中。每次获取的时候才会调用方法创建对象;
singleton:单实例的(默认值):ioc容器启动会调用方法创建对象放到ioc容器中,以后每次获取就是直接从容器 (map.get())中拿
request:同一次请求创建一个实例(web环境下)
session:同一个session创建一个实例(web环境下)
@Lazy:懒加载,配和@Component及其子注解使用,被注解的bean 不是不实例化,而是先创建一个代理bean 注入容器启动不创建对象。第一次使用(获 取)Bean创建对象,并初始化;