1.注解开发的好处
无需编写xml文件,通过注解全部配置,可以简化工程,但是存在对第三方资源配置时比较复杂的问题(如数据库连接池)。
public class JDBCConfig {
//静态 非静态没有任何影响
@Bean("dataSource")
public DruidDataSource getDataSource(){
DruidDataSource ds = new DruidDataSource();
ds.setDriverClassName("");
ds.setUrl("");
ds.setUsername("");
ds.setPassword("");
return ds;
}
}
因为我们没法深入Druid的代码,去使用properties的方式给这四个字断进行赋值。
2.Spring常用注解
1.对于注解的使用,我们需要进行开启,也就是让Spring扫描我们加了注解的类,让它帮我们造那些类的实例,所以要制定Spring扫描哪些类,也就是指定Spring扫描的包:
<context:component-scan base-package="com.example"/>
但在这里还是用到配置文件了,我们也可以完全不用到配置文件,使用配置类+纯注解的方式消除配置文件,通过@Configuration来指定配置文件所在的类,@ComponentScan来指定扫描的包。
@Configuration
@ComponentScan("com.example")
public class SpringConfig {
}
此时若要加载这个配置类,也不能再使用ClassPathXmlApplicationContext,而需要用AnnotationConfigApplicationContext来进行加载,加载内容填配置类的字节码即可。
ApplicationContext ac =
new AnnotationConfigApplicationContext(SpringConfig.class);
若出现多个配置类,可以让一个配置类为主类,其它配置类通过@Import的方式引入,@Import仅可写一次,若有多个以数组方式给出({a1.class,a2.class....}),如JDBC数据库池配置引入:
public class JDBCConfig {
//静态 非静态没有任何影响
@Bean("dataSource")
public DruidDataSource getDataSource(){
DruidDataSource ds = new DruidDataSource();
ds.setDriverClassName("");
ds.setUrl("");
ds.setUsername("");
ds.setPassword("");
return ds;
}
}
@Configuration
@ComponentScan("com.example")
@Import(JDBCConfig.class)
public class SpringConfig {
}
当然,也可以没有主从关系的引入,都通过类来加载,加载顺序按填入参数顺序:
ApplicationContext ac =
new AnnotationConfigApplicationContext(SpringConfig.class, JDBCConfig.class);
所以对于第三方配置的bean,无需使用@Component的方式进行依赖注入,直接以配置的方式被导入即可。
2.声明一个类为bean,让其能够被Spring的IoC容器管理,通过注解:@Component,@Controller,@Service,@Repository其中之一即可,不同的名字仅代表不同含义,功能相同,只要将其定义在类上方即可,同时我们还可以给其起id,根据需求,不一定要有:
@Component("userService")
@Scope("singleton")
public class UserServiceImpl implements UserService
bean的作用域通过@Scope注解声明,属性为单例与非单例,默认单例。
3.bean的声明周期随作用域不同而不同,对于初始化方法与销毁方法的注解声明使用@PostConstruct与@PreDestory(仅在上下文关闭时生效):
@PostConstruct
public void init(){
System.out.println("init");
}
@PreDestroy
public void destory(){
System.out.println("destory");
}
4.使用bean加载第三方资源,此时@Bean作为一个方法注解加在获取第三方资源的方法上,可以起个id,该方法的返回值作为spring管理的bean
public class JDBCConfig {
//静态 非静态没有任何影响
@Bean("dataSource")
public DruidDataSource getDataSource(){
DruidDataSource ds = new DruidDataSource();
ds.setDriverClassName("");
ds.setUrl("");
ds.setUsername("");
ds.setPassword("");
return ds;
}
}
5.bean的非引用与引用类型注入
此时由于注解可以加到当前类中,所以不再受私有变量的影响,不需要set方法了。
对非引用类型使用@Value注解:
@Value("3")
private int number;
对于引用类型使用@Autowired与@Qualifier,对于@Qualifier如果不存在同类型冲突的问题不需要使用,@Qualifier主要根据id去唯一匹配类型。
//required代表是否允许为空,默认为false
@Autowired(required = false)
//@Qualifier("userDao")
private UserDao userDao;
@Autowired的匹配方式为默认按照类型装配,所以不需要给被装配类型起别名,正常情况也不需要使用@Qualifier指定,但是若存在类型相同的问题,此时会根据变量名(变量名与@Component时声明的id)进行匹配。
若不给id,并且出现多类型相同的问题,那么此时可以用@Primary指明,首要配对哪个。
//先配对
@Primary
@PropertySource("classpath:jdbc.properties")
public class UserDaoImpl implements UserDao
@Component
@PropertySource("classpath:jdbc.properties")
public class UserDaoImpl2 implements UserDao
6.Properties文件加载
在需要使用的类上方使用@PropertySource加载,且加载一次,全局通用,并且不能用通配符的形式。加载完成后,使用注入的方式注入即可。
@Component
@Primary
//还可以指定找不到怎么样
@PropertySource(value = "classpath:jdbc.properties",ignoreResourceNotFound = false)
public class UserDaoImpl implements UserDao {
@Value("${jdbc.userName}")
private String userName;
@Value("${jdbc.password}")
private String password;
@Override
public void save() {
System.out.println("dao saving"+userName+password);
}
}
3.bean的加载控制
加载控制的目的是控制类的加载顺序。
我们可以通过@DependsOn的方式来实现加载顺序控制,它是使被注解的类OR方法在指定bean加载完毕后加载。@Order是加在类上的注解,它通过数值的大小来控制类的加载顺序。@Lazy是加在类或者方法上的注解,控制加载时机,延迟加载。
@DependsOn的一个应用场景,比如有个消息发布与订阅的服务,此时应该先开启订阅,再开启发布,这样才能不丢失数据。
@Lazy的一个应用场景,程序出现灾难后对应的应急预案处理是启动容器时的加载时机。
@Order,可以指定先加载系统级配置。
4.案例2
将案例1中的xml配置文件方式修改为注解方式。
public class JDBCConfig {
@Value("${jdbc.driver}")
private String JDBCDriver;
@Value("${jdbc.url}")
private String Url;
@Value("${jdbc.username}")
private String Username;
@Value("${jdbc.password}")
private String Password;
@Bean
public DruidDataSource getDruidDataSource(){
DruidDataSource dds = new DruidDataSource();
dds.setDriverClassName(JDBCDriver);
dds.setUrl(Url);
dds.setUsername(Username);
dds.setPassword(Password);
return dds;
}
}
public class MybatisConfig {
//这里很重要
//注意dataSource要写在参数里,由Spring装配
@Bean
public SqlSessionFactoryBean getSqlSessionFactoryBean(@Autowired DataSource dataSource){
SqlSessionFactoryBean ssfb = new SqlSessionFactoryBean();
ssfb.setDataSource(dataSource);
return ssfb;
}
@Bean
public MapperScannerConfigurer getMapperScannerConfigurer(){
MapperScannerConfigurer mscf = new MapperScannerConfigurer();
mscf.setBasePackage("com.example.dao");
return mscf;
}
}
@Configuration
@ComponentScan("com.example")
@PropertySource("classpath:jdbc.properties")
@Import({JDBCConfig.class,MybatisConfig.class})
public class SpringConfig {
}
也就是把之前在applicationContext.xml中的配置放入了java代码中。
5.IoC底层核心原理
核心架构:BeanFactory,AutowireCapableBeanFactory,ListableBeanFactory。
1.组件扫描器,我们可以根据需求来加载有必要的bean,排除指定bean。
我们可以通过配置文件的方式也可以通过注解的方式。
配置文件的方式:
<bean id="filter" class="com.example.config.filter.MyTypeFilter"/>
<context:component-scan base-package="com.example" >
<context:exclude-filter type="custom" expression="filter"/>
<!-- <context:include-filter type="" expression=""/>-->
</context:component-scan>>
注解的方式:
@ComponentScan(value = "com.example",
excludeFilters = {@ComponentScan.Filter(
type = FilterType.CUSTOM,
classes = MyTypeFilter.class)})
type指代过滤器的类型,然后根据指定的类型,指定指定的过滤:
public enum FilterType {
ANNOTATION,
ASSIGNABLE_TYPE,
ASPECTJ,
REGEX,
CUSTOM;
private FilterType() {
}
}
若选择自定义过滤器,则可以选择CUSTOM,自定义过滤器需要继承TypeFilter这个注解:
public class MyTypeFilter implements TypeFilter {
@Override
public boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory) throws IOException {
ClassMetadata classMetadata = metadataReader.getClassMetadata();
String className = classMetadata.getClassName();
if("com.example.service.impl.AccountServiceImpl".equals(className)){
return true;
}
return false;
}
}
通过元数据可以获得注解的bean的名称,若match返回为true则被过滤,反之则不。
2.导入器,在需要配置大量bean的时候,可以通过导入器指定要导入的bean,从而不用对bean加@Component注解或xml配置。自定义导入器需要继承自ImportSelector,最后通过在SpringConfig中使用@Import导入配置。
public class MyImportSelector implements ImportSelector {
@Override
public String[] selectImports(AnnotationMetadata annotationMetadata) {
ResourceBundle rb = ResourceBundle.getBundle("import");
String className = rb.getString("className");
return new String[]{className};//这个返回的字符串数组就是要导入bean的全类名
}
}
并且为了解耦合,我们可以将bean全类名的获取改到从配置文件获取。
3.注册器,之前我们对于bean的注册,要么是通过在xml文件中配置扫描,要么在配置类中使用注解配置扫描,我们也可以自定义注册器来实现,在定义完注册器后,可以将其在配置类中通过@Import引入,自定义注册器需要实现ImportBeanDefinitionRegistrar。
public class MyImportBeanDefinitionResgistrar implements ImportBeanDefinitionRegistrar {
@Override
public void registerBeanDefinitions(AnnotationMetadata annotationMetadata, BeanDefinitionRegistry beanDefinitionRegistry) {
ClassPathBeanDefinitionScanner scanner = new ClassPathBeanDefinitionScanner(beanDefinitionRegistry,false);
scanner.addIncludeFilter(new MyTypeFilter());
scanner.scan("com.example");
}
}
在这其中也可以指定过滤器,因为原本若通过注解指定过滤器就是在扫描注解中。
4.bean初始化过程解析:
Spring通过工厂方法+文件的方式实现解耦合,所以在正式创建bean之前会创建工厂类,通过工厂创建bean。
我们可以通过自定义BeanFactoryPostProcessor类的子类对象来解决bean工厂创建后,子类创建前的处理,仅执行一次,实现好通过import引入。
public class MyBeanFactoryPostProcessor implements BeanFactoryPostProcessor {
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory configurableListableBeanFactory) throws BeansException {
System.out.println("处理....");
}
}
我们可以通过BeanPostProcessor来解决所有bean初始化之前的统一动作,用于对bean创建前业务的处理,每个bean创建都会处理,但记住系统的bean是在一开始全部初始化的,实现好也通过import引入。
public interface BeanPostProcessor {
@Nullable
default Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
return bean;
}
@Nullable
default Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
return bean;
}
}
我们可以通过InitializingBean来解决每个bean创建前个性化的动作,不是每个bean都统一处理,每个bean自己实现InitializingBean接口。
@Service
public class AccountServiceImpl implements AccountService, InitializingBean {
@Override
public void afterPropertiesSet() throws Exception {
System.out.println("account的个性化");
}
}
5.繁杂bean的初始化是通过FactoryBean来解决,达到简化操作的目的,比如SqlSessionFactoryBean,而BeanFactory是Spring Bean容器的顶层接口,定义了Bean的相关操作。