spring的三大组件:node
1.bean:bean的定义,bean的建立已及对bean的解析web
2.context:给 spring 提供一个运行的环境(链接上下文)正则表达式
3.core:相似于utility类,定义了资源的访问方式spring
接下来直接从代码来看:express
1.启动:app
(1)web.XML中配置spring(开发环境通常都是这种)jvm
(2) ApplicationContext手动加载(测试)ide
由于是我的实验,因此我选择第(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;
}
从上述代码来看,spring 采用的是延迟加载,只要当类用到的时候才会去调用doCreateBean(beanDefinition)方法去建立实例化类。
由于以前只是加载的类并没实例化,因此调用方法实例化而且保存在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(织入逻辑).