1

起因

clipboard.png

欲实现路由验证,写了一个Hibernate拦截器,在对数据库进行操作之前对菜单进行校验,如果存在,则抛出异常,终止保存操作的执行;如果不存在,则继续执行。拦截器代码如下:

package com.mengyunzhi.measurement.interceptor;

import com.mengyunzhi.measurement.Service.WebAppMenuService;
import com.mengyunzhi.measurement.entity.WebAppMenu;
import org.hibernate.EmptyInterceptor;
import org.hibernate.type.Type;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.dao.DataIntegrityViolationException;

import java.io.Serializable;

/**
 * 继承自Hibernate提供的EmptyInterceptor拦截器
 * 在进行数据库操作之前进行处理,判断该路由是否唯一
 * 抽象菜单:则不能相同
 * 其子菜单:如果不属于同一父菜单,可以相同
 */
public class WebAppMenuInterceptor extends EmptyInterceptor {

    @Autowired
    private WebAppMenuService webAppMenuService;

    /**
     * 重写OnSave方法
     */
    @Override
    public boolean onSave(Object entity, Serializable id, Object[] state, String[] propertyNames, Type[] types) {
        // 如果是菜单实体
        if (entity instanceof WebAppMenu) {
            // 则执行路由验证
            this.validateWebAppMenuRoute((WebAppMenu) entity);
        }
        // 调用父类的onSave方法,不太明白这个方法设计返回boolean值是为何?
        return super.onSave(entity, id, state, propertyNames, types);
    }

    /**
     * 路由验证
     */
    private void validateWebAppMenuRoute(WebAppMenu webAppMenu) {
        // 初始化变量
        Boolean isAbstract = webAppMenu.isAbstract();
        String routeName = webAppMenu.getRouteName();
        Boolean isExist;
        // 如果是抽象菜单
        if (isAbstract){
            // 根据路由名和抽象为true,判断是否存在
            isExist = webAppMenuService.existsByRouteNameAndIsAbstractIsTrue(routeName);
        } else {
            // 非抽象,判断其同一父菜单下是否有相同菜单
            isExist = webAppMenuService.existsByRouteNameAndParentWebAppMenu(routeName, (WebAppMenu) webAppMenu.getParentWebAppMenu());
        }
        if (isExist) {
            throw new DataIntegrityViolationException("该路由非法");
        }
    }
}

但是测试一直通不过,报错:无法加载上下文。

clipboard.png

打印了一下webAppMenuService的值,发现全都是null;果然,这个东西没注入进来。

clipboard.png

看来@Autowired并不是万能的,才有了下文对Spring IOC的学习。

系统的学习一下,不要像以前一样去StackOverflow查答案,然后照搬别人的解决方案,感觉现在对Spring的了解还是太少了。

IOC

IOC:控制反转,大家都熟知的定义这里就不再赘述了。

其实,Spring负责帮我们管理对象,就像我在关于接口的代码复用中实现的手动注入对象的代码一样。

/**
 * 鸟
 */
public class Bird extends Animal implements Volitant {

    private Volitant volitant;

    public void setVolitant(Volitant volitant) {
        this.volitant = volitant;
    }

    @Override
    public void fly() {
        this.volitant.fly();
    }
}

/**
 * 蝙蝠
 */
public class Bat extends Animal implements Volitant {

    private Volitant volitant;

    public void setVolitant(Volitant volitant) {
        this.volitant = volitant;
    }

    @Override
    public void fly() {
        this.volitant.fly();
    }
}

public class Main {

    public static void main(String[] args) {
        Volitant volitant = new VolitantImpl();
        Bird bird = new Bird();
        bird.setVolitant(volitant);
        bird.fly();
        Bat bat = new Bat();
        bat.setVolitant(volitant);
        bat.fly();
    }
}

上述代码是手动对一个类中需要设置的对象进行注入,其实Spring IOC就是一个帮助我们管理和注入对象的角色。

容器

说到对象的管理,不得不提一个“容器”的概念,这个容器装的是对象。

当然,这里的容器是我们的想象的一个概念,它的术语名称大家可能会很熟悉——“上下文”,只是上下文是一种更高级的容器罢了。

建立一个maven项目,依赖引入spring-context,我们以一个实际的Spring使用来学习IOC

clipboard.png

clipboard.png

这是Spring的七个模块,在IBM开发者学习文档中找到的。大致看了一下介绍,coreSpring的核心库,有各种功能,context调用核心库中的方法,对外提供容器以及其他功能。

BeanFactory

BeanFactory是一个最基础的容器接口,提供了从容器中获取对象,判断容器中是否有某对象的方法。只有最基础的容器功能,现在已经不建议使用。

public interface BeanFactory {
    String FACTORY_BEAN_PREFIX = "&";

    Object getBean(String var1) throws BeansException;

    <T> T getBean(String var1, @Nullable Class<T> var2) throws BeansException;

    Object getBean(String var1, Object... var2) throws BeansException;

    <T> T getBean(Class<T> var1) throws BeansException;

    <T> T getBean(Class<T> var1, Object... var2) throws BeansException;

    boolean containsBean(String var1);

    boolean isSingleton(String var1) throws NoSuchBeanDefinitionException;

    boolean isPrototype(String var1) throws NoSuchBeanDefinitionException;

    boolean isTypeMatch(String var1, ResolvableType var2) throws NoSuchBeanDefinitionException;

    boolean isTypeMatch(String var1, @Nullable Class<?> var2) throws NoSuchBeanDefinitionException;

    @Nullable
    Class<?> getType(String var1) throws NoSuchBeanDefinitionException;

    String[] getAliases(String var1);
}

既然我们要从容器中拿对象?那我们就需要告诉IOC容器,你的容器中应该有什么对象,并且各个对象之间的依赖关系,总不能我写的每个类都实例化到容器中吧?所以诞生了xml配置文件。类似下面这样。

clipboard.png

一个beanid是什么,对应的是哪个类,并且这个对象依赖于其他的什么对象。

clipboard.png

看了一下,有个删除线,不建议使用,也就没有过深的研究,我们投入上下文的怀抱。

ApplicationContext

ApplicationContext当然也支持xml配置,其实现类为ClassPathXmlApplicationContext

因为本人更喜欢注解,这里深入学习一下注解的配置方式。

模拟项目中的真实架构,Service接口,依赖,为其注入实现。

TestService接口:

package com.mengyunzhi.spring.service;

public interface TestService {
}

TestService实现:

package com.mengyunzhi.spring.service;

import org.springframework.stereotype.Component;

@Component
public class TestServiceImpl implements TestService {

    @Override
    public String toString() {
        return "我是TestServiceImpl, TestService的实现";
    }
}

你问这里为什么加的是@Component注解而不是@Service,其实这几个注解,无论是@Service@Repository@Controller实际上都是声明一个组件,让其被Spring所管理,这么设计,不过是让人们看代码上的注解时,就能清晰地了解这个类的职责。

clipboard.png

clipboard.png

clipboard.png

注意,这里加的@Component告诉Spring,你为我管理这个类。然后还有一个注解,@ComponentScan就是表示我要扫描哪个包下的有@Component注解的类,并对其进行管理。

主方法:

package com.mengyunzhi.spring;

import com.mengyunzhi.spring.service.TestService;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.ComponentScan;

@ComponentScan("com.mengyunzhi.spring")
public class Application {

    public static void main(String []args) {
        ApplicationContext context = new AnnotationConfigApplicationContext(Application.class);
        TestService testService = context.getBean(TestService.class);
        System.out.println(testService);
    }
}

新建一个基于注解的上下文,然后获取TestService这个类的实例,同时打印。注意,这里的@ComponentScan一定要加,不加会报错。(我感觉这种方式比xml配置简单多了,@几下就完成了配置)

运行结果:

clipboard.png

Bean

突发奇想,我们修改一下toString方法的代码,相信你看到代码就知道我想干什么了。

package com.mengyunzhi.spring.service;

import org.springframework.stereotype.Component;

@Component
public class TestServiceImpl implements TestService {

    @Override
    public String toString() {
        return "我是TestServiceImpl, TestService的实现\n"
                + "我的内存地址是:"
                + super.toString();
    }
}

修改主方法代码,现在我们从上下文中获取两个TestService的实例。

package com.mengyunzhi.spring;

import com.mengyunzhi.spring.service.TestService;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.ComponentScan;

@ComponentScan("com.mengyunzhi.spring")
public class Application {

    public static void main(String []args) {
        ApplicationContext context = new AnnotationConfigApplicationContext(Application.class);
        TestService testService1 = context.getBean(TestService.class);
        System.out.println(testService1);
        TestService testService2 = context.getBean(TestService.class);
        System.out.println(testService2);
    }
}

clipboard.png

我们发现这两个对象的内存地址是相同的,说明这两个对象引用的是同一块内存。

Scope

这就涉及到Bean对象的Scope属性,默认的Scope属性是Singleton,单例,全容器共享这个实例。

如果想使用多例,就将其Scope属性设置为Prototype

@Component
@Scope("prototype")
public class TestServiceImpl implements TestService {

    @Override
    public String toString() {
        return "我是TestServiceImpl, TestService的实现\n"
                + "我的内存地址是:"
                + super.toString();
    }
}

TestServiceImpl上加上@Scope注解,注意这里的prototype一定要是小写的。

再次运行:

clipboard.png

两个内存地址不同,这是两个独立的对象。

为容器添加对象

我们自己写的类,我们想将其加入到容器中,我们可以将其加上@Component注解将其添加到容器中,但是如果我们想把某个第三方库中的对象添加到容器中怎么办呢?我们可不能去改动依赖开源库的代码。

package com.mengyunzhi.spring;

public class OtherService {

    public void show() {
        System.out.println("我是一个第三方的库");
    }
}
package com.mengyunzhi.spring;

import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;

@Configuration
@ComponentScan("com.mengyunzhi.spring")
public class Application {

    public static void main(String []args) {
        ApplicationContext context = new AnnotationConfigApplicationContext(Application.class);
        OtherService otherService = context.getBean(OtherService.class);
        otherService.show();
    }

    @Bean
    OtherService getOtherService() {
        return new OtherService();
    }
}

@Configuration:表示这是一个Spring的配置类,然后就可以在其中配置相关Bean,通过@Bean注解,会将其标注的方法的返回值对象加入到容器中。

运行结果:

clipboard.png

总结

Spring IOC其实就是一个容器,我们常说的启动一个Spring项目其实就是所谓的加载容器(上下文)。

路漫漫其修远兮,让我们一起探索Spring的本质!

张喜硕
2.1k 声望423 粉丝

浅梦辄止,书墨未浓。