Spring
本文是对自己实现Spring核心IOC和AOP的一个简单的介绍。
Spring是一个轻量级的控制反转(IoC)和面向切面(AOP)的容器框架。
控制反转——Spring通过一种称作控制反转(IoC)的技术促进了松耦合。当应用了IoC,一个对象依赖的其它对象会通过被动的方式传递进来,而不是这个对象自己创建或者查找依赖对象。你可以认为IoC与JNDI相反——不是对象从容器中查找依赖,而是容器在对象初始化时不等对象请求就主动将依赖传递给它。
面向切面——Spring提供了面向切面编程的丰富支持,允许通过分离应用的业务逻辑与系统级服务(例如审计(auditing)和事务(transaction)管理)进行内聚性的开发。应用对象只实现它们应该做的——完成业务逻辑——仅此而已。它们并不负责(甚至是意识)其它的系统级关注点,例如日志或事务支持。
容器——Spring包含并管理应用对象的配置和生命周期,在这个意义上它是一种容器,你可以配置你的每个bean如何被创建——基于一个可配置原型(prototype),你的bean可以创建一个单独的实例或者每次需要时都生成一个新的实例——以及它们是如何相互关联的。然而,Spring不应该被混同于传统的重量级的EJB容器,它们经常是庞大与笨重的,难以使用。
(以上对于Spring的理解,摘自于百度百科)
百度百科给的解释有些官方话,下面来说说我自己对于Spring的理解。
IOC:我们在编写java程序时,总是需要new来生成一个新的对象,我们如果还需要它时,我们就再重新new一个出来,如果不需要,有GC帮助我们进行回收,新对象的生成与否全是由我们自己决定。然而,对于IOC,对象的生成不需要由我们考虑,我们不需要写生成新对象的代码,对象已经自动生成好了,我们直接get就好。对象的控制权完全交由第三方,我们只有使用权,这就是所谓的控制反转。
给个例子:
public class TwoClass {
public TwoClass() {
}
@Override
public String toString() {
return "这是一个TwoClass的对象";
}
}
public class OneClass {
private TwoClass two;
public OneClass() {
}
public void doOneThing() {
System.out.println(two);
}
}
public class Demo {
public static void main(String[] args) {
OneClass one = new OneClass();
one.doOneThing();
}
}
这里的执行结果肯定为null;是因为OneClass类的two成员没有被初始化;
如果想这么一种办法:对于OneClass类中的two成员,通过一种工具,自动地,悄悄地完成对two成员的初始化(注入),那么,Demo类的主函数执行结果就完美了!其实这就是IOC。
AOP:事实上就是由代理技术和拦截器实现的。在Spring中是利用JDKProxy代理技术以及CGLibProxy代理技术实现的。代理技术是基于类的,而不是基于对象的,因为对象是不确定的。将代理技术(基于类)和拦截器封装起来,可以实现对某个方法进行拦截。拦截器拦截的是方法的调用。
此后,我将对IOC和AOP的核心进行模拟实现。
IOC
IOC中存在着一个Bean容器(工厂),这个容器里存放着用户已经配置好的类。想要把这些类放到一个Bean容器中,不得不对用户自己定义的类进行扫描。到目前为止,我已经掌握了的扫描方式有Xml文件的扫描和包扫描(搭配注解技术)。
先来实现包扫描(搭配注解技术)
为此,我们先引出四个注解Component 、 AutoWire、Bean和Qualifier:
@Retention(RUNTIME)
@Target(TYPE)
public @interface Component {
String name() default "";
}
@Retention(RUNTIME)
@Target(FIELD)
public @interface Autowired {
String name() default "";
// Spring的Autowired注解,比我现在定义的更加丰富,比如:
// boolean lazy() default true; // 懒汉模式
// boolean singleton() default true; // 单例模式
// boolean request() default true; // 注入时是否允许为null
}
@Retention(RUNTIME)
@Target(METHOD)
public @interface Bean {
String name() default "";
}
@Retention(RUNTIME)
@Target({ FIELD, METHOD, PARAMETER })
public @interface Qualifier {
String name();
}
对以上的四个注解做一个解释。
Component注解:这是对类的注解。只要一个类有Component注解,那么表示这个类将被放入Bean容器中。当扫描每个带有Component 注解的类的时候,我们会采用beanDefinition类对这个类进行封装,同时放到Bean容器。
public class BeanDefinition {
private Class<?> klass;
private Object object;
private boolean inject;
BeanDefinition() {
this.inject = false;
}
boolean isInject() {
return inject;
}
void setInject(boolean inject) {
this.inject = inject;
}
Class<?> getKlass() {
return klass;
}
void setKlass(Class<?> klass) {
this.klass = klass;
}
Object getObject() {
return object;
}
void setObject(Object object) {
this.object = object;
}
@Override
public String toString() {
return klass.getSimpleName() + " : " + object;
}
}
上述说到的所谓的封装,其实就是对这个类的一些属性进行提取并设置到这个类对应的BeanDefinitio类里面。在这里,就是要把Class成员和Object成员进行设置,inject成员默认为false(该类还尚且没有注入)。
Autowired注解:对要注入的成员的注解(也可以通过set方法得到该类)。一个成员有该注解,那么说明这个成员需要被注入。有可能该成员是一个类类型的成员,其的内部还有一个成员也有Autowired注解,也需要被注入但是目前还没有被注入,那么我们就需要先将那个内部成员注入,之后才能对该成员进行注入。这时,这个Autowired注解就对应了一个Component注解。这就会涉及到一个很复杂的问题:循环注入。各个类之间彼此循环依赖,这个问题我们在后面解决。
Bean注解:这个注解是用在方法身上的。如果我们想对jar包里的类进行注入的话,我们是不可能对jar文件进行修改的,也就是说,用之前的Component注解是没有用的。这时,我们可以在某个类里面写一个方法,该方法的返回值是jar文件里的一个要被注入的类。对该类加上Component注解,对该方法加上Bean注解。执行该方法时,其返回值就是该类的对象,可以作为object设置到BeanDefinition里面,object.getClass()可以得到其类型,也可以设置到BeanDefinition里面,最后把BeanDefinition装到容器里面。
Qualifier注解:这个注解使用在方法或参数身上的,可以方便用户写别名。
现在,我们来看看Bean容器吧。Bean容器其实就是一个Map,里面的键是String类型,值是BeanDefinition类型。
public class BeanFactory {
// 这里的beanPool就是Bean容器(工厂)
private static final Map<String, BeanDefinition> beanPool;
static {
beanPool = new HashMap<String, BeanDefinition>();
}
private static boolean firstGetBean = true;
public BeanFactory() {
}
// 包扫描技术,请看之前的博文
public void scanBeanByPackage(String packageName) {
OnReadyBeanMethodDefinition orbmd = new OnReadyBeanMethodDefinition();
ParameterDependance parameterDependance = new ParameterDependance();
new PackageScanner() {
@Override
public void dealClass(Class<?> klass) {
if (klass.isPrimitive()
|| klass.isInterface()
|| klass.isAnnotation()
|| klass.isEnum()
|| klass.isArray()
|| !klass.isAnnotationPresent(Component.class)) {
return;
}
Object object = null;
try {
object = klass.newInstance();
BeanDefinition bd = new BeanDefinition();
bd.setKlass(klass);
bd.setObject(object);
beanPool.put(klass.getName(), bd);
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
}.packageScanner(packageName);
}
@SuppressWarnings("unchecked")
public <T> T getBean(String klassName) throws RuntimeException {
BeanDefinition bd = getBeanDefinition(klassName);
if (bd == null) {
return null;
}
return (T) bd.getObject();
}
public <T> T getBean(Class<?> klass) throws RuntimeException {
return getBean(klass.getName());
}
}
解释一下这个BeanFactory类,这个类通过scanBeanByPackage()方法,将有注解的类都进行简单的newInstance()操作,并放到map里面,之后,我们可以在外面通过getBean()方法进行获取。
AOP
对于aop的实现,因为我们需要对方法进行拦截,所以先要有拦截器,还要能将拦截器和被拦截的方法之间映射起来,最后,我们自己决定选择什么样的代理,去执行拦截,使得这个拦截起作用。
整套思路也就是这样,一共有三大步骤:
1.生成拦截器
2.生成映射关系
3.生成相关代理
4.将上述三点串联起来,可以通过代理,调用拦截器去拦截需要被拦截的方法。
对于拦截器,我们需要通过建立两条链来表示前置拦截链和后置拦截链。对于映射关系,我们需要将映射关系新生成一个类,将这个类加上注解或通过xml配置的方式,扫描到这个类的方法。对于前置拦截、后置拦截等的方法,我们需要将该方法加上注解或配置xml,用他们来生成一个拦截器。对于代理,我们之前已经实现了代理机制工具工具里面的缺点是不能形成拦截器链,所以,我们将对拦截工具的代码进行稍微的修改,可以把拦截器添加进去。
在后面的博文中,我会详细讨论AOP的拦截器的生成过程,生成拦截器与被拦截方法之间的映射关系的过程中遇到的难处。
上述仅仅是对IOC和AOP的一个简单的介绍,之后的博文会把这些思想进行更深一步的挖掘,会实现更加深邃和复杂的操作。