起因
欲实现路由验证,写了一个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("该路由非法");
}
}
}
但是测试一直通不过,报错:无法加载上下文。
打印了一下webAppMenuService
的值,发现全都是null
;果然,这个东西没注入进来。
看来@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
。
这是Spring
的七个模块,在IBM
的开发者学习文档中找到的。大致看了一下介绍,core
是Spring
的核心库,有各种功能,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
配置文件。类似下面这样。
一个bean
,id
是什么,对应的是哪个类,并且这个对象依赖于其他的什么对象。
看了一下,有个删除线,不建议使用,也就没有过深的研究,我们投入上下文的怀抱。
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
所管理,这么设计,不过是让人们看代码上的注解时,就能清晰地了解这个类的职责。
注意,这里加的@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
配置简单多了,@
几下就完成了配置)
运行结果:
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);
}
}
我们发现这两个对象的内存地址是相同的,说明这两个对象引用的是同一块内存。
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
一定要是小写的。
再次运行:
两个内存地址不同,这是两个独立的对象。
为容器添加对象
我们自己写的类,我们想将其加入到容器中,我们可以将其加上@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
注解,会将其标注的方法的返回值对象加入到容器中。
运行结果:
总结
Spring IOC
其实就是一个容器,我们常说的启动一个Spring
项目其实就是所谓的加载容器(上下文)。
路漫漫其修远兮,让我们一起探索
Spring
的本质!