spring的三大组件:
1.bean:bean的定义,bean的创建已及对bean的解析
2.context:给 spring 提供一个运行的环境(连接上下文)
3.core:类似于utility类,定义了资源的访问方式
接下来直接从代码来看:
1.启动:
(1)web.XML中配置spring(开发环境一般都是这种)
(2) ApplicationContext手动加载(测试)
因为是个人实验,所以我选择第(2)种方式.
2.总流程:
读取ClassPathXmlApplicationContext.xml 文件-->按照<bean>标签通过bean工厂创建bean仓库-->按照key值返回bean对象
这里要提一点:仓库是final修饰,只在第一次赋值,之后为只读状态.但是StringBuffer和list可以进行修改,因为绑定的是地址而不是值.
3.言归正传,我们先来看一下他是怎么读取xml文件的:
(1)先来一个总的:
解读:调用构造方法创建XmlBeanDefinitReader对象,调用方法加载资源获取bean仓库对象
接下来看他是怎么加载资源的:
public XmlBeanDefinitionReader(ResourceLoader resourceLoader) { super(resourceLoader); }
XmlBeanDefinitionReader 实例化类的时候,因为继承了抽象类AbstractBeanDefinitionReader
public class XmlBeanDefinitionReader extends AbstractBeanDefinitionReader {}
所以他会在初始化的时候先初始化父类,创建一个Map作为bean 仓库.
protected AbstractBeanDefinitionReader(ResourceLoader resourceLoader) { this.registry = new HashMap<String, BeanDefinition>(); this.resourceLoader = resourceLoader; }
//获取外部资源,把xml文件转成输入流
@Override public void loadBeanDefinitions(String location) throws Exception { InputStream inputStream = getResourceLoader().getResource(location).getInputStream(); doLoadBeanDefinitions(inputStream); }
protected void doLoadBeanDefinitions(InputStream inputStream) throws Exception { DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); DocumentBuilder docBuilder = factory.newDocumentBuilder(); Document doc = docBuilder.parse(inputStream);//把输入流包装到 Document 类对象 // 注册bean registerBeanDefinitions(doc); inputStream.close(); }
这里提一下newInstance()方法,主要是与new的比较
new关键字是强类型的,效率相对较高。
newInstance()是弱类型的,效率相对较低。只能调用无参构造方法
例子: 既然使用newInstance()构造对象的地方通过new关键字也可以创建对象,为什么又会使用newInstance()来创建对象呢?
假设定义了一个接口Door,开始的时候是用木门的,定义为一个类WoodenDoor,在程序里就要这样写 Door door = new WoodenDoor() 。假设后来生活条件提高,换为自动门了,定义一个类AutoDoor,这时程序就要改写为 Door door = new AutoDoor() 。虽然只是改个标识符,如果这样的语句特别多,改动还是挺大的。于是出现了工厂模式,所有Door的实例都由DoorFactory提供,这时换一种门的时候,只需要把工厂的生产模式改一下,还是要改一点代码。
而如果使用newInstance(),则可以在不改变代码的情况下,换为另外一种Door。具体方法是把Door的具体实现类的类名放到配置文件中,通过newInstance()生成实例。这样,改变另外一种Door的时候,只改配置文件就可以了。示例代码如下:
String className = 从配置文件读取Door的具体实现类的类名;
Door door = (Door) Class.forName(className).newInstance();
再配合依赖注入的方法,就提高了软件的可伸缩性、可扩展性。
public void registerBeanDefinitions(Document doc) { Element root = doc.getDocumentElement(); parseBeanDefinitions(root); }
//把bean转换成beanDefinition对象
protected void parseBeanDefinitions(Element root) { NodeList nl = root.getChildNodes(); for (int i = 0; i < nl.getLength(); i++) { Node node = nl.item(i); if (node instanceof Element) { Element ele = (Element) node; processBeanDefinition(ele); } } }
//装配bean,并且存库里面
protected void processBeanDefinition(Element ele) { String name = ele.getAttribute("id"); String className = ele.getAttribute("class"); BeanDefinition beanDefinition = new BeanDefinition(); processProperty(ele, beanDefinition); //把类实例化,然后放到beanDefinition里面 beanDefinition.setBeanClassName(className); getRegistry().put(name, beanDefinition); }
//把属性加到propertyValues,如果有ref(依赖其他类),就把依赖类对象加到propertyValues
private void processProperty(Element ele, BeanDefinition beanDefinition) { NodeList propertyNode = ele.getElementsByTagName("property"); for (int i = 0; i < propertyNode.getLength(); i++) { Node node = propertyNode.item(i); if (node instanceof Element) { Element propertyEle = (Element) node; String name = propertyEle.getAttribute("name"); String value = propertyEle.getAttribute("value"); if (value != null && value.length() > 0) { beanDefinition.getPropertyValues().addPropertyValue(new PropertyValue(name, value)); } else { String ref = propertyEle.getAttribute("ref"); if (ref == null || ref.length() == 0) { throw new IllegalArgumentException("Configuration problem: <property> element for property '" + name + "' must specify a ref or value"); } BeanReference beanReference = new BeanReference(ref); beanDefinition.getPropertyValues().addPropertyValue(new PropertyValue(name, beanReference)); } } } }
这样就创建了ioc容器了,他的主要思想就是服务刚启动的时候把xml文件里面的bean所对应的类加载(注意是加载,而不是实例化类对象)到一个类库,类库是一个map<String(类名),BeanDefinition(类定义)>.
4.按照类名获取实例化类对象
先上一段测试代码:
@Test
public void test() throws Exception {
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("tinyioc.xml");
HelloWorldService helloWorldService = (HelloWorldService) applicationContext.getBean("helloWorldService");
helloWorldService.helloWorld();
}
我们着重来看下getBean方法
@Override
public Object getBean(String name) throws Exception {
return beanFactory.getBean(name);
}
继续往下走
public Object getBean(String name) throws Exception {
BeanDefinition beanDefinition = beanDefinitionMap.get(name);
if (beanDefinition == null) {
throw new IllegalArgumentException("No bean named " + name + " is defined");
}
Object bean = beanDefinition.getBean();
if (bean == null) {
bean = doCreateBean(beanDefinition);
bean = initializeBean(bean, name);
beanDefinition.setBean(bean);
}
return bean;
}
从上述代码来看,我们可以大胆的推测,beanDefinitionMap 就是我们类库,我们先是去类库找,如果没有的话去创建类定义对象塞进去.
现在我们来验证这个推论
这个肯定是在创建ioc容器的时候进行初始化的,先找到初始化的方法
public void registerBeanDefinition(String name, BeanDefinition beanDefinition) throws Exception {
beanDefinitionMap.put(name, beanDefinition);
beanDefinitionNames.add(name);
}
继续往回找调用的方法
public void refresh() throws Exception {
loadBeanDefinitions(beanFactory);
registerBeanPostProcessors(beanFactory);
onRefresh();
}
public ClassPathXmlApplicationContext(String configLocation, AbstractBeanFactory beanFactory) throws Exception {
super(beanFactory);
this.configLocation = configLocation;
refresh();
}
public ClassPathXmlApplicationContext(String configLocation) throws Exception {
this(configLocation, new AutowireCapableBeanFactory());
}
找到了,就是在 ClassPathXmlApplicationContext 初始化的同时把 beanDefinitionMap 初始化
接下来我们继续beanDefiniti
onMap 里面按照配置文件加载相应的类
因为之前只是加载的类并没实例化,所以调用方法实例化并且保存在beanDefinition 对象中
这就实现了根据类名返回实现类
AOP实现:
首先,在读取xml文件的时候,会加载一些静态常量参数,如下图:
然后我们来说aop拦截器的实现:
先上一段代码:
从上面我们看到,他说先会去匹配expression,然后再按照正则表达式去拦截相应的方法(代码太长不贴了).
也就是说我们已经找到切入点(joinPoint)了.
接下来我们来看aop的织入器实现:
aop的实现是生成代理类,然后在将织入器织入,
aop 分为两种:静态AOP和动态AOP
静态织入:
a、原理:在编译期,切面直接以字节码形式编译到目标字节码文件中 ;
b、优点:对系统性能无影响;
c、缺点:不够灵活;
动态代理 :
a、原理:在运行期,目标类加载后,为接口动态生成代理类。将切面织入到代理类中;
b、优点:更灵活;
c、缺点:切入的关注点要实现接口;
Joinpoint:拦截点,如某个业务方法;
Pointcut:Jointpoint的表达式,表示拦截哪些方法。一个Pointcut对应多个Joinpoint;
Advice:要切入的逻辑。
Before Advice:在方法前切入;
After Advice:在方法后切入,抛出异常时也会切入;
After Returning Advice:在方法返回后切入,抛出异常不会切入;
After Throwing Advice:在方法抛出异常时切入;
Around Advice:在方法执行前后切入,可以中断或忽略原有流程的执行;
aop动态代理:
1. 在使用动态代理类时,我们必须实现InvocationHandler接口
2.从代码看
//生成代理类
public Object getProxy() {
return Proxy.newProxyInstance(getClass().getClassLoader(), advised.getTargetSource().getInterfaces(), this);
}
首先普及一下基础知识:
getClassLoader():类加载器,实现了把.class文件从内存加载到jvm,xx. getClassLoader().getInstance()才是返回的是类对象
然后对生成方法的参数进行分析:
这里为什么要是用接口:
当调用代理类的方法时与调用被代理类的方法时在写法上是没有任何区别的,只有接口才能保证这种一致性。(这里跟动态字节码有关系,稍后回来更新)
3.当我们执行被代理类的方法的时候,会由代理类去执行invoke()方法,然后可以在方法执行前和执行后加入advice(织入逻辑).