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站就可以看,简直是太棒了。

posted @ 2020-07-08 08:27  你眼里的星辰  阅读(342)  评论(0)    收藏  举报