Spring 注解驱动开发
之前用Spring开发,往往是写xml配置文件,现在用注解开发的越来越多,而且随着SpringBoot等技术,注解开发 越来越普遍,这就是大势所趋。
class Person{ int age; String name; public Person() { } public Person(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 "Person{" + "age=" + age + ", name='" + name + '\'' + '}'; } } @Configuration // 表示这是一个配置类 class MyConfig{ @Bean Person getPerson(){ return new Person(15,"xixi"); } } public class TestS { public static void main(String[] args) { AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(MyConfig.class); Person person = (Person)applicationContext.getBean("getPerson"); System.out.println(person); } }
1)通过 @Configuration 和 @Bean 来创建 bean @Configuration 修饰的类是一个配置类,作用相当于xml配置文件
通过这种方式创建的bean, bean名字默认就是方法名。 可以通过@Bean(”bean名字“)的方式,来设置创建的bean的名字
之前用xml 进行配置的方式

2) 我们会经常用到 @Repository @Service @Controller @Component注解 创建IOC容器时,检测到这些注解后会自动创建 它的bean实例
但是怎么检测到它们呢? 应该就是通过Class对象,然后,得到它的注解 即可, 这时候需要进行执行检测那些Class对象,也就是扫描哪些包下面的文件
这时候,就要用到@ComponentScan(”包名“) 注解,来指定进行扫描的包, 这个注解也是放在配置类的上面,因为创建IOC容器是从配置类开始的。
3) 创建的Bean默认都是单实例的,可以通过 @Scope 注解,来改变作用域 比如改为 Prototype原型 @Scope("prototype")
singleton 单例模式 下, 会在IOC启动的时候就创建对象,放到容器中。以后每次获取,得到的都是同一个实例。
prototype 原型模式 下, 会在获取bean的时候才会进行创建,每次获取,都会新创建一个实例。
4) 单实例默认是在IOC容器启动的时候创建的,可以通过设置懒加载的方式,来将bean的创建延迟到第一次获取这个bean的时候。懒加载是针对于单例模式而言的,原型模式不存在懒加载的问题。 用@Lazy 注解,与@Bean等搭配使用即可
5) @Conditional 注解 按照条件进行bean的注册.
6) 使用@Import (xxx.class) 注解导入组件, 比如有一个第三方的包, 要用到他下面的某个类, 就可以用@Import(xx.class) 将这个类的实例创建出来. bean的名字默认为全路径名
@Import 注解的参数 一个Class的数组, 因此可以传递多个Class,
为了 代码的好看, 可以创建一个类, 实现ImportSelector接口,重写接口中的方法,将要导入的组件都写到那里面,然后将我们编写的实现类传进来,代码会看起来更加整洁. (这种方式用的挺多的)
还可以创建一个类, 实现 ImportBeanDefinitionRegiterar 接口, 主动注册 BeanDefiniton ..
[ 这里的BeanDefiniton 特别重要,bean的创建,就是根据这个来创建的, (其实 本质也是Class对象是不是) . >>>>>>>>> 后面要好好看看这里]
[ 这个有点偏底层了]
class TestImportDemo{ } class MyImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar{ @Override public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) { BeanDefinition beanDefinition=new RootBeanDefinition(TestImportDemo.class); registry.registerBeanDefinition("xixihaha",beanDefinition); } }
7) 实现FactoryBean接口, 来创建Bean . Spring 与 其他的框架整合时,有大量用到 FactoryBean.
在工厂类中, 创建Bean, 然后将工厂类, 放到配置类中, 按照前面的那些情况,这时创建的应该是这个工厂类本身,但是实际创建的是工厂类中创建的对象,而不是工厂本身. 通过bean名字获取bean ,获取的也是工厂创建的对象实例, 获取bean时,在名字前面加 &符号, 获取的就是工厂对象了.
class MyFactoryBean implements FactoryBean<Color>{ @Override public Color getObject() throws Exception { return new Color(); } @Override public Class<?> getObjectType() { return Color.class; } @Override public boolean isSingleton() { return true; } }
@Configuration
@ComponentScan("com.atguigu.gulimall.product.Test")
@Import(MyImportBeanDefinitionRegistrar.class)
class MyConfig{
// @Scope("prototype")
@Lazy
@Bean("xxxx")
Person getPerson(){
return new Person(15,"xixi");
}
@Bean("factoryBean")
MyFactoryBean myFactoryBean(){
return new MyFactoryBean();
}
}
8) bean的生命周期
生命周期的过程:
bean 创建 -> 初始化 ->销毁
bean的生命周期是由IOC容器来管理的, 我们可以自定义 初始化 和 销毁方法.
>>>1 通过在 @Bean注解进行InitMethod参数, 和destroyMethod参数, 指定即可
@Configuration @ComponentScan("com.atguigu.gulimall.product.Test") @Import(MyImportBeanDefinitionRegistrar.class) class MyConfig{ // @Scope("prototype") @Lazy @Bean("xxxx") Person getPerson(){ return new Person(15,"xixi"); } @Bean("factoryBean") MyFactoryBean myFactoryBean(){ return new MyFactoryBean(); } @Bean(initMethod = "init",destroyMethod = "destroy") Car car(){ return new Car(); } } class Car{ Car(){ System.out.println("Car被创建出来"); } void init(){ System.out.println("执行了初始化方法"); } void destroy(){ System.out.println("执行了销毁方法"); } } public class TestS { public static void main(String[] args) { AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(MyConfig.class); System.out.println("容器创建完成"); applicationContext.close(); } }

初始化方法的执行时间:
对象创建完成,并且赋值完成之后,才会执行初始化方法. (单实例bean在IOC容器创建的时候, 会调用初始化方法, 多实例bean在获取bean的时候,才会调用初始化方法)
销毁方法的执行时间:
IOC容器关闭的时候,会掉用销毁方法. ( 单实例 (singleton) bean才会调用销毁方法,多实例 (prototype) bean不会调用销毁方法 )
>>>> 2 通过实现接口的方式, 来设置初始化方法和销毁方法.
class Car implements InitializingBean, DisposableBean { Car(){ System.out.println("Car被创建出来"); } @Override public void afterPropertiesSet() throws Exception { System.out.println("调用了初始化方法"); } @Override public void destroy() throws Exception { System.out.println("调用了销毁方法"); } }
>>>>> 3 用 @PostConstruct 和 @PreDestroy
class Car { Car(){ System.out.println("Car被创建出来"); } @PostConstruct void init(){ System.out.println("调用初始化方法"); } @PreDestroy void destroy(){ System.out.println("调用销毁方法"); } }
>>>>>4 通过Bean的后置处理器
通过创建一个类,实现BeanPostProcessor接口,重写里面的两个方法,一个用于初始化前,一个用于初始化后,然后将创建的后置处理器类也要加载容器中。
这样,所有的Bean在初始化前后,都会调用到我们创建的这个后置处理器中重写的方法。
也就是,后置处理器不是针对于某一个bean的,而是针对于所有的bean的,而且Spring中存在这大量的后置处理器。IOC容器创建bean的时候,先创建后置处理器的bean, 因为这样,后面的bean 创建的时候,才会用到后置处理器的 功能。
@Component class MyBeanPostProcessor implements BeanPostProcessor{ @Override public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException { System.out.println("初始化之前"); return bean; } @Override public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException { System.out.println("初始化之后"); return bean; } }
要明确初始化方法的调用是咋 bean实例创建完成之后,并且属性赋值完成之后,才会调用初始化方法,而初始化方法的设置就是前面的1-3这三种方式,后置处理器是在初始化方法调用的前后,执行一些操作。 上面就是bean生命周期的内容。
--- 好东西 啊!!! 在 docreateBean方法中; 这些需要反复仔细研究
1)创造完实例后(通过反射 jdk还是cglib),
2)先进行属性的赋值,这里面就会涉及到循环依赖(是通过Autowire 根据名字还是类型 这才是循环依赖!!!!! 这里也会有后置处理器)
有用到 InstantiationAwareBeanPostProcessor 这个后置处理器 在实例化前后起作用
3)然后进行对象的初始化(后置处理器)
// Initialize the bean instance. Object exposedObject = bean; try { populateBean(beanName, mbd, instanceWrapper); exposedObject = initializeBean(beanName, exposedObject, mbd); }
---
在底层代码的位置:
创建完实例后,执行完populate赋值结束完成后,会调用 initializeBean 方法,初始化 和初始化前后的方法的调用都在这里面
// Initialize the bean instance. Object exposedObject = bean; try { populateBean(beanName, mbd, instanceWrapper); exposedObject = initializeBean(beanName, exposedObject, mbd); // 初始化bean }
protected Object initializeBean(final String beanName, final Object bean, @Nullable RootBeanDefinition mbd) { if (System.getSecurityManager() != null) { AccessController.doPrivileged((PrivilegedAction<Object>) () -> { invokeAwareMethods(beanName, bean); return null; }, getAccessControlContext()); } else { invokeAwareMethods(beanName, bean); } Object wrappedBean = bean; if (mbd == null || !mbd.isSynthetic()) {
/// 初始化前 wrappedBean = applyBeanPostProcessorsBeforeInitialization(wrappedBean, beanName); //后置处理器中的方法 初始化前 } try {
/// 初始化 invokeInitMethods(beanName, wrappedBean, mbd); // 初始化 就用到了前面说的3种初始化方法 } catch (Throwable ex) { throw new BeanCreationException( (mbd != null ? mbd.getResourceDescription() : null), beanName, "Invocation of init method failed", ex); } if (mbd == null || !mbd.isSynthetic()) {
/// 初始化后 wrappedBean = applyBeanPostProcessorsAfterInitialization(wrappedBean, beanName); // 后置处理器中的方法 初始化后 } return wrappedBean; }
初始化方法部分的流程就是: 先执行所有后置处理器中的 初始化前方法, 然后执行具体bean的初始化方法 ,最后执行后置处理器中的 初始化后方法。
还有!! 一些注解为什么能有相应的功能?? 很多靠的就是这个后置处理器,后置处理器进行解析Class对象的注解,并执行 初始化前 后的方法中,进行操作,来实现注解的功能。
比如前面的那个通过 @PostConstruct 和 @PreDestroy 方法用的就是 InitDestroyAnotationBeanPostProcessor这个后置处理器,来实现的相应的功能。
(所以。Spring中 注解的生效 靠的是大量的后置处理器来保证的吗? 》》》》》》》》》 这也是一个值得深度思考的点 )
@Value注解 给变量赋值 可以输入正常的参数 还可以 用${ }来取配置文件中的值来进行赋值
【怎么实现的变量的赋值 ,没有调用Setter方法啊!!! 通过反射进行的赋值吗 ,是的!! 》》》》》》 需要进一步研究】
@PropertySource 注解 用于导入外部的配置文件,将配置文件中的信息,以 K:V 的方式保存起来,这样 就可以进行后面的使用了。比如结合 @Value注解来一起使用
自动装配:
Spring利用依赖注入 (DI),完成对IOC容器中各个组件的依赖关系赋值
@Autowired 自动注入
先按照类型去寻找相应的组件,如果找到多个,就根据属性名来匹配对应的组件,如果属性名不一致则报错。
可以加入@Primary注解,通过这个注解,当使用@Autowired进行自动装配的时候,如果根据类型匹配到多个组件,那么就会先加载用@Primary注解修饰的这个。
所以如果根据类型找到多个组件,我们就可以用@Qulifier(”bean名字“)注解来指定特定的组件。
@Service class MyService{ @Autowired
// @Qulifier("myDao2") MyDao myDao2; @Override public String toString() { return "MyService{" + "myDao=" + myDao2 + '}'; } } @Repository class MyDao{ String name="hehe"; public String getName() { return name; } public void setName(String name) { this.name = name; } @Override public String toString() { return "MyDao{" + "name='" + name + '\'' + '}'; } }
@Configuration
@ComponentScan("com.atguigu.gulimall.product.Test")
@Import(MyImportBeanDefinitionRegistrar.class)
class MyConfig{
// @Scope("prototype")
@Bean("person")
Person getPerson(){
return new Person();
}
@Bean("factoryBean")
MyFactoryBean myFactoryBean(){
return new MyFactoryBean();
}
@Bean()
Car car(){
return new Car();
}
@Bean("myDao2")
MyDao myDao(){
MyDao myDao = new MyDao();
myDao.setName("xixi");
return myDao;
}
}
public class TestS {
public static void main(String[] args) {
AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(MyConfig.class);
System.out.println("容器创建完成");
MyService myService = (MyService)applicationContext.getBean("myService");
System.out.println(myService);
applicationContext.close();
}
}
如果@Autowired 根据类型进行匹配,一个组件也没有匹配到,会报错。 但是如果,给@Autowired注解加入 required=false 参数,就不会报错了,这个参数默认是true, 也就是默认必须要根据类型找到组件。
@Resource 自动装配也可以使用 @Resource 注解。不过@Resource注解不是Spring的注解 是java规范中的注解
@Resource 默认不是按照类型进行装配的,是按照属性名进行装配的, 可以通过参数 name="bean名字" 来指定特定的bean.
@Autowired的功能是通过 AutowiredAnnotationBeanProcessor 这个后置处理器 来实现的。(》》》》 后置处理器强大啊!!)
@Autowired 不是只能放在属性的上面 还可以放到构造器的上面,这样构造器中传入的参数要是依赖了别的bean,就会通过构造器进行注入,也可以直接加载依赖的参数前面,也能加载方法上面,比如setter方法。
是否在Spring的底层代码中,看到了大量的Aware, 我们可以自己创建类,来实现相应的xxxAware接口,然后加载我们创建的这个类的时候,就会执行重写的xxxAware接口中的方法。
实现了xxxAware接口的bean,被加载的时间还是挺早的。在常规的bean加载之前,但是在后置处理器和配置类加载之后。
@Component class MyAwareImpl implements ApplicationContextAware, BeanNameAware { @Override public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { System.out.println(applicationContext+">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>"); } @Override public void setBeanName(String name) { System.out.println(name+"》》》》》》》》》》》》》》》》》》》》》》》"); } }
xxxAwre接口,都是有相应的后置器来进行解析的,比如ApplicationContextAware 就是 通过 ApplicationContextAwareProcessor
[ Spring中告诉了我很多,其中一点就是,一切都不是凭空出现的,一切功能的背后都是有类在做相关的底层操作 ]
那么对于我们实现了这个接口的类,他究竟做了什么呢??什么时候做的???
首先,这个ApplicationContextAwareProcessor 实现了 BeanProcessor 接口,也就是说这个类也是一个后置处理器,那么当我们的类MyAwareImp的实例创建完成后,并赋值完成后(当然我们这里不需要赋值),就会执行初始化操作,在初始化前后会执行后置处理器中的方法,这里就用到了这个后置处理器中的方法。
@Override @Nullable public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException { if (!(bean instanceof EnvironmentAware || bean instanceof EmbeddedValueResolverAware || bean instanceof ResourceLoaderAware || bean instanceof ApplicationEventPublisherAware || bean instanceof MessageSourceAware || bean instanceof ApplicationContextAware)){ return bean; } AccessControlContext acc = null; if (System.getSecurityManager() != null) { acc = this.applicationContext.getBeanFactory().getAccessControlContext(); } if (acc != null) { AccessController.doPrivileged((PrivilegedAction<Object>) () -> { invokeAwareInterfaces(bean); return null; }, acc); } else { invokeAwareInterfaces(bean); } return bean; }
private void invokeAwareInterfaces(Object bean) { if (bean instanceof EnvironmentAware) { ((EnvironmentAware) bean).setEnvironment(this.applicationContext.getEnvironment()); } if (bean instanceof EmbeddedValueResolverAware) { ((EmbeddedValueResolverAware) bean).setEmbeddedValueResolver(this.embeddedValueResolver); } if (bean instanceof ResourceLoaderAware) { ((ResourceLoaderAware) bean).setResourceLoader(this.applicationContext); } if (bean instanceof ApplicationEventPublisherAware) { ((ApplicationEventPublisherAware) bean).setApplicationEventPublisher(this.applicationContext); } if (bean instanceof MessageSourceAware) { ((MessageSourceAware) bean).setMessageSource(this.applicationContext); } if (bean instanceof ApplicationContextAware) {
// 这里调用的就是我们自己创建的类中的 方法 ((ApplicationContextAware) bean).setApplicationContext(this.applicationContext); } }
上面还有一个细节(今天就较上劲了,哈哈哈),就是 ApplicationContextAwareProcessor中 会被传输一个参数 Object bean, 这个变量就是我们的bean, 在IOC创建的总流程中,每个bean实现被创建出来,进行赋值后,在初始化之前,会遍历所有的 后置处理器,来进行初始化前的操作,当前也就是调用已经创建好的后置处理器的bean实例,会将要处理的bean传给后置处理器,也就是这里看到的参数Object bean , 后置处理器就可以操作这个bean,来进行一些操作,这里就是调用的这个bean的setApplicationContext方法,也就是调用了我们自己重写的方法了!!!! (事情就是这么个事情)
执行的时间,就是后置处理器中的方法的被调用的时间!!!! [ 看到了吗?? 一切都没有那么神秘,靠后置处理器来决定方法的调用时间 ]
每个xxxAware 都有他对应的 xxxxAwareProcessor (后置处理器)
@Profile注解 根据环境来决定激活(注册)哪些组件
那么怎么生效呢? 通过下面这两种方式。

从上面可以看到,这个register方法很重要,他注册的那个类就是我们的配置类(当然这里可以传入多个),可以说IOC容器中的bean的信息就是通过这里注册进去的,后面创建bean,就是根据这里的bean信息,如果没有注册进来,后面bean也不会创建。
小总结:

在SpringBoot中, @Conditional注解 和 @Import注解 用的特别多


参考文档:
强烈推荐尚硅谷的 雷丰阳的课程,b站就可以看,简直是太棒了。
浙公网安备 33010602011771号