Spring的注解式编程
配置组件(Configur Components)
@Configuration
把一个类作为一个IOC容器,它的某个方法头上如果注册了@Bean,就会作为这个Spring容器中的Bean
首先创建一个Bean
public class Student {
private int age;
private String name;
public Student() {
}
public Student(int age, String name) {
this.age = age;
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "Student{" +
"age=" + age +
", name='" + name + '\'' +
'}';
}
}
在类上注解@Configruation标志这个类是一个配置类
注意:new Student(12,“student”)在Spring中不是直接使用,而是根据方法的内容用原型模式重新new的对象,这里的声明只是告诉Spring怎么创建对象
@Configuration
public class MyConfig {
@Bean
public Student student(){
return new Student(12,"student");
}
}
使用
public class MyTest {
@Test
public void test(){
ApplicationContext app=new AnnotationConfigApplicationContext(MyConfig.class);
Object bean=app.getBean("student");
System.out.println(bean);
}
}
结果
验证Bean是否是单例的
@Test
public void test(){
ApplicationContext app=new AnnotationConfigApplicationContext(MyConfig.class);
Object bean=app.getBean("student");
Object bean1=app.getBean("student");
System.out.println(bean==bean1);
}
}
结果
这里说明了Spring中创建的student对象时单例的
补充:
Object bean2=app.getBean(Student.class);可以根据类拿到对象
对象的名称拿的是方法名
修改方法名
@Bean
public Student student1(){
return new Student(12,"student");
}
}
Object bean1=app.getBean("student1");
结果可以得出直接使用@Bean配置的对象名使用的是其方法名,我们也可以使用@Bean"自定义名字")去自定义对象名
@ComponentScan
在配置类上添加@ComponentScan注解,该注解会默认扫描该类所在的包下所有的配置类,相当于XML中的<context:component-scan>
使用@ComponentScan可以自定义规则,它的默认规则是扫描使用(@Service、@Controller、@Repostory、@Component)注解的Bean,默认的就不举例了
第一种:
@Configuration
@ComponentScan(value = "com.hyg.project",includeFilters ={@ComponentScan.Filter(type = FilterType.ANNOTATION,value = Controller.class)} )
public class MyConfig1 {
}
@Controller
public class MyController {
}
public class MyTest {
@Test
public void test(){
ApplicationContext app=new AnnotationConfigApplicationContext(MyConfig1.class);
String[] beanDefinitionNames = app.getBeanDefinitionNames();
System.out.println(Arrays.toString(beanDefinitionNames)
.replaceAll("\\[|\\]","")
.replaceAll(",","\n"));
}
}
结果:
第二中直接指定某个类
@Configuration
@ComponentScan(value = "com.hyg.project",includeFilters ={@ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE,value = MyController.class)} )
public class MyConfig1 {
}
第三种自己定义规则
看@Filter注解里的value属性类型是一个FilterType,所以可以自己创建一个类去实现FilterType接口来实现加载的配置
自定义过滤类
public class MyFilter implements TypeFilter {
/**
*
* @param metadataReader:获取当前操作类的信息
* @param metadataReaderFactory:获取上下文信息
* @return
* @throws IOException
*/
@Override
public boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory) throws IOException {
//拿到当前类的所有的注解信息
AnnotationMetadata annotationMetadata = metadataReader.getAnnotationMetadata();
//获得当前扫描到的类的信息
ClassMetadata classMetadata = metadataReader.getClassMetadata();
//获取到当前类的所有资源信息
Resource resource = metadataReader.getResource();
//拿到类名
String className = classMetadata.getClassName();
System.out.println("-------"+className+"------");
//这个环节是 根据拿到的类信息 判断类名里是否包含er 有就加载 没有就返回false 表示不加载过滤掉
if (className.contains("er")){
return true;
}
return false;
}
}
将controller、service、dao的注解去掉
//@Repository
public class MyDao {
}
//@Service
public class MyService {
}
//@Controller
public class MyController {
}
测试结果,可以看出在指定的包名下MyDao跟Student因为类名里不包含er所以被过滤掉了,这种方式就可以自己去自定义规则选择需要加载进Spring IOC容器里的Bean
@Scope
用于指定scope作用域的(用在类上),scope默认是单例的
@Configuration
public class MyConfig {
/**
* scope里可以填四个参数 :prototype:原型,多例
* singleton:单例
* request:主要应用于web模块,同一次请求只创建一个实例
* session:主要应用于web模块,同一个session值创建一个实例
*
* @return
*/
@Scope("prototype")
@Bean
public Student student(){
return new Student(12,"student");
}
}
使用prototype属性测试
public class MyTest {
@Test
public void test(){
ApplicationContext app=new AnnotationConfigApplicationContext(MyConfig.class);
Object student = app.getBean("student");
Object student1 = app.getBean("student");
System.out.println(student==student1);
}
}
结果:可以得出每获取一个对象就会创建一次新对象,多例情况
@Lazy
配置Bean在我们使用的时候才去加载,而不是启动时就加载,默认是非延迟加载,延迟加载 只针对单例Bean起作用
验证之前
@Configuration
public class MyConfig {
@Bean
public Student student(){
System.out.println("将对象添加到IOC容器中");
return new Student(12,"student");
}
}
public class MyTest {
@Test
public void test(){
ApplicationContext app=new AnnotationConfigApplicationContext(MyConfig.class);
System.out.println("IOC容器初始化完成");
Object student = app.getBean("student");
System.out.println(student);
}
}
结果:可以看到Spring是先初始化Bean才完成容器的初始化
在@Bean上使用@Lazy
结果:当调用getBean方法的时候才初始化对象
@Conditional
作用是按照一定的条件判断,满足条件才给容器注册Bean
假设当系统的运行时Windows,就加载条件满足的Bean,当操作系统是Linux的就加载符合条件的Bean
首先@Conditional注解的value属性是一个继承Condition的类,所以我们需要自定义一个类去实现Condition
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Conditional {
Class<? extends Condition>[] value();
}
判断符合Windows系统的类
public class WinCondition implements Condition {
@Override
public boolean matches(ConditionContext conditionContext, AnnotatedTypeMetadata annotatedTypeMetadata) {
//拿到Bean的工厂
ConfigurableListableBeanFactory beanFactory = conditionContext.getBeanFactory();
//拿到环境变量
Environment environment = conditionContext.getEnvironment();
//拿到系统的名字
String property = environment.getProperty("os.name");
//如果操作系统包含windows系统的返回true
if (property.contains("Windows")){
return true;
}
return false;
}
}
判断符合Linux系统的类
public class LinuxCondition implements Condition {
@Override
public boolean matches(ConditionContext conditionContext, AnnotatedTypeMetadata annotatedTypeMetadata) {
ConfigurableListableBeanFactory beanFactory = conditionContext.getBeanFactory();
Environment environment = conditionContext.getEnvironment();
String property = environment.getProperty("os.name");
//如果操作系统包含Linux系统的返回true
if (property.contains("Linux")) {
return true;
}
return false;
}
}
配置类,@Conditional的使用是在需要做条件判断的Bean上注解,下面的例子我给前面两个Bean判断是windows系统的就注册Bean,第三个符合Linux系统的才祖册
@Configuration
public class MyConfig {
@Conditional(WinCondition.class)
@Bean
public Student student(){
System.out.println("将对象添加到IOC容器中");
return new Student(12,"student");
}
@Conditional(WinCondition.class)
@Bean
public Student studentOne(){
System.out.println("将对象1添加到IOC容器中");
return new Student(12,"student");
}
@Conditional(LinuxCondition.class)
@Bean
public Student studentTwo(){
System.out.println("将对象2添加到IOC容器中");
return new Student(12,"student");
}
}
@Test
public void test(){
ApplicationContext app=new AnnotationConfigApplicationContext(MyConfig.class);
System.out.println("IOC容器初始化完成");
String[] beanDefinitionNames = app.getBeanDefinitionNames();
System.out.println(Arrays.toString(beanDefinitionNames)
.replaceAll("\\[|\\]","")
.replaceAll(",","\n"));
}
}
结果,当前系统的windows的所以可以看到满足条件的Bean注册成功,第三个Bean不满足,因此没有注册
下面配置环境切到Linux,测试
测试结果
从上面的演示可以知道@Conditional可以动态的去判断这个Bean是否符合注册的条件,在开发中可以根据业务场景设置想祖册谁就注册谁。
@Import
导入外部资源,可以手动指定加载第三方资源
简单使用,创建一个新的类不需要做任何事情
public class Teacher {
}
使用@Import在value指定类进行加载
@Configuration
@Import(value = Teacher.class)
public class Config {
}
直接看测试结果,Teacher这个类被成功的加载到容器中,这就是@Import的简单使用,只需要简单的配置就可以将Bean加载到容器,不需要我们的Bean做任何事情
实现ImportSelector 自定义规则,在String数组中指定类路径
public class MyImport implements ImportSelector {
@Override
public String[] selectImports(AnnotationMetadata annotationMetadata) {
return new String[]{"com.hyg.project.entity.Teacher",
"com.hyg.project.entity.Student"};
}
}
使用
@Configuration
@Import(value = MyImport.class)
public class Config {
}
结果,成功注册
实现ImportBeanDefinitionRegistrar,灵活的根据自己的逻辑,去注册需要的Bean
public class MyImport implements ImportBeanDefinitionRegistrar {
@Override
public void registerBeanDefinitions(AnnotationMetadata annotationMetadata, BeanDefinitionRegistry beanDefinitionRegistry) {
//逻辑自定义判断 假设 判断容器是否有Student类和Teacher 这两个类,才可以把School对象注册到容器中
boolean b =beanDefinitionRegistry.containsBeanDefinition("com.hyg.project.entity.Teacher");
boolean b1 = beanDefinitionRegistry.containsBeanDefinition("com.hyg.project.entity.Student");
//判断是否存在,都存在就注册School类
if (b&&b1){
//new beanDefinition 传入需要注册的类
BeanDefinition beanDefinition=new RootBeanDefinition(School.class);
//自定义类名 并传入beanDefinition
beanDefinitionRegistry.registerBeanDefinition("school",beanDefinition);
}
}
}
@Configuration
@Import(value = {MyImport.class, com.hyg.demo.MyImport.class})
public class Config {
}
结果
验证没有Student对象看看是否注册School对象,从结果可以看出条件不成立时没有注册School对象
注意:用@Import导入的类名默认是全类名,而不是默认类名首字母小写
把需要注册的对象封装为FactoryBean
public class MyFactoryBean implements FactoryBean<School> {
@Override
public School getObject() throws Exception {
return new School();
}
@Override
public Class<?> getObjectType() {
return School.class;
}
//设置是否是单例,默认是true 可以设置false为多例
@Override
public boolean isSingleton() {
return true;
}
}
@Configuration
public class Config {
@Bean
public MyFactoryBean school(){
return new MyFactoryBean();
}
}
结果 school对象成功注入容器,并且可以发现我们在配置类中配置的Bean是MyFactoryBean ,而通过getBean可以拿到school,并且实际拿到的类是School,
以下方法可以拿到school对象对应的factoryBean对象,在对象名前加&
Object factoryBean=app.getBean("&school")
给Ioc容器注册Bean的总结
- @Bean 直接导入单个类
- @ComponentScan 默认扫描(@Controller、@Service、@Repostory、@Component)
- @Import 快速给容器导入Bean
1. @Import 直接参数导入
2. 实现ImportSelector 自定义规则实现
3. 实现ImportBeanDefinitionRegistrar,获得BeanDefinitionRegistry可以手动直接往Ioc容器中注入 - FactoryBean 把需要注册的对象封装为FactoryBean
1. FactoryBean 负责将Bean注册到Ioc容器的Bean
2. BeanFactory 从Ioc容器中获取的Bean对象
3. 使用注解@PostConstruct和@PreDestroy
4. 实现BeanPostProcessor接口直接重写BeanPostProcessor 里面的默认方法
Bean生命周期的注解
有两种写法
- 配置@Bean的参数
- 分别实现 InitializingBean和 DisposableBean
作用就是在Bean初始化或者销毁时做一些事情
先创建一个Car类
public class Car {
public Car() {
System.out.println("调用构造方法");
}
public void addOil(){
System.out.println("行驶前加油");
}
public void run(){
System.out.println("正在行驶");
}
public void stop(){
System.out.println("停车");
}
}
```java
@Configuration
public class Config {
@Bean(initMethod = "addOil",destroyMethod = "stop")
public Car car(){
return new Car();
}
}
结果中看到在car对象初始化时方法addOil被调用了
关闭容器
((AnnotationConfigApplicationContext) app).close();
可以看到对象被销毁时调用了stop方法
第二种方法
@Component
public class Train implements InitializingBean, DisposableBean {
@Override
public void afterPropertiesSet() throws Exception {
System.out.println("行驶前加油");
}
@Override
public void destroy() throws Exception {
System.out.println("停车");
}
}
@Configuration
@ComponentScan("com.hyg.project.entity")
public class Config {
@Lazy
@Bean(initMethod = "addOil",destroyMethod = "stop")
public Car car(){
return new Car();
}
}
结果
第三种,使用注解
@Component
public class Plane {
public Plane() {
System.out.println("调用构造方法");
}
@PostConstruct
public void before(){
System.out.println("飞机起飞前");
}
public void fly(){
System.out.println("飞机在飞行");
}
@PreDestroy
public void landing(){
System.out.println("飞机降落");
}
}
结果
第四种,实现BeanPostProcessor接口
直接重写BeanPostProcessor 里面的默认方法,将默认修饰符default改为public
@Component
public class BeanPost implements BeanPostProcessor {
@Override
@Nullable
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
//这里可以拿到Bean的信息
System.out.println("Bean调用构造方法后"+beanName+"---"+bean);
return bean;
}
@Override
@Nullable
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
System.out.println("Bean被销毁后后"+beanName+"---"+bean);
return bean;
}
}
启动容器,可以看到上面的方法可以拿到各个Bean的信息,可以自己判断哪些Bean需要初始化或者销毁时处理事情,