Spring管理bean

场景一 : 一个接口的多实例Bean的选择

首先定义一个接口和两个简单的实现类,并演示一下我们通常的用法
一个输出的接口定义如下
public interface IPrint {
void print(String msg);
}

对应给以下两个实现
@Service
public class ConsolePrint implements IPrint {

@Override

public void print(String msg) {

System.out.println("console print: " + msg);

}

}

@Slf4j
@Service
public class LogPrint implements IPrint {
@Override
public void print(String msg) {
log.info(“log print: {}”, msg);
}
}
下面就是我们一般的引用方式

@RestController
@RequestMapping(“/api/{edition}/page”)
public class MyPageHelperController {

@Autowired
private HouseMovingService houseMovingService;
@Autowired
private IPrint print;

@GetMapping(“/a”)
@ResponseBody
public Result a(HttpServletRequest request, HttpServletResponse response) {
try {
print.print(“top”);
return ResultBulider.succuss(“成功”, “ok”);
} catch (Exception e) {
return ResultBulider.faile(ResultCode.DATA_IS_WRONG);
}
}
}

这样启动是会报错的:
Description:
Field print in com.example.demo.controller.MyPageHelperController required a single bean, but 2 were found:

  • consolePrint: defined in file [/Users/zhangjiguo/workspace/myProjects/demo/target/classes/com/example/demo/service/impl/ConsolePrint.class]
  • logPrint: defined in file [/Users/zhangjiguo/workspace/myProjects/demo/target/classes/com/example/demo/service/impl/LogPrint.class]

解决方法:

方法1 :
@Autowired注解是按照类型操作,属性名即为默认的Bean名,如下面的logPrint就是获取beanName=logPrint的bean
图片

方法2:
@Resource(name=xxx) 直接指定Bean的name,来唯一选择匹配的bean,resource是按照名子来操作的。
图片

方法3:
@Primary注解
这个注解就是为了解决当有多个bean满足注入条件时,有这个注解的实例被选中,@Primary注解的使用有唯一性要求:即对应上面的case,一个接口的子类中,只能有一个实现上有这个注解

方法4:
相对高级的解决方案我们提出进一步的期望:就是只修改一个配置就能完成部署环境切换的操作。比如:deploy:
province: beijing
当我们期望把部署环境从北京切换到上海的时候,只需要将上文配置中的beijing 改成 shanghai ,这该怎么实现呢?在北京的实现类上面加上ConditionalOnProperty注解,havingValue的值为beijing@Component
@ConditionalOnProperty(value=“deploy.province”,havingValue = “beijing”)
public class DemoServiceBeijing implements IDemoService {
在上海的实现类上面加上ConditionalOnProperty注解,havingValue的值为shanghai@Component
@ConditionalOnProperty(value=“deploy.province”,havingValue = “shanghai”)
public class DemoServiceShanghai implements IDemoService {
「ConditionalOnProperty注解在这里的作用就是」:读取配置文件发现deploy.province,并将该配置的值与havingValue匹配,匹配上哪一个就实例化哪一个类作为该接口的实现类bean注入到Spring容器中(当然注入过程需要配合@Component注解实现)。

场景二 :SpringBoot 启动加载 CommandLineRunner
在项目中,经常有这样的需求,我们需要在项目启动完立即初始化一些数据(比如缓存等),以便后面调用使用。spring boot可以通过CommandLineRunner接口实现启动加载功能
新建一个Java文件,类需要用Component声明下,需要实现CommandLineRunner接口,然后重写run方法,在run方法内编写需要加载的内容。
代码如下:

package com.study.test.startup;

import org.springframework.boot.CommandLineRunner;
import org.springframework.stereotype.Component;

/**

  • @Description: 初始化启动类

  • @Author: chen

  • @Date: Created in 2019/2/22
    */
    @Component
    public class InitStarter implements CommandLineRunner{

    @Override
    public void run(String… args) throws Exception {
    System.out.println(“CommandLineRunner example start”);
    }
    }

启动项目,运行结果证明:CommandLineRunner会在服务启动之后被立即执行

在项目中,我们可以写一个类继承CommandLineRunner接口,然后在实现方法中写多个需要加载的方法,也可以写多个类继承CommandLineRunner,这些类之间,可以通过order注解(@Order(value=1))实现先后顺序。
例子如下:

图片

图片

总结:
CommandLineRunner会在服务启动之后被立即执行。
CommandLineRunner可以有多个,且多个直接可以用order注解进行排序

场景五 :SpringBoot 启动加载 @PostConstruct
在Spring中,构造器Constructor,@Autowired,@PostConstruct三者的执行顺序,首先说@PostConstruct修饰的方法是在构造函数执行之后执行的,那么构造函数Constructor肯定就优先于@PostConstruct函数执行,那么@Autowired呢,@Autowired是用于注入对象使用的,那么肯定本对象已经有了才能注入依赖的对象,所以构造器Constructor优先于@Autowired执行,接下来就是分析

@Autowired和@PostConstruct,可以看看@PostConstruct的描述,必须在所有的依赖都注入,所以@Autowired是优先于@PostConstruct执行的
所以:Constructor、Autowired、PostContruce三者的执行顺序是:Constructor > @Autowired > @PostConstruct

如果想在生成对象时候完成某些初始化操作,而偏偏这些初始化操作又依赖于依赖注入,那么就无法在构造函数中实现。为此,可以使用@PostConstruct注解一个方法来完成初始化,@PostConstruct注解的方法将会在依赖注入完成后被自动调用。

简单说明:在Spring中一个类作为组件,同时在启动时会纳入Spring IOC中,那么在Servlet容器启动的时候就会生成对象,如果有方法【这些方法有一定的限制,参考@PostConstruct的说明】被@PostConstruct,那么就会自动执行,如果没有作为Spring组件,在Servlet容器启动时也是不会执行的,那么就只能在创建对象时执行,简单来说就是创建了对象被@PostConstruct修饰的方法才会执行

在Bean的初始化操作中,有时候会遇到调用其他Bean的时候报空指针错误。这时候就可以将调用另一个Bean的方法这个操作放到@PostConstruct注解的方法中,将其延迟执行。

错误示例:
@Component
public class PostConstructTest1 {

    @Autowired
    PostConstructTest2 postConstructTest2;

    public PostConstructTest1() { //由于构造函数先于依赖注入执行,所以这里执行的时候postConstructTest2还没有注入,所以报错
        postConstructTest2.hello();
    }
}

@Component
public class PostConstructTest2 {

    public void hello() {
        System.out.println("hello, i am PostConstructTest2");
    }
}

正确的做法是:

@Component
public class PostConstructTest1 {

    @Autowired
    PostConstructTest2 postConstructTest2;

    public PostConstructTest1() {
//        postConstructTest2.hello();
    }
    @PostConstruct //该注解标注的方法,会在构造函数、依赖注入之后执行,所以正确
    public void init() {
        postConstructTest2.hello();
    }
}

说明一下,@PostConstruct更针对性于当前类文件,而CommandLineRunner更服务于整个项目。所以在我们使用中,可根据自己的使用场景来进行选择用这两种方式来实现初始化。@PostConstruct 先于 CommandLineRunner执行

场景三:SpringBoot 启动加载 @BeanPostProcessor

BeanPostProcessor接口作用是:如果我们需要在Spring容器完成Bean的实例化、配置和其他的初始化前后添加一些自己的逻辑处理,我们就可以定义一个或者多个BeanPostProcessor接口的实现,然后注册到容器中

BeanPostProcessor也称为Bean后置处理器,,作用是在Bean对象在实例化和依赖注入完毕后,在显示调用初始化方法的前后添加我们自己的逻辑。注意是Bean实例化完毕后及依赖注入完成后触发的,是先实例化才到初始化的, 源码如下:

public interface BeanPostProcessor {
Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException;
Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException;
}
其中postProcessBeforeInitialization方法会在每一个bean对象的初始化方法调用之前回调;postProcessAfterInitialization方法会在每个bean对象的初始化方法调用之后被回调。

图片
spring给我们一个给bean加工的机会,比如生成代理对象。还有底层的aop实现也是通过这个方式。

@PostConstruct注解 \ 实现BeanPostProcessor接口 区别:
1、PostConstruct、BeanPostProcessor都是在实例化、依赖注入之后执行的。

2、@PostConstruct函数会在BeanPostProcessor接口的postProcessBeforeInitialization函数之后,postProcessAfterInitialization函数之前执行。

3、而且最大的区别是,BeanPostProcessor接口控制的是更宏观的东西,所有Bean的加载都会执行这个接口的方法,而@PostConstruct只会在你加的Bean里执行。

也就是说,如果你想在加载所有Bean时执行一些东西,就用BeanPostProcessor接口,如果只想在加载某一个bean时执行就在该Bean里加上@PostConstruct注解即可

------ 例如:
@Data
@Builder
@AllArgsConstructor
public class UserInfo {

private Integer age;

private String name;

private Integer sex;

public UserInfo(){
System.out.println(“UserInfo 被实例化”);
}

/**

  • 自定义的初始化方法
    */
    @PostConstruct
    public void start(){
    System.out.println(“User 中自定义的初始化方法”);
    }
    }

@Component
public class MyBeanPostProcessor1 implements BeanPostProcessor {
@Override
@Nullable
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
System.out.println(“bean 初始化之前”);
return bean;
}

@Override
@Nullable
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
System.out.println(“bean 初始化之后”);
return bean;
}
}
上述的执行顺序是:
1、UserInfo 被实例化
2、bean 初始化之前
3、User 中自定义的初始化方法
4、bean 初始化之后

全文重点如下:
1、Resource bean的注入时使用,按照byName自动注入,Resource 默认按名称装配,如果不到与名称匹配的bean,会按类型装配

2、Autowired bean的注入时使用,按照byType自动注入,如果我们想使用按照名称(byName)来装配,可以结合@Qualifier注解一起使用

3、Qualifier Qualifier可以结合Autowired一起使用,@Autowired @Qualifier(“user”)

4、Primary 当Spring容器扫描到某个接口的多个 bean 时,如果某个bean上加了@Primary 注解 ,则这个bean会被优先选用

5、PostConstruct 被注解的方法,在对象加载完依赖注入后执行

6、CommandLineRunner 实现在项目启动后执行的功能

7、ApplicationRunner 实现在项目启动后执行的功能

8、BeanPostProcessor BeanPostProcessor提供了两个接口回调。当BeanPostProcessor的实现类注册到spring ioc容器中,对于改spring ioc容器所创建的每个bean实例在调用其初始化方法调用前将会调用BeanPostProcessor中的postProcessBeforeInitialization方法,而在bean实例初始化方法调用完成后,则会调用BeanPostProcessor中的postProcessAfterInitialization方法spring给我们一个给bean加工的机会,比如生成代理对象。还有底层的aop实现也是通过这个方式

9、Aware 感知的Spring容器的存在,与Spring容器建立联系,使用spring容器内的资源,如BeanNameAware获取容器中bean的名字

---------------------------------------- 重点内容 ------------------------------------------

一、Spring声明一个bean

1、xml配置文件的形式

2、@import

3、@Configuration + @bean

4、@Component 及其子注解(@controller、@service、@Repository)声明

二、Spring创建bean的三种方式:

1、调用构造器创建Bean

2、调用静态工厂方法创建Bean

3、调用实例工厂方法创建Bean

三、Spring可以进行初始化bean的地方

1、构造函数

2、@PostConstruct 构造函数之后,init()方法之前执行

3、init()初始化方法,有两种实现

(1)实现InitializingBean接口中的afterPropertiesSet方法 (推荐使用)

(2)xml中配置init方法

4、BeanPostProcessor 后置处理器,有如下两个方法,分别是前、后执行

(1) postProcessBeforeInitialization

(2) postProcessAfterInitialization

    执行顺序是:构造函数  ->  @Autowired  -> postProcessBeforeInitialization -> @PostConstruct -> InitializingBean -> xml中配置init方法 -> postProcessAfterInitialization

(1)在利用上述方法进行初始化操作的时候要注意执行顺序问题

(2)@PostConstruct 和BeanPostProcessor的区别,BeanPostProcessor接口控制的是更宏观的东西,所有Bean的加载都会执行这个接口的方法,而@PostConstruct只 会在你加的Bean里执行。也就是说,如果你想在加载所有Bean时执行一些东西,就用BeanPostProcessor接口,如果只想在加载某一个bean时执行就在该Bean里加上@PostConstruct注解即可。

(3)BeanPostProcessor接口中两个方法不能返回null,如果返回null那么在后续初始化方法将报空指针异常或者通过getBean()方法获取不到bena实例对象,因为后置处理器从Spring IoC容器中取出bean实例对象没有再次放回IoC容器中

关于工具类静态方法调用@Autowired注入的问题
解决方案如下:

1.对工具类使用@Component 注解

2.@Autowired 注解注入bean

3.@PostConstruct 使用该注解定义init()方法,在方法中给logTool赋值

使用时调用logTool.xxxMapper.method();

@Component  
public class TestUtils {     
@Autowired    
private ItemService itemService;

@Autowired    
private ItemMapper itemMapper;
          
public static TestUtils testUtils; 
         
@PostConstruct     
public void init() {
testUtils = this;     }          
 //utils工具类中使用service和mapper接口的方法例子,用"testUtils.xxx.方法" 就可以了    
public static void test(Item record){         testUtils.itemMapper.insert(record);
testUtils.itemService.queryAll();
     }
 }

或者

@Component
public class MyUtil {

    private static UserServiceImpl myuserService;

    @Autowired
    private UserServiceImpl userService;

    @PostConstruct //该注解标注的方法,会在构造函数、依赖注入之后执行,所以正确
    public void init() {
        myuserService = userService;
    }

    public static String a1(){
        System.out.println("--- 2 ---");
        myuserService.aa();;
        return "guo";
    }
}

总结:spring注入是在容器中实例化对象的,而且静态是优先于对象存在的,因此直接在静态方法中调用注入的静态变量实际上是为null的,sping中若是要对静态变量进行注入只能是变相、间接的来完成。


Bean的注册方式有三种

  • XML配置文件的注册方式
  • Java注解的注册方式
  • JavaAPI的注册方式

XML 配置文件注册方式

<bean id="person" class="org.springframework.beans.Person">
   <property name="id" value="1"/>
   <property name="name" value="Java"/>
</bean>

Java 注解注册方式

@Component
public class Person {
   private Integer id;
   private String name
   // 忽略其他方法
}

也可以使用 @Bean 注解方式来注册 Bean,代码如下:

@Configuration
public class Person {
   @Bean
   public Person  person(){
      return new Person();
   }
   // 忽略其他方法
}

@Configuration可以理解为XML配置里的标签,而 @Bean 可理解为用 XML 配置里面的 标签。

Java API 注册方式
使用 BeanDefinitionRegistry.registerBeanDefinition() 方法的方式注册 Bean,代码如下:

public class CustomBeanDefinitionRegistry implements BeanDefinitionRegistryPostProcessor {
	@Override
	public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
	}
	@Override
	public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {
		RootBeanDefinition personBean = new RootBeanDefinition(Person.class);
		// 新增 Bean
		registry.registerBeanDefinition("person", personBean);
	}
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值