<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd ">
<bean id="cat" class="com.hx.pojo.Cat">bean>
<bean class="com.hx.pojo.Cat">bean>
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">bean>
beans>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
<context:component-scan base-package="com.hx"/>
beans>
使用@Component及其衍生注解@Controller 、@Service、@Repository定义bean
@Service
public class BookServiceImpl implements BookService {
}
使用@Bean定义第三方bean,并将所在类定义为配置类或Bean
@Component
public class DbConfig {
@Bean
public DruidDataSource getDataSource(){
DruidDataSource ds = new DruidDataSource();
return ds;
}
}
Configuration
@ComponentScan("com.hx")
public class SpringConfig {
@Bean
public DruidDataSource getDataSource(){
DruidDataSource ds = new DruidDataSource();
return ds;
}
}
@Configuration配置项如果不用于被扫描可以省略
public class TestDemo {
@Test
public void test01(){
//使用配置类
ApplicationContext context = new AnnotationConfigApplicationContext(SpringConfig.class);
String[] beans = context.getBeanDefinitionNames();
for (String bean : beans) {
System.out.println(bean);
}
}
}
使用FactroyBean接口
补充一个小知识,spring提供了一个接口FactoryBean,也可以用于声明bean,只不过实现了FactoryBean接口的类造出来的对象不是当前类的对象,而是FactoryBean接口泛型指定类型的对象。
如下列,造出来的bean并不是DogFactoryBean,而是Dog。有什么用呢?可以在对象初始化前做一些事情,下例中的注释位置就是让你自己去扩展要做的其他事情的。
public class DogFactoryBean implements FactoryBean<Dog> {
@Override
public Dog getObject() throws Exception {
Dog d = new Dog();
//.........
return d;
}
@Override
public Class<?> getObjectType() {
return Dog.class;
}
@Override
public boolean isSingleton() {
return true;
}
}
proxyBeanMethods属性
前面的例子中用到了@Configuration这个注解,当我们使用AnnotationConfigApplicationContext加载配置类的时候,配置类可以不添加这个注解。但是这个注解有一个更加强大的功能,它可以保障配置类中使用方法创建的bean的唯一性。为@Configuration注解设置proxyBeanMethods属性值为true即可,由于此属性默认值为true。假如将其属性设置为false,则其创造出来的对象就不是同一个了。
@Configuration(proxyBeanMethods = true)
public class SpringConfig33 {
@Bean
public Cat cat(){
return new Cat();
}
}
下面通过容器再调用上面的cat方法时,得到的就是同一个对象了。注意,必须使用spring容器对象调用此方法才有保持bean唯一性的特性。
public class App33 {
public static void main(String[] args) {
ApplicationContext ctx = new AnnotationConfigApplicationContext(SpringConfig33.class);
String[] names = ctx.getBeanDefinitionNames();
for (String name : names) {
System.out.println(name);
}
System.out.println("-------------------------");
SpringConfig33 springConfig33 = ctx.getBean("springConfig33", SpringConfig33.class);
System.out.println(springConfig33.cat());
System.out.println(springConfig33.cat());
System.out.println(springConfig33.cat());
}
}
使用扫描的方式加载bean是开发中常见的bean的加载方式,但是由于扫描的时候不仅可以加载到你要的东西,还有可能加载到各种各样的乱七八糟的东西,万一没有控制好得不偿失了。
所以我们需要一种精准制导的加载方式,使用@Import注解就可以解决你的问题。它可以加载所有的一切,只需要在注解的参数中写上加载的类对应的.class即可。有人就会觉得,还要自己手写,多麻烦,不如扫描好用。对呀,但是他可以指定加载啊,好的命名规范配合@ComponentScan可以解决很多问题,但是@Import注解拥有其重要的应用场景。有没有想过假如你要加载的bean没有使用@Component修饰呢?这下就无解了,而@Import就无需考虑这个问题。
精确制导,注入所需要的Bean。
@Import({Dog.class,DbConfig.class})
public class SpringConfig4 {
}
前面介绍的加载bean的方式都是在容器启动阶段完成bean的加载,下面这种方式就比较特殊了,可以在容器初始化完成后手动加载bean。通过这种方式可以实现编程式控制bean的加载。
public class App5 {
public static void main(String[] args) {
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(SpringConfig.class);
//上下文容器对象已经初始化完毕后,手工加载bean
ctx.register(Mouse.class);
}
}
其实这种方式坑还是挺多的,比如容器中已经有了某种类型的bean,再加载会不会覆盖呢?这都是要思考和关注的问题。慎用。
public class App5 {
public static void main(String[] args) {
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(SpringConfig.class);
//上下文容器对象已经初始化完毕后,手工加载bean
ctx.registerBean("tom", Cat.class,0);
ctx.registerBean("tom", Cat.class,1);
ctx.registerBean("tom", Cat.class,2);
System.out.println(ctx.getBean(Cat.class));
}
}
其实这种加载是会进行覆盖的,谨慎使用。
在方式五种,我们感受了bean的加载可以进行编程化的控制,添加if语句就可以实现bean的加载控制了。但是毕竟是在容器初始化后实现bean的加载控制,那是否可以在容器初始化过程中进行控制呢?答案是必须的。实现ImportSelector接口的类可以设置加载的bean的全路径类名,记得一点,只要能编程就能判定,能判定意味着可以控制程序的运行走向,进而控制一切。
现在又多了一种控制bean加载的方式,或者说是选择bean的方式。
MyImportSelector.java
public class MyImportSelector implements ImportSelector {
@Override
public String[] selectImports(AnnotationMetadata metadata) {
//return new String[0];
//各种条件的判定,判定完毕后,决定是否装载指定的bean
if(metadata.hasAnnotation("org.springframework.context.annotation.Configuration")){
return new String[]{"com.hx.pojo.Cat"};
}
return new String[]{"com.hx.pojo.Dog"};
}
}
SpringConfig3.java
@Configuration
@Import(MyImportSelector.class)
public class SpringConfig3 {
}
public class TestDemo3 {
@Test
public void test01(){
//使用配置类
ApplicationContext context = new AnnotationConfigApplicationContext(SpringConfig3.class);
String[] beans = context.getBeanDefinitionNames();
for (String bean : beans) {
System.out.println(bean);
}
}
}
开发中不可能在spring容器中进行bean的饱和式加载的。什么是饱和式加载,就是不管用不用,全部加载。比如jdk中有两万个类,那就加载两万个bean,显然是不合理的,因为你压根就不会使用其中大部分的bean。
那合理的加载方式是什么?肯定是必要性加载,就是用什么加载什么。继续思考,加载哪些bean通常受什么影响呢?最容易想的就是你要用什么技术,就加载对应的bean。用什么技术意味着什么?就是加载对应技术的类。
所以在spring容器中,通过判定是否加载了某个类来控制某些bean的加载是一种常见操作。下例给出了对应的代码实现,其实思想很简单,先判断一个类的全路径名是否能够成功加载,加载成功说明有这个类,那就干某项具体的工作,否则就干别的工作。
下面代码就是通过反射获取一个类,判断该类是否存在,存在就加载相对应的类,否则就不加载。
public class MyImportSelector implements ImportSelector {
public String[] selectImports(AnnotationMetadata annotationMetadata) {
//return new String[]{"com.xnh.pojo.Cat"};
try {
Class<?> clazz = Class.forName("com.xnh.pojo.Dog");
if(clazz!=null){
return new String[]{"com.xnh.pojo.Cat"};
}
} catch (ClassNotFoundException e) {
return new String[0];
}
return null;
}
}
通过上述的分析,可以看到此类操作将成为开发中的常见操作,于是springboot将把这些常用操作给我们做了一次封装。这种逻辑判定你开发者就别搞了,我springboot信不过你这种新手开发者,我给你封装一下,做几个注解,你填参数吧。
下例使用@ConditionalOnClass注解实现了当虚拟机中加载了com.itheima.bean.Wolf类时加载对应的bean。比较一下上面的代码和下面的代码,有没有感觉很清爽。其实此类注解还有很多。
即com.xnh.bean这个包下是否有Wolf这个类,有就加载下面的Cat类,否则就不加载。
@Bean
@ConditionalOnClass(name = "com.xnh.bean.Wolf")
public Cat tom(){
return new Cat();
}
@ConditionalOnMissingClass注解控制虚拟机中没有加载指定的类才加载对应的bean。
@Bean
@ConditionalOnMissingClass("com.xnh.bean.Dog")
public Cat tom(){
return new Cat();
}
除了判定是否加载类,还可以对当前容器类型做判定,下例是判定当前容器环境是否是web环境。
@Bean
@ConditionalOnWebApplication
public Cat tom(){
return new Cat();
}
当多条件存在时,只有多个条件均满足才会加载。
public class SpringConfig {
@Bean
//@Conditional()
//有Dog这个类就加载下面方法产生的类,建议使用全路径名称书写
//@ConditionalOnClass(name = "com.xnh.pojo.Dog")
//没有Mouse这个类就加载下面方法产生的类
@ConditionalOnMissingClass(value = "com.xnh.pojo.Mouse")
@ConditionalOnNotWebApplication
public Cat Tom(){
return new Cat();
}
}
bean的加载及加载控制已经搞完了,下面研究一下bean内部的事情。bean在运行的时候,实现对应的业务逻辑时有可能需要开发者提供一些设置值,有就是属性了。如果使用构造方法将参数固定,灵活性不足,这个时候就可以使用前期学习的bean的属性配置相关的知识进行灵活的配置了。先通过yml配置文件,设置bean运行需要使用的配置信息。
cartoon:
cat:
name: "图多盖洛"
age: 5
mouse:
name: "泰菲"
age: 1
然后定义一个封装属性的专用类,加载配置属性,读取对应前缀相关的属性值。
@ConfigurationProperties(prefix = "cartoon")
@Data
public class CartoonProperties {
private Cat cat;
private Mouse mouse;
}
最后在使用的位置注入对应的配置即可。
@EnableConfigurationProperties(CartoonProperties.class)
public class CartoonCatAndMouse{
@Autowired
private CartoonProperties cartoonProperties;
}
建议在业务类上使用@EnableConfigurationProperties声明bean,这样在不使用这个类的时候,也不会无故加载专用的属性配置类CartoonProperties,减少spring管控的资源数量。
总结