数据存储和文件下载异步遇到的问题之注解不生效
初始设计时,直接将需要的Bean进行了@AutoWired注解,yml配置文件也进行了@Value("${isAsync}")注解,但进行调试的发现相应的Bean和读取配置的属性全部返回null,在查询相应的资料后得到解决。接下来分析原因。
1.读取配置的方式
1.@Value的方式
@Value("${async.executor.thread.core_pool_size}")
private int corePoolSize;
这种生效需要什么条件呢?
需要将类注册在spring的上下文中,即在springboot实现自动装配的注解@SpringBootApplication中可以看到有三个核心的注解:
@Configuration
、@EnableAutoConfiguration
、@ComponentScan
注解的集合。根据 SpringBoot 官网,这三个注解的作用分别是:
-
@EnableAutoConfiguration
:启用 SpringBoot 的自动配置机制 -
@Configuration
:允许在上下文中注册额外的 bean 或导入其他配置类 -
@ComponentScan
: 扫描被@Component
(@Service
,@Controller
)注解的 bean,注解默认会扫描启动类所在的包下所有的类 ,可以自定义不扫描某些 bean
也就是说你在启动这个自动装配的时候,需要将类注解为@Component
,@Service
,@Controller
,它才会被扫描到,但由于项目原先设计的类中,我的构造函数为有参,需要注解成bean的话,那么你就需要逐层的对他进行一个注解,不然ioc容器无法找到相应的依赖进行注入,但我这里想的一点就是说,如果你把这个接口注解成bean了,那么在属性注入初始化之后,如果这个接口没有重写对吧,那么误调用了没有重写的接口,肯定是与预期的结果就不一样的,所以在目前的项目中不采用注解成bean的方法。
2.@ConfigurationProperties
这种方式与@Value的原理是一样的,也是需要将类注解为组件bean,才会被扫描,不同的是它只需要在@ConfigurationProperties后加一个prefix前缀,那么对当前的bean都生效,只需要获取对应的value值即可,如下
@Component
@ConfigurationProperties(prefix = "yancloud.dataretention")
public class DataRetentionProperties {
private String storage;
private ThirdApi thirdApi;
3.Environment的方式
@Configuration("MyPropertiesConfig")
public class MyPropertiesConfig {
@Resource
private Environment env;
@PostConstruct
public void setProperties() {
PropertiesUtil.setEnvironment(env);
}
}
@Component("PropertiesUtil")
public class PropertiesUtil {
private static Environment env = null;
public static void setEnvironment(Environment env) {
PropertiesUtil.env = env;
}
public static String getProperty(String key) {
return PropertiesUtil.env.getProperty(key);
}
}
通过@Autowired的方式对env进行初始化,而且那个Environment默认实现的类是AbstractEnvironment,所有environment实现都会继承AbstractEnvironment,而且AbstractEnvironment中就包含了MutablePropertySources(即存储配置文件的对象),所以这就是为什么从environment中可以获取所有配置的原因了。@PostConstruct的作用是让读取配置在bean示例生成之后执行,这点是显然的,bean实例化初始化才会取调用配置文件进行读取配置。不然的话你先读取配置,再实例化,这时候bean的属性就是默认初始化的值而不是读取配置的值了。从注解上来看这个注解也是属于一种后置增强的功能了,实例化后置增强。
具体参考这篇文章:
那么这个时候就可以不需要注解直接调用PropertiesUtil的getProperty()方法取相应的value进行赋值
String isAsync = PropertiesUtil.getProperty("isAsync");
2.@AutoWired注解生效的条件
上面提到由于我们需要改造的类中调用的接口中有的接口没有实现,导致无法进行@Service等注解成一个bean,ioc容器里面(Beanfactory)都不存在这个bean,没有进行管理,所以@Autowired自然不会生效了(为null),因此我们必须从别的方法入手。看如下方法通过实现ApplicationContextAware的接口来实现获取ApplicationContext中的所有bean。
@Component("SpringUtil")
public class SpringUtil implements ApplicationContextAware{
private static ApplicationContext applicationContext;
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
//ioc为空,则将加载的ioc赋值给全局静态ioc
if(SpringUtil.applicationContext == null) {
SpringUtil.applicationContext = applicationContext;
}
}
//获取applicationContext
public static ApplicationContext getApplicationContext() {
return applicationContext;
}
//通过name获取 Bean.
public static Object getBean(String name){
ApplicationContext applicationContext = getApplicationContext();
return applicationContext.getBean(name);
}
//通过class获取Bean.
public static <T> T getBean(Class<T> clazz){
return getApplicationContext().getBean(clazz);
}
//通过name,以及Clazz返回指定的Bean
public static <T> T getBean(String name,Class<T> clazz){
return getApplicationContext().getBean(name, clazz);
}
}
为什么重写AppplicationContextAware就可以实现beanFactory无法实例化的bean呢?
ApplicationContext的BeanFactory 的子类, 拥有更强大的功能,ApplicationContext可以在服务器启动的时候自动实例化所有的bean,而 BeanFactory只有在调用getBean()的时候才去实例化那个bean, 这也是我们为什么要得到一个ApplicationContext对象, 事实上Spring相关的web应用默认使用的是ApplicationContext对象去实例化bean, 换一句话说, 在服务器启动的时候,Spring容器就已经实例化好了一个ApplicationContext对象,所以我们要在老的代码里尝试去获取这个对象。 但是如何才能得到一个ApplicationContext对象呢?方法很多,最常用的办法就是用ClassPathXmlApplicationContext, FileSystemClassPathXmlApplicationContext, FileSystemXmlApplicationContext 等对象去加载Spring配置文件,这样做也是可以, 但是在加载Spring配置文件的时候,就会生成一个新的ApplicaitonContext对象而不是Spring容器帮我们生成的哪一个, 这样就产生了冗余, 所以我们在这里不采用这种加载文件的方式,我们使用ApplicationContextAware让Spring容器传递自己生成的ApplicationContext给我们, 然后我们把这个ApplicationContext设置成一个类的静态变量, 这样我们就随时都可以在老的代码里得到Application的对象了。
3.一点扩展
Spring中@Value中#和$有什么区别?
@Value的值有两类:
① ${ property : default_value }
② #{ obj.property? : default_value }
@Value("#{}") 表示SpEl表达式通常用来获取bean的属性,或者调用bean的某个方法。当然还有可以表示常量,而@Value("${}")是用来读取yml配置文件对应的value值,通俗的讲一个是到内部找bean的属性值,另一个是取外部配置找key对应的value