主要记录一直以来都没学明白的一些问题
Bean到底是什么?
其实并不需要理解Bean是什么,只要知道它是Spring实现自动注入生成对象过程中,必须使用的一个组件,这样就可以了。Spring在实例化对象的时候,不止需要一个类,还需要将这个类配置为Bean才可以,通常情况下Spring容器中管理着许多Bean。
将类配置为Bean通常有三种方式: xml、注解、Java Bean
xml是比较早的配置方式,其灵活且复杂,下面配置了两个Bean。
<?xml version="1.0" encoding="UTF-8" ?>
<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-3.0.xsd">
<!--其中id是bean名称,class是bean名称-->
<bean id="car" class="com.yf.simple.Car"></bean>
<bean id="boss" class="com.yf.simple.Boss"></bean>
</beans>
我们都在微博上@过某某,对方会优先看到这条信息,并给你反馈,那么在Spring中,你标识一个@符号,那么Spring就会来看看,并且从这里拿到一个Bean(注册)或者给出一个Bean(使用)
注解的配置方式是现在最主流的使用方式,SpringBoot项目通常也是这么使用。可以看到,使用@Component
注解在UserDao
类声明处对类进行标注,它可以被Spring容器识别,Spring容器自动将POJO转换为容器管理的Bean
package com.yf.anno;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Repository;
@Component("userDao")
public class UserDao {
}
这段代码等效于xml
<bean id="userDao" class="com.yf.anno.UserDao"/>
可以看出,注解的写法更容易理解。使用xml配置的方式,导致类的声明与Bean的声明过程是分开的;而使用注解的方式,只需要在类上加一个注解就可以同时将该类声明为Bean了。
除了@Component
以外,Spring提供了3个功能基本和@Component
等效的注解,它们分别用于对DAO、Service及Web层的Controller进行注解,所以也称这些注解为Bean的衍型注解,之所以要在@Component之外提供这三个特殊的注解,是为了让注解类本身的用途清晰化,此外Spring将赋予它们一些特殊的功能。
注解 | 说明 |
---|---|
@Component |
最普通的组件,可以被注入到spring容器进行管理 |
@Repository |
作用于持久层,在MyBatis时,也可以使用@Mapper |
@Service |
作用于业务逻层 |
@Controller |
作用于变现层(SpringMVC注解) |
Spring提供了一个context的命名空间,它提供了通过扫描类包以应用注解定义Bean的方式,只有在命名空间内的类标注了相关注解,Spring才会将其扫描为Bean。命名空间可以自己配置。
<?xml version="1.0" encoding="UTF-8" ?>
<!--①声明context的命名空间-->
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-3.0.xsd"
>
<!--②扫描类包以应用注解定义的Bean-->
<context:component-scan base-package="com.yf.anno"/>
<bean class="com.yf.anno.LogonService"></bean>
</context:component-scan -->
</beans>
@ComponentScan
注解声明@ComponentScan(scanBasePackages = "com.yf.anno.LogonService")
public class BeanTestApplication {
public static void main(String[] args) {
}
}
ps: SpringBoot默认会扫描基类包里的所有类
JavaBean的配置方式也是采用两个注解,即@Configuration
和@Bean
.
@Configuration
作用于类上,在普通的实体类中只要标注@Configuration注解,就可以为Spring容器提供Bean定义的信息@Bean
作用于类方法,每个标注了@Bean的类方法都相当于提供了一个Bean的定义信息。package com.yf.conf;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
//①将一个POJO标注为定义Bean的配置类
@Configuration
public class AppConf {
//②以下两个方法定义了两个Bean,以提供了Bean的实例化逻辑
@Bean
public UserDao userDao(){
return new UserDao();
}
@Bean
public LogDao logDao(){
return new LogDao();
}
//③定义了logonService的Bean
@Bean
public LogonService logonService(){
LogonService logonService = new LogonService();
//④ 将②和③处定义的Bean注入到LogonService Bean中
logonService.setLogDao(logDao());
logonService.setUserDao(userDao());
return logonService;
}
}
①处: 在AppConf类的定义处标注了@Configuration注解,说明这个类可用于为Spring提供Bean的定义信息。
类的方法处可以标注@Bean注解,Bean的类型由方法返回值类型决定,名称默认和方法名相同,也可以通过入参显示指定Bean名称,如@Bean(name="userDao").直接在@Bean所标注的方法中提供Bean的实例化逻辑。
在②处userDao()和logDao()方法定义了一个UserDao和一个LogDao的Bean,它们的Bean名称分别是userDao和logDao。在③处,又定义了一个logonService的Bean,并且在④处注入②处所定义的两个Bean。
等效于xml声明了
<bean id="userDao" class="com.yf.anno.UserDao"/>
<bean id="logDao" class="com.yf.anno.LogDao"/>
<bean id="logService" class="com.yf.conf.LogonService" p:logDao-ref="logDao" p:userDao-ref="userDao"/>
Spring注入就是将具体信息注入到Bean里,从而生成对象。
属性注入即通过setXxx()方法注入Bean的属性值或依赖对象,由于属性注入方式具有可选择性和灵活性高的优点,因此属性注入是实际应用中最常采用的注入方式。
属性注入要求Bean提供一个默认的构造函数,并为需要注入的属性提供对应的Setter方法。Spring先调用Bean的默认构造函数实例化Bean对象,然后通过反射的方式调用Setter方法注入属性值。
<bean id="myCar" class="cn.yf.pojo.Car">
<!-- 通过constructor-arg的name属性,指定构造器参数的名称,为参数赋值 -->
<constructor-arg name="speed" value="100" />
<constructor-arg name="price" value="99999.9"/>
</bean>
<bean id="user" class="cn.yf.pojo.User">
<property name="name" value="aaa" />
<property name="age" value="123" />
<!-- car是引用类型,所以这里使用ref为其注入值,注入的就是上面定义的myCar
基本数据类型或Java包装类型使用value,
而引用类型使用ref,引用另外一个bean的id
-->
<property name="car" ref="myCar" />
</bean>
使用构造方法注入的前提是Bean必须提供带参数的构造函数。
<bean id="myCar" class="cn.yf.pojo.Car">
<!-- 通过constructor-arg的name属性,指定构造器参数的名称,为参数赋值 -->
<constructor-arg name="speed" value="100" />
<constructor-arg name="price" value="99999.9"/>
</bean>
静态工厂和实例工厂。。。。 待补充。。。。
使用注解的方式为Bean注入属性值
@Autowired
或者@Resource
这两个注解进行依赖注入@Value
注解。位置不同
@Resourse
是javax.annother
包提供的一个注解,是Java EE
的方法,属于标准注解,但Spring也支持该注解的导入; 而@Autowired
是Spring
提供的关键字。
注入方式不同
@Autowired
是byType
的,即根据类型注入如果存在多个同样类型的Bean
,就会报出BeanCreationException
的错误,要解决这个问题,使用@Qualifier
注解,@Qualifier("classname")
来让Spring根据Bean的名称来进行装配。
//通常是实现一个接口的两个类
//第一个
@Service
public class UserServiceImpl1 implements UserService {
}
//第二个
@Service
public class UserServiceImpl2 implements UserService {
}
//此时@Autowired按类型注入就不知道该注入哪个类了,因为UserServiceImpl1和UserServiceImpl2都是UserService接口的实现类,所以需要用@Qualifier指定一下类名
@RestController
public class UserController {
@Autowired
@Qualifier("UserServiceImpl2")
UserService userService;
}
而@Resourse
关键字是byName
的,即根据名称注入,如果没有在使用@Resource
时指定Bean的名字,同时Spring容器中又没有该名字的Bean,这时@Resource
就会退化为@Autowired
即按照类型注入,这样就有可能违背了使用@Resource
的初衷。所以建议在使用@Resource
时都显示指定一下Bean
的名字@Resource(name="xxx")
下面再继续介绍一些常用注解
注解 | 说明 |
---|---|
@Configration |
Java Config配置文件 |
@Import |
手动将类导入Spring容器,以生成Bean |
@Conditional |
条件注解,通常作用于方法,方法返回true则生成组件,返回false则不生成 |
package com.example.beantest.service;
//注意:这里没有使用@Service注解
public class Service1 {
}
//----------
package com.example.beantest.service;
//注意:这里没有使用@Service注解
public class Service2 {
}
package com.example.beantest.service;
//注意:这里没有使用@Service注解
public class Service3 {
}
@Configuration //定义Java Config
@Import({Service1.class, Service2.class})
//此处使用@Import注解,将Service1和Service2两个类导入Spring容器(即ApplicationContext),生成Bean
public class BeanConfig {
//配置一些SpringBean
@Conditional(MyBeanCondition.class)
//MyBeanCondition是条件配置类(见下文3.),需要继承Condition接口,
//重写接口的matches()方法,调用返回true则生成Bean,false则不生成
@Bean
public Service3 getService3(){
return new Service3();
}
}
//条件配置,继承Condition接口,重写matches方法
public class MyBeanCondition implements Condition {
@Override
public boolean matches(ConditionContext conditionContext, AnnotatedTypeMetadata annotatedTypeMetadata) {
if(new Random().nextInt(10) >= 5){
return true;
}
return false;
}
}
@SpringBootApplication
public class BeanTestApplication {
public static void main(String[] args) {
ConfigurableApplicationContext context = SpringApplication.run(BeanTestApplication.class, args);
Service1 service1Bean = context.getBean(Service1.class);
System.out.println(service1Bean);
Service2 service2Bean = context.getBean(Service2.class);
System.out.println(service2Bean);
Service3 service3Bean = context.getBean(Service3.class);
System.out.println(service3Bean);
}
}
结果
...
2021-08-01 16:29:30.660 INFO 7912 --- [ main] c.example.beantest.BeanTestApplication : Started BeanTestApplication in 1.566 seconds (JVM running for 2.566)
com.example.beantest.service.Service1@5b6e8f77
com.example.beantest.service.Service2@41a6d121
Exception in thread "main" org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type 'com.example.beantest.service.Service3' available
at org.springframework.beans.factory.support.DefaultListableBeanFactory.getBean(DefaultListableBeanFactory.java:351)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.getBean(DefaultListableBeanFactory.java:342)
at org.springframework.context.support.AbstractApplicationContext.getBean(AbstractApplicationContext.java:1172)
at com.example.beantest.BeanTestApplication.main(BeanTestApplication.java:22)
这部分主要是Spring IoC注解的使用,进行注解汇总
@Configuration把一个类作为一个IoC容器,它的某个方法头上如果注册了@Bean,就会作为这个Spring容器中的Bean。
@Lazy(true) 表示延迟初始化
@Service用于标注业务层组件
@Controller用于标注控制层组件(如struts中的action)
@Repository用于标注数据访问组件,即DAO组件。
@Component泛指组件,当组件不好归类的时候,我们可以使用这个注解进行标注。
@Scope用于指定scope作用域的(用在类上)
@PostConstruct用于指定初始化方法(用在方法上)
@PreDestory用于指定销毁方法(用在方法上)
@Required用于bean属性的 setter 方法,表明该属性必须有值才可以初始化bean
@Resource 默认按名称装配,当找不到与名称匹配的bean才会按类型装配。
@DependsOn:定义Bean初始化及销毁时的顺序
@Primary:自动装配时当出现多个Bean候选者时,被注解为@Primary的Bean将作为首选者,否则将抛出异常。(只对接口的多个实现生效)
@Autowired 默认按类型装配,如果我们想使用按名称装配,可以结合@Qualifier注解一起使用
@Autowired @Qualifier(“personDaoBean”) 存在多个实例配合使用