IoC容器的初始化包括BeanDefinition的Resource定位、载入和注册这三个基本的过程。

一、Resource定位。BeanDefinition的资源定位有resourceLoader通过统一的Resource接口来完成,这个Resource对各种形式的BeanDefinition的使用提供了统一接口。对于这些BeanDefinition的存在形式,相信不陌生,如:

FileSystemResource、ClassPathResource。这个过程类似于容器寻找数据的过程,就像用水桶装水要把水找到一样。

二、第二个关键的部分是BeanDefinition的载入,该载入过程把用户定义好的Bean表示成IoC容器内部的数据结构,而这个容器内部的数据结构就是BeanDefinition。简单说,BeanDefinition实际上是POJO对象在IoC容器中的抽象,这个BeanDefinition定义了一系列的数据来使得IoC容器能够方便地对POJO对象也就是Spring的Bean进行管理。即BeanDefinition就是Spring的领域对象。

三、第三个过程是向IoC容器注册这些BeanDefinition的过程。这个过程是通过调用BeanDefinitionRegistry接口的实现来完成的,这个注册过程把载入过程中解析得到的BeanDefinition向IoC容器进行注册。可以看到,在IoC容器内部,是通过使用一个HashMap来持有BeanDefinition数据的。

总结:

此处对于BeanPostProcessor接口的调用应该属于高级应用了,该思路常用来解决扩展或集成Spring框架,其核心的思路可以分为以下几步:

1、自定义实现类路径扫描类,决定哪些类应该被注入进Spring容器。

2、采用Java动态代理来动态实现对于声明接口类的注入。

3、实现BeanDefinitionRegistryPostProcessor,在Spring初始化初期将需要扫描导入Spring容器的类进行注入。

 4、通过代码动态创建

代码动态创建

我们通过getBean来获得对象,但这些对象都是事先定义好的,我们有时候要在程序中动态的加入对象.因为如果采用配置文件或者注解,我们要加入对象的话,还要重启服务,如果我们想要避免这一情况就得采用动态处理bean,包括:动态注入,动态删除。

本节大纲 :
(1)动态注入bean思路;
(2)动态注入实现代码;
(3)多次注入同一个bean的情况;
(4)动态删除;

接下来我们看下具体的内容:

(1)动态注入bean思路;

在具体进行代码实现的时候,我们要知道,Spring管理bean的对象是BeanFactory,具体的是DefaultListableBeanFactory,在这个类当中有一个注入bean的方法:registerBeanDefinition,在调用registerBeanDefinition方法时,需要BeanDefinition参数,那么这个参数怎么获取呢?Spring提供了BeanDefinitionBuilder可以构建一个BeanDefinition,那么我们的问题就是如何获取BeanFactory了,这个就很简单了,只要获取到ApplicationContext对象即可获取到BeanFacory了。

(2)动态注入实现代码;

综上所述,如果我们要编写一个简单里的例子的话,那么分以个几个步骤进行编码即可进行动态注入了:

<1>. 获取ApplicationContext;
<2>. 通过ApplicationContext获取到BeanFacotory;
<3>. 通过BeanDefinitionBuilder构建BeanDefiniton;
<4>. 调用beanFactory的registerBeanDefinition注入beanDefinition;
<5>. 使用ApplicationContext.getBean获取bean进行测试;

很明显我们需要先定义个类进行测试,比如TestService代码如下:

package com.dxz.test;

public class TestService {

    private String name;

    public String getName() {
return name;
} public void setName(String name) {
this.name = name;
} public void print() {
System.out.println("动态载入bean,name=" + name);
} }

注意:这里没有使用@Service和配置文件进行注入TestService。

那么下面我们的目标就是动态注入TestService了,根据以上的分析,我们进行编码,具体代码如下:

package com.dxz.test;

import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ApplicationContext; @SpringBootApplication
public class Application { public static void main(String[] args) {
ApplicationContext ctx = SpringApplication.run(Application.class, args); // 获取BeanFactory
DefaultListableBeanFactory defaultListableBeanFactory = (DefaultListableBeanFactory) ctx
.getAutowireCapableBeanFactory(); // 创建bean信息.
BeanDefinitionBuilder beanDefinitionBuilder = BeanDefinitionBuilder.genericBeanDefinition(TestService.class);
beanDefinitionBuilder.addPropertyValue("name", "张三"); // 动态注册bean.
defaultListableBeanFactory.registerBeanDefinition("testService", beanDefinitionBuilder.getBeanDefinition()); // 获取动态注册的bean.
TestService testService = ctx.getBean(TestService.class);
testService.print();
}
}

或者

@SpringBootApplication
public class Application { public static void main(String[] args) {
ClassPathResource res = new ClassPathResource("beans.xml"); // 获取BeanFactory
DefaultListableBeanFactory factory= new DefaultListableBeanFactory(); // 创建bean信息.
XmlBeanDefinitionReader beanDefinitionBuilder = new XmlBeanDefinitionReader(factory);
beanDefinitionBuilder.loadBeanDefinitions(res);
//...
}
}

执行代码我们会在控制台看到如下打印信息:

动态载入bean,name=张三

到这里,就证明我们的代码很成功了。

(3)多次注入同一个bean的情况;

多次注入同一个bean的,如果beanName不一样的话,那么会产生两个Bean;如果beanName一样的话,后面注入的会覆盖前面的。

第一种情况:beanName一样的代码:

beanDefinitionBuilder = BeanDefinitionBuilder.genericBeanDefinition(TestService.class);
beanDefinitionBuilder.addPropertyValue("name","李四");
defaultListableBeanFactory.registerBeanDefinition("testService", beanDefinitionBuilder.getBeanDefinition());

运行看控制台:

动态载入bean,name=李四

第二种情况:beanName不一样的代码:

beanDefinitionBuilder = BeanDefinitionBuilder.genericBeanDefinition(TestService.class);
beanDefinitionBuilder.addPropertyValue("name","李四");
defaultListableBeanFactory.registerBeanDefinition("testService1",beanDefinitionBuilder.getBeanDefinition());

此时如果没有更改别的代码直接运行的话,是会报如下错误的:

Caused by: org.springframework.beans.factory.NoUniqueBeanDefinitionException: No qualifying bean of type [com.kfit.demo.service.TestService] is defined: expected single matching bean but found 2: testService1,testService

大体意思就是在getBean的时候,找到了两个bean,这时候就不知道要获取哪个了,所以在获取的时候,我们就要指定我们是要获取的testService还是testService1,只需要修改一句代码:

将代码:

TestService testService =ctx.getBean(TestService.class);
修改为:
TestService testService =ctx.getBean("testService");

(4)动态删除;

相对于动态注入,动态删除就很简单了,直接奉上代码:

//删除bean.
defaultListableBeanFactory.removeBeanDefinition("testService");

实现BeanDefinitionRegistryPostProcessor,在Spring初始化初期将需要扫描导入Spring容器的类进行注入

"对于Spring框架,现实公司使用的非常广泛,但是由于业务的复杂程度不同,了解到很多小伙伴们利用Spring开发仅仅是利用了Spring的IOC,即使是AOP也很少用,但是目前的Spring是一个大家族,形成了一个很大的生态,覆盖了我们平时开发的方方面面,抛开特殊的苛刻要求之外,Spring的生态其实已经很全面了,所以在此开个系列来研究下Spring提供给我们的一些平时不太却又很实用的内容。"

上一篇我们分析了BeanPostProcessor的基本使用,接下来我们分析下如何使用该类实现动态的接口注入,示例说明:在BeetlSQL框架中,在使用自动扫描注入时,我们通常只需要配置上要扫描的包路径,然后在该路径下声明对应的Dao接口类,这些接口类都默认继承BaseMapper接口类,然后我们在使用这些Dao类的时候,直接根据类型注入(@Autowired)即可使用,这个其实和Mybatis的那一套相似,也和Spring自身的Spring-data框架也类似。这个经常用于框架的开发,那么我就该部分的实现做相应的解释,这三个框架具体实现可能有差距,感兴趣的小伙伴自行去查看源码,我会以一个很简单的例子来讲解大概的实现逻辑。

问题描述:

继承Spring框架,实现声明某个自定义接口(UserMapper),改接口继承通用接口BaseMapper,(通用接口BaseMapper有默认的实现类),实现通过类型注入UserMapper类,然后通过Spring框架的上下文类(ApplicationContext实现类)的getBean()方法拿到UserMapper类来调用内部提供的方法。

1、声明BaseMapper接口类

package com.dxz.test;
public interface BaseMapper { public void add(String value); public void remove(String key);
} public class CustomBaseMapper implements BaseMapper {
private final Logger logger = Logger.getLogger(this.getClass().getName());
private List<String> dataList = new CopyOnWriteArrayList<>(); @Override
public void add(String value) {
logger.info("添加数据:" + value);
dataList.add(value);
} @Override
public void remove(String key) {
if (dataList.isEmpty())
throw new IllegalArgumentException("Can't remove because the list is Empty!");
}
}

接下来是继承Spring的核心代码

3、首先我们要先定义一个扫描某路径下的类,该类继承ClassPathBeanDefinitionScanner,自定义扫描类:DefaultClassPathScanner

package com.dxz.test;

import java.io.IOException;
import java.util.Arrays;
import java.util.Set; import org.springframework.beans.factory.annotation.AnnotatedBeanDefinition;
import org.springframework.beans.factory.config.BeanDefinitionHolder;
import org.springframework.beans.factory.config.RuntimeBeanReference;
import org.springframework.beans.factory.support.AbstractBeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.GenericBeanDefinition;
import org.springframework.context.annotation.ClassPathBeanDefinitionScanner;
import org.springframework.core.type.classreading.MetadataReader;
import org.springframework.core.type.classreading.MetadataReaderFactory;
import org.springframework.core.type.filter.TypeFilter; public class DefaultClassPathScanner extends ClassPathBeanDefinitionScanner { private final String DEFAULT_MAPPER_SUFFIX = "Mapper"; public DefaultClassPathScanner(BeanDefinitionRegistry registry) {
super(registry, false);
} private String mapperManagerFactoryBean; /**
* 扫描包下的类-完成自定义的Bean定义类
*
* @param basePackages
* @return
*/
@Override
protected Set<BeanDefinitionHolder> doScan(String... basePackages) {
Set<BeanDefinitionHolder> beanDefinitions = super.doScan(basePackages);
// 如果指定的基础包路径中不存在任何类对象,则提示
if (beanDefinitions.isEmpty()) {
logger.warn("系统没有在 '" + Arrays.toString(basePackages) + "' 包中找到任何Mapper,请检查配置");
} else {
processBeanDefinitions(beanDefinitions);
}
return beanDefinitions;
} /**
* 注册过滤器-保证正确的类被扫描注入
*/
protected void registerFilters() {
addIncludeFilter(new TypeFilter() {
@Override
public boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory)
throws IOException {
String className = metadataReader.getClassMetadata().getClassName();
// TODO 这里设置包含条件-此处是个扩展点,可以根据自定义的类后缀过滤出需要的类
return className.endsWith(DEFAULT_MAPPER_SUFFIX);
}
});
addExcludeFilter(new TypeFilter() {
@Override
public boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory)
throws IOException {
String className = metadataReader.getClassMetadata().getClassName();
return className.endsWith("package-info");
}
});
} /**
* 重写父类的判断是否能够实例化的组件-该方法是在确认是否真的是isCandidateComponent 原方法解释:
* 确定给定的bean定义是否有资格成为候选人。 默认实现检查类是否不是接口,也不依赖于封闭类。 以在子类中重写。
*
* @param beanDefinition
* @return
*/
protected boolean isCandidateComponent(AnnotatedBeanDefinition beanDefinition) {
// 原方法这里是判断是否为顶级类和是否是依赖类(即接口会被排除掉-由于我们需要将接口加进来,所以需要覆盖该方法)
return beanDefinition.getMetadata().isInterface() && beanDefinition.getMetadata().isIndependent();
} /**
* 扩展方法-对扫描到的含有BeetlSqlFactoryBean的Bean描述信息进行遍历
*
* @param beanDefinitions
*/
void processBeanDefinitions(Set<BeanDefinitionHolder> beanDefinitions) {
GenericBeanDefinition definition;
for (BeanDefinitionHolder holder : beanDefinitions) {
definition = (GenericBeanDefinition) holder.getBeanDefinition();
String mapperClassName = definition.getBeanClassName();
// 必须在这里加入泛型限定,要不然在spring下会有循环引用的问题
definition.getConstructorArgumentValues().addGenericArgumentValue(mapperClassName);
// 依赖注入
definition.getPropertyValues().add("mapperInterface", mapperClassName);
// 根据工厂的名称创建出默认的BaseMapper实现
definition.getPropertyValues().add("mapperManagerFactoryBean",
new RuntimeBeanReference(this.mapperManagerFactoryBean));
definition.setBeanClass(BaseMapperFactoryBean.class);
// 设置Mapper按照接口组装
definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);
logger.info("已开启自动按照类型注入 '" + holder.getBeanName() + "'.");
}
} public void setMapperManagerFactoryBean(String mapperManagerFactoryBean) {
this.mapperManagerFactoryBean = mapperManagerFactoryBean;
}
}

4、核心的接口实现类:BaseMapperFactoryBean

package com.dxz.test;

import java.lang.reflect.Proxy;

import org.springframework.beans.BeansException;
import org.springframework.beans.factory.FactoryBean;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.ApplicationEvent;
import org.springframework.context.ApplicationListener; public class BaseMapperFactoryBean<T>
implements FactoryBean<T>, InitializingBean, ApplicationListener<ApplicationEvent>, ApplicationContextAware {
/**
* 要注入的接口类定义
*/
private Class<T> mapperInterface; /**
* Spring上下文
*/
private ApplicationContext applicationContext; // 也因该走工厂方法注入得来 private BaseMapper mapperManagerFactoryBean; public BaseMapperFactoryBean(Class<T> mapperInterface) {
this.mapperInterface = mapperInterface;
} @Override
public T getObject() throws Exception {
// 采用动态代理生成接口实现类,核心实现
return (T) Proxy.newProxyInstance(applicationContext.getClassLoader(), new Class[] { mapperInterface },
new MapperJavaProxy(mapperManagerFactoryBean, mapperInterface));
} @Override
public Class<?> getObjectType() {
return this.mapperInterface;
} @Override
public boolean isSingleton() {
return true;
} @Override
public void afterPropertiesSet() throws Exception {
// TODO 判断属性的注入是否正确-如mapperInterface判空
if (null == mapperInterface)
throw new IllegalArgumentException("Mapper Interface Can't Be Null!!");
} /**
* Handle an application event.
*
* @param event
* the event to respond to
*/
@Override
public void onApplicationEvent(ApplicationEvent event) {
// TODO 可依据事件进行扩展
} @Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
} public void setMapperInterface(Class<T> mapperInterface) {
this.mapperInterface = mapperInterface;
} public void setMapperManagerFactoryBean(BaseMapper mapperManagerFactoryBean) {
this.mapperManagerFactoryBean = mapperManagerFactoryBean;
}
}

5、定义默认的BaseMapper的FactoryBean-MapperManagerFactoryBean

package com.dxz.test;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method; public class MapperJavaProxy implements InvocationHandler { private BaseMapper baseMapper; private Class<?> interfaceClass; public MapperJavaProxy(BaseMapper baseMapper, Class<?> interfaceClass) {
this.baseMapper = baseMapper;
this.interfaceClass = interfaceClass;
} @Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if (!interfaceClass.isInterface()) {
throw new IllegalArgumentException("mapperInterface is not interface.");
} if (baseMapper == null) {
baseMapper = new CustomBaseMapper();
}
return method.invoke(baseMapper, args);
}
}

7、调用时的核心配置类:DefaultClassRegistryBeanFactory

package com.dxz.test;

import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanNameAware;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.BeanDefinitionRegistryPostProcessor;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.ConfigurableApplicationContext; public class DefaultClassRegistryBeanFactory
implements ApplicationContextAware, BeanDefinitionRegistryPostProcessor, BeanNameAware { private String scanPackage; private String beanName; private String mapperManagerFactoryBean; private ApplicationContext applicationContext; public String getScanPackage() {
return scanPackage;
} public void setScanPackage(String scanPackage) {
this.scanPackage = scanPackage;
} public String getMapperManagerFactoryBean() {
return mapperManagerFactoryBean;
} public void setMapperManagerFactoryBean(String mapperManagerFactoryBean) {
this.mapperManagerFactoryBean = mapperManagerFactoryBean;
} @Override
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry beanDefinitionRegistry) throws BeansException {
if (StringUtils.isEmpty(this.scanPackage)) {
throw new IllegalArgumentException("scanPackage can't be null");
}
String basePackage2 = this.applicationContext.getEnvironment().resolvePlaceholders(this.scanPackage);
String[] packages = StringUtils.tokenizeToStringArray(basePackage2,
ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS);
DefaultClassPathScanner defaultClassPathScanner = new DefaultClassPathScanner(beanDefinitionRegistry);
defaultClassPathScanner.setMapperManagerFactoryBean(mapperManagerFactoryBean);
defaultClassPathScanner.registerFilters(); defaultClassPathScanner.doScan(packages);
} @Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory configurableListableBeanFactory)
throws BeansException { } @Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
} @Override
public void setBeanName(String name) {
this.beanName = name;
}
}

8、调用测试

8.1、假设你在包目录:colin.spring.basic.advanced.inject.dao下声明自定义的类UserMapper

public interface UserMapper extends BaseMapper {
}

8.2、声明配置类:ClassRegistryBeanScannerConfig

package com.dxz.test;

@Configuration
public class ClassRegistryBeanScannerConfig { @Bean(name = "mapperManagerFactoryBean")
public MapperManagerFactoryBean configMapperManagerFactoryBean() {
MapperManagerFactoryBean mapperManagerFactoryBean = new MapperManagerFactoryBean();
return mapperManagerFactoryBean;
} @Bean
public DefaultClassRegistryBeanFactory configDefaultClassRegistryBeanFactory() {
DefaultClassRegistryBeanFactory defaultClassRegistryBeanFactory = new DefaultClassRegistryBeanFactory();
defaultClassRegistryBeanFactory.setScanPackage("colin.spring.basic.advanced.inject.dao");
defaultClassRegistryBeanFactory.setMapperManagerFactoryBean("mapperManagerFactoryBean");
return defaultClassRegistryBeanFactory;
}
}

8.3、测试调用

package com.dxz.test;

public class Snippet {
public static void main(String[] args) {
AnnotationConfigApplicationContext acApplicationCOntext = new AnnotationConfigApplicationContext("colin.spring.basic.advanced.inject");
UserMapper userMapper = acApplicationCOntext.getBean(UserMapper.class);
userMapper.add("lalaldsf");
acApplicationCOntext.stop();
}
}

CXF之三 Tomcat中发布Web Service的更多相关文章

  1. CXF实战之在Tomcat中公布Web Service(二)

    服务接口及实现类请參考WebService框架CXF实战(一) 创建Maven Web项目,在pom.xml中加入CXF和Spring Web的引用,因为CXFServlet须要Spring Web的 ...

  2. tomcat发布web service教程

    这几天一直在准备找工作,自学了关于web service的一些基本的内容,也遇到了不少问题.现在就把我自己学到的知识和大家分享一下,由于是初学,所以有什么错误的地方请大家帮忙指正,感激不尽~~!! 1 ...

  3. 如何在Eclipse或者Myeclipse中使用tomcat(配置tomcat,发布web项目)?(图文详解)(很实用)

    前期博客 Eclipse里的Java EE视图在哪里?MyEclipse里的Java EE视图在哪里?MyEclipse里的MyEclipse Java Enterprise视图在哪里?(图文详解) ...

  4. Eclipse+tomcat+axis2进行web service部署

    用Eclipse+axis2+tomcat进行web service部署 2016-12-07 目录  1 安装JDK  1.1 下载JDK  1.2 安装和配置JDK  1.3 验证2 安装Ecli ...

  5. 用JAX-WS在Tomcat中发布WebService

    JDK中已经内置了Webservice发布,不过要用Tomcat等Web服务器发布WebService,还需要用第三方Webservice框架.Axis2和CXF是目前最流行的Webservice框架 ...

  6. Apache CXF实战之四 构建RESTful Web Service

    Apache CXF实战之一 Hello World Web Service Apache CXF实战之二 集成Sping与Web容器 Apache CXF实战之三 传输Java对象 这篇文章介绍一下 ...

  7. 新手Axis2 发布Web Service之路

    由于公司的需求,需要写几个银行接口写模拟器(Mock Server),此次接口需要发布成一个WEB Service. 一开始,我以为只要负责写接口的业务层就行了,具体的框架或是环境搭建可以不用管.在与 ...

  8. 在Tomcat中部署Web项目的操作方法,maven项目在Tomcat里登录首页报404

     maven项目在Tomcat里登录首页报404, 解决:编辑conf/server.xml进行配置<Host>里的<Context>标签里的path. <Context ...

  9. 使用Apache CXF和Spring集成创建Web Service(zz)

    使用Apache CXF和Spring集成创建Web Service 您的评价:       还行  收藏该经验       1.创建HelloWorld 接口类 查看源码 打印? 1 package ...

随机推荐

  1. String声明为NULL和""的区别

    代码虐我千百遍,我待代码如初恋. String 声明为 NULL 则声明了一个变量不指向任何一块地址,则 length()会出现错误. 声明为"",则是一个长度为0的字符串.

  2. bnu 4351 美女来找茬(水水)

    http://www.bnuoj.com/bnuoj/problem_show.php?pid=4351 [题意]:用最小的矩形框,框住像素点差超过5的点. [题解]:求坐标x,y最大最小值 [cod ...

  3. 实时数据处理环境搭建flume+kafka+storm:1.zookeeper 安装配置

    1. 解压 tar -zxvf 2.创建目录  zk根目录创建         mkdir zkdatalog  --日志          mkdir zkdata  ---快照文件 3.修改配置文 ...

  4. 1027: [JSOI2007]合金 - BZOJ

    Description 某公司加工一种由铁.铝.锡组成的合金.他们的工作很简单.首先进口一些铁铝锡合金原材料,不同种类的原材料中铁铝锡的比重不同.然后,将每种原材料取出一定量,经过融解.混合,得到新的 ...

  5. MapperScannerConfigurer(转)

    转:http://blog.csdn.net/ranmudaofa/article/details/8508028 原文:http://www.cnblogs.com/daxin/p/3545040. ...

  6. windows8 64位 IIS8 PHP5.5 安装 Imagemagick 组件

    为什么这里一定要说 windows 系统是64位呢,因为如果是系统是64位,那么PHP5.5 一般都会选择64的, Imagemagick 组件也会选择64位的, 但是操蛋的是 64位的Imagema ...

  7. java Collections.sort()实现List排序的默认方法和自定义方法

    1.java提供的默认list排序方法 主要代码: List<String> list = new ArrayList();list.add("刘媛媛"); list. ...

  8. windows命令行编码与nodejs编码格式冲突的解决方式

    今天写一个工具,由于大部分人使用的机器都是windows,在和nodejs结合的时候出问题了. win命令行的编码格式是gbk,而nodejs支持的编码只有:utf8 ascii和base64,必须让 ...

  9. 性能优化工具 MVC Mini Profiler

    性能优化工具 MVC Mini Profiler   MVC MiniProfiler是Stack Overflow团队设计的一款对ASP.NET MVC.WebForm 以及WCF 的性能分析的小程 ...

  10. objective C中数据持久化方式1--对象归档

    第一.数据持久化的方式: NSKeyedArchiver--对象归档 属性列表化(NSArray.NSDictionary.NSUserDefault) SQlite数据库.CoreData数据库 其 ...