Spring中你可能不知道的事(一)

栏目: Java · 发布时间: 7年前

内容简介:Spring作为Java的王牌开源项目,相信大家都用过,但是可能大家仅仅用到了Spring最常用的功能,Spring实在是庞大了,很多功能可能一辈子都不会用到,今天我就罗列下Spring中你可能不知道的事。一是可以帮助大家以后阅读源码,知道Spring为什么会这么写,二是可以作为知识储备,当人家不会的时候,你正好知道这个点,三下五除二就搞定了,嘿嘿。三是平时吹牛的时候可以更有资本。。。当然最重要的就是可以对Spring有一个更全面的认识。现在官方推荐应该就是用JavaConfig的风格来完成Spring的配

Spring作为 Java 的王牌开源项目,相信大家都用过,但是可能大家仅仅用到了Spring最常用的功能,Spring实在是庞大了,很多功能可能一辈子都不会用到,今天我就罗列下Spring中你可能不知道的事。一是可以帮助大家以后阅读源码,知道Spring为什么会这么写,二是可以作为知识储备,当人家不会的时候,你正好知道这个点,三下五除二就搞定了,嘿嘿。三是平时吹牛的时候可以更有资本。。。当然最重要的就是可以对Spring有一个更全面的认识。

register

现在官方推荐应该就是用JavaConfig的风格来完成Spring的配置,也是现在的主流用法。我们经常这么写:

@Configuration
@ComponentScan
public class AppConfig {
}
复制代码
AnnotationConfigApplicationContext context=new AnnotationConfigApplicationContext(AppConfig.class);
复制代码

这段代码太简单,就不再解释了,但是我们可以把方法拆分下:

AnnotationConfigApplicationContext context=new AnnotationConfigApplicationContext();
context.register(AppConfig.class);
复制代码

在第二行代码才去注册配置类。

效果是一样的,我们除了可以注册配置类,还可以单独注册一个 bean:

@Component
public class Service {
}
复制代码
public class Main {
    public static void main(String[] args) {
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
        context.register(Service.class);
        context.refresh();//很重要        
        System.out.println(context.getBean(Service.class).getClass().getSimpleName());
    }
}
复制代码

这样我们就可以完成对bean的注入,这里面有一个细节很重要,需要调用refresh方法,不然会报错:

public class Main {
    public static void main(String[] args) {
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
        context.register(Service.class);
        System.out.println(context.getBean(Service.class).getClass().getSimpleName());
    }
}
复制代码
Spring中你可能不知道的事(一)

如果选择先实例化AnnotationConfigApplicationContext ,再去注册,当注册配置类的时候,不需要调用refresh方法,但是注册bean的时候,需要调用refresh方法。

registerBean

上面的方法虽然可以单独注册一个bean,但是在bean的类上,你必须打上@Component或者@Service或者@Repository,如果你不想用默认的作用域,也得打上@Scope,有没有一种方法,可以不用在bean的类上打各种注解?此时registerBean出场了:

public class Service {
    public Service(String str){
        System.out.println(str);
    }
}
复制代码
public class Main {
    public static void main(String[] args) {
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
        context.registerBean("myService", Service.class, () -> new Service("Hello"), z -> {
            z.setScope("prototype");
        });
        context.refresh();
        System.out.println(context.getBean("myService").getClass().getSimpleName());
        System.out.println(context.getBeanDefinition("myService").getScope());
    }
}
复制代码

我注册了名为myService的Bean,类是Service,并且作用域为prototype,且会调用带参的构造方法:

Spring中你可能不知道的事(一)

BeanPostProcessor

如果说上面两个小点不重要,那么这一个就是重磅级的了,BeanPostProcessor是Spring扩展点之一,BeanPostProcessor是一个接口,程序员可以通过实现它,插手bean的实例化过程,在bean创建前后做一些事情。在Spring内部,也大量的运用了BeanPostProcessor来完成各种功能。我们可以看下Spring内部有多少类实现了BeanPostProcessor接口(注意,注意,前方高能)。

Spring中你可能不知道的事(一)

Spring内部有这么多类(间接)实现了BeanPostProcessor接口,可想而知这个接口的重要性,那么这个接口应该怎么使用呢,很简单,我们只需要写一个类去实现BeanPostProcessor接口就可以。

在这里,我利用这个接口,来完成一个阉割版的JDK动态代理的注入:

首先定义一个接口:

public interface Service {
    void query();
}
复制代码

实现类:

@Component
public class ServiceImpl implements  Service {
    @Override
    public void query() {
        System.out.println("正在查询中");
    }
}
复制代码

实现InvocationHandler接口:

public class MyInvationHandler implements InvocationHandler {
    private Object target;

    public MyInvationHandler(Object target){
        this.target=target;
    }
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("进来了");
        Object obj = method.invoke(target, args);
        System.out.println("出去了");
        return obj;
    }
}
复制代码

实现BeanPostProcessor 接口:

@Component
public class MyBeanPostProcess implements BeanPostProcessor {
    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        Object o = Proxy.newProxyInstance(MyBeanPostProcess.class.getClassLoader(),
                bean.getClass().getInterfaces(), new MyInvationHandler(bean));
        return o;
    }

    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        return bean;
    }
}
复制代码

配置类

@Configuration
@ComponentScan
public class AppConfig {
}
复制代码

测试方法:

public class Main {
    public static void main(String[] args) {
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
        context.getBean(Service.class).query();
    }
}
复制代码

运行结果:

Spring中你可能不知道的事(一)

有木有很神奇,不管在main方法,还是业务的实现类,都没有看到JDK动态代理的影子,但是动态代理真真实实生效了,这就是BeanPostProcessor接口的神奇所在,事实上,Spring内部也是通过实现BeanPostProcessor接口来完成动态代理的,这个暂时不表。

BeanFactoryPostProcessor

BeanFactoryPostProcessor也是Spring的扩展点,程序员可以通过实现它,读取bean的定义,然后对其进行修改,比如我需要修改bean的作用域为prototype,可以这么做:

@Component
public class MyBeanFactoryPostProcessor implements BeanFactoryPostProcessor {
    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory factory) throws BeansException {
        factory.getBeanDefinition("repo").setScope("prototype");
    }
}
复制代码
@Repository
public class Repo {
}
复制代码
public class Main {
    public static void main(String[] args) {
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
        System.out.println(context.getBeanDefinition("repo").getScope());
    }
}
复制代码
Spring中你可能不知道的事(一)

大家都知道bean的默认作用域为singleton,这里就通过实现BeanFactoryPostProcessor接口,把作用域改成了prototype。

BeanFactoryPostProcessor 在 BeanPostProcessor之前。

单例bean中有原型bean

如果一个单例的bean中,包含原型的bean,会发生什么事情呢?我们写一个例子看一下:

@Configuration
@ComponentScan
public class AppConfig {
    @Bean
    @Scope("singleton")
    public Single singleton(){
        return new Single();
    }
    @Bean
    @Scope("prototype")
    public Prototype prototype(){
        return new Prototype();
    }
}
复制代码
public class Single {
    public Single(){
        System.out.println("Single构造方法");
    }
    @Autowired
    private Prototype prototype;
    public Prototype getPrototype() {
        return prototype;
    }
    public void setPrototype(Prototype prototype) {
        this.prototype = prototype;
    }
    public void say() {
        System.out.println(this);
        prototype.say();
    }
}
复制代码
public class Prototype {
    public Prototype(){
        System.out.println("Prototype构造方法");
    }
    public void say() {
        System.out.println(this);
    }
}
复制代码
@Component
public class Test {
    @Autowired
    Single single;
    public void run() {
        for (int i = 0; i < 5; i++) {
            single.say();
        }
    }
}
复制代码

因为代码比较长,避免大家上下来回滚动,我简单的说明下这段代码:Single类是单例的,Prototype是原型的,Single类依赖Prototype,分别给两个类添加一个构造方法,打印一句话,Single类中的方法调用Prototype类的方法,两个方法都打印this。然后再测试方法中自动注入Single,循环5次,调用Single类中的方法。

运行结果:

Spring中你可能不知道的事(一)

这结果明显有问题,Single因为是单例的,只能执行到一次构造方法,每次打印出来的对象也相同,这是没有问题的,但是Prototype是原型的,也只运行了一次构造函数,打印出来的对象也相同,这就有问题了。

这问题怎么解决呢?

ApplicationContextAware

对Single类进行改造,让它实现ApplicationContextAware接口中的setApplicationContext方法:

public class Single implements ApplicationContextAware {
    public Single() {
        System.out.println("Single构造方法");
    }

    private ApplicationContext context;

    public void say() {
        System.out.println(this);
        context.getBean(Prototype.class).say();
    }

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.context = applicationContext;
    }
}
复制代码

运行结果:

Spring中你可能不知道的事(一)

说的简单点,就是通过ApplicationContextAware接口中的setApplicationContext方法,获得ApplicationContext ,赋值给类中的变量ApplicationContext context, 然后从context中获得Prototype Bean。

此方法需要依赖ApplicationContext。

lookup

@Component
@Scope("singleton")
public class Single {
    public Single() {
        System.out.println("Single构造方法");
    }
    public void say() {
        System.out.println(this);
        getPrototype().say();
    }
    @Lookup
    public Prototype getPrototype() {
        return null;
    }
}
复制代码
@Component
@Scope("prototype")
public class Prototype {
    public Prototype(){
        System.out.println("Prototype构造方法");
    }

    public void say() {
        System.out.println(this);
    }
}
复制代码

运行结果:

Spring中你可能不知道的事(一)

此方法需要把配置类中的定义bean改为在类上加注解的方式。

Import

Import是Spring提供的注解,可以通过这个注解,在一个类引入另外一个类, 并且自动完成另外一个类的注册:

@Configuration
@Import(ServiceImpl.class)
public class AppConfig {
}
复制代码
public class ServiceImpl  {
    public void query() {
        System.out.println("正在查询中");
    }
}
复制代码
public class Main {
    public static void main(String[] args) {
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
        context.getBean(ServiceImpl.class).query();
    }
}
复制代码

运行结果:

Spring中你可能不知道的事(一)

可以看到虽然ServiceImpl类上没有打上任何注解,但是在AppConfig配置类上通过Import注解,把ServiceImpl给引入进来了,并且自动注册了ServiceImpl。

也许,单单使用Import注解,会把代码搞得更复杂,所以需要搭配使用,才能把它的能力发挥出来,下面让我们有请ImportSelector。

ImportSelector

让我们把目光回到介绍BeanPostProcessor的这一段中,在其中,我们定义了一个MyBeanPostProcess来完成JDK动态代理,但是让我们想一个问题,如果我们不需要使用这个MyBeanPostProcess了,怎么办?我们需要把MyBeanPostProcess类上的Component注解删除,哪天又需要使用了,还得加上,如果只有一个类,还不算糟糕,但是如何有几十个类呢?相当麻烦,我们能不能在一个类中统一处理,需要启动哪些Bean就像Spring Boot 一样?当然可以。我们可以借助于ImportSelector来完成:

首先我们需要定义一个类,实现ImportSelector 中的 selectImports方法,这个方法返回的是需要与此类绑定的bean的名称的数组:

public class AspectSelector implements ImportSelector {
    @Override
    public String[] selectImports(AnnotationMetadata importingClassMetadata) {
        return new String[]{MyBeanPostProcess.class.getName()};
    }
}
复制代码

我们再自定义一个注解,打上Import注解,引入上面的类:

@Import(AspectSelector.class)
@Retention(RetentionPolicy.RUNTIME)
public @interface EnableAspect{
}
复制代码

注意看AppConfig 的注解,多了一个EnableAspect注解:

@Configuration
@ComponentScan
@EnableAspect
public class AppConfig {
}
复制代码
public class Main {
    public static void main(String[] args) {
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
        context.getBean(Service.class).query();
    }
}
复制代码

然后我们把MyBeanPostProcess上的注解删除,运行:

Spring中你可能不知道的事(一)

当我们不需要使用MyBeanPostProcess了,只要在AppConfig删除EnableAspect注解就OK了。

这是相当炫酷的一个技巧,在SpringBoot大量使用,比如开启事务管理EnableTransactionManagement。

FactoryBean

FactoryBean经常会和BeanFactory放在一起比较,因为他们太像了,不过仅仅是长得像,其实它们完全不是同一个东西。

FactoryBean,是一种特殊的Bean,特殊在它除了自身是Baen,还可以生产Bean,是不是很符合FactoryBean这个名称?

FactoryBean是一个接口,我们需要实现它:

@Component
public class MyFactoryBean implements FactoryBean {

    public Object getObject() throws Exception {
        return new DataSource();
    }
    @Override
    public Class<?> getObjectType() {
        return null;
    }
}
复制代码
public class DataSource {
}
复制代码
@Configuration
@ComponentScan
public class AppConfig {
}
复制代码
public class Main {
    public static void main(String[] args) {
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
        System.out.println(context.getBean("myFactoryBean").getClass().getSimpleName());
        System.out.println(context.getBean("&myFactoryBean").getClass().getSimpleName());
    }
}
复制代码

运行结果:

Spring中你可能不知道的事(一)

我们可以看到MyFactoryBean上打了一个Component,它可以被扫描到,但是DataSource上什么都没有加,按理来说,是没有被扫描到的,但是它就是被注册进去了,因为它实现了FactoryBean接口,在getObject方法返回了DataSource的实例,可以理解为DataSource是MyFactoryBean生产出来的一个Bean。

让我们仔细看下main方法和运行结果,可以看到 MyFactoryBean本身的BeanName是&myFactoryBean,MyFactoryBean生产出来的Bean的BeanName是myFactoryBean。

这有什么用呢?可以隐藏构建Bean的细节。如果我们的DataSource是第三方提供的,里面有一堆的字段需要配置,还有一堆的依赖,如果我们来配置的话,根本无法完成,最好的办法就是还是交给维护第三方去配置,但是DataSource是不能去修改的。这个时候,就可以用FactoryBean来完成,在getObject配置好DataSource,并且返回。我们经常使用的Mybatis也利用了FactoryBean接口。

Spring实在是太庞大了,很多功能都不是经常用,我在这里只是稍微罗列了几个小点,加上我们经常用的那些,可能还不及Spring的十分之一,这已经是乐观的了。

限于篇幅关系,这一章的内容到这里就结束了,其中BeanPostProcessor,BeanFactoryPostProcessor,FactoryBean,Import,ImportSelector这几块内容非常重要,正在由于这些,才让Spring变的更加灵活,更加好用。


以上所述就是小编给大家介绍的《Spring中你可能不知道的事(一)》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!

查看所有标签

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

Ordering Disorder

Ordering Disorder

Khoi Vinh / New Riders Press / 2010-12-03 / USD 29.99

The grid has long been an invaluable tool for creating order out of chaos for designers of all kinds—from city planners to architects to typesetters and graphic artists. In recent years, web designers......一起来看看 《Ordering Disorder》 这本书的介绍吧!

CSS 压缩/解压工具
CSS 压缩/解压工具

在线压缩/解压 CSS 代码

JSON 在线解析
JSON 在线解析

在线 JSON 格式化工具

XML 在线格式化
XML 在线格式化

在线 XML 格式化压缩工具