目录
spring webflux在流程上和spring MVC的区别
Spring是什么
spring是一个轻量级的JavaEE框架,对原生的JAVA EE进行了一次封装
包含了spring IOC 、spring Aop、sprng Web、 spring Core、spring Context、spring data、spring orm七大模块
Spring IOC
IOC是一种控制反转的思想、将对象的创建和销毁交由spring IOC容器进行管理,IOC容器就是对象工厂,ioc在进行创建被调用哲实例时候,需要进行依赖注入,而实现依赖注入有三种方法se方法注入、构造器注入、p命名空间注入
Spring IOC 容器的实现是通过两种方式(两个接口)
BeanFactory:IOC的基础实现,是spring内部的使用接口,不提供开发人员的使用,它在加载配置文件的时候,是不会创建对象的,在获取对象的时候才会创建对象
ApplicationContext:是BeanFactory的子接口,提供更多的功能,它在加载配置文件的时候,就会创建对象
ApplicationContext的三个实现类
ClassPathXmlApplicationContext、
它可以加载类路径下的配置文件,要求配置文件必须在类路径下。
AnnotationConfigApplicationContext
它可以加载磁盘任意路径下的配置文件
FileSystemXmlApplicationContext
它是用于读取注解创建容器的
大致从源码分析而出
初始化bean的大概流程就是在初始化bean的实例时候,判断是否写了构造方法,没有的话,是会通过反射调用无参,如果有的会获取所有候选的构造方法,然后返回参数个数,并将参数个数封装成TypeStringValue,且会针对public和参数个数来做一个优先级的排列,遍历候选构造方法并转换参数类型为目的类型,来找到合适的构造方法,最后基于找出的构造方法和参数创建实例,并设置到BeanWrapper中
bean管理
这样管理bean的过程可以细分为两个部分,一个部分是创建bean,另一个部分是对bean的属性进进行一个依赖注入
创建bean可以通过xml文件,中创建bean标签,标签里添加对应属性
依赖注入,通过xml格式的话:
通过set方法注入、
<bean id="类名" class="类的全路径">
<property name="" value=""></property>
</bean>
通过有参构造器注入
<bean id="" class="">
<constructor-arg name="" value=""></constructor-arg>
</bean>
通过p命名名称空间注入
<bean id="" class="" p:bname="" p:bauthor="">
</bean>
如何通过xml注入外部属性
<bean id="userService" class="com.alpaca.service">
<property name="userService" ref="userServiceImpl"></property>
</bean>
<bean id="userServiceImpl" class="com.alpaca.service.impl"></bean>
如何通过xml注入内部bean
<bean id="userService" class="com.alpaca.service">
<property name="" value=""></property>
<property name="userService" >
<bean id="" class="">
<property name="" value=""></property>
</bean>
</property>
</bean>
通过集合注入,就不再赘述
bean类型
大致分为两种bean,一种是普通的bean,另一种是FactoryBean
普通的bean就是自己创建的bean:而且在配置问价中定义的bean就是他们的返回类型
FactoryBean:在配置文件中定义的bean类型和他们返回的类型是不一致的,它的主要作用就是简化bean的实例过程
bean的作用域
singleton 、prototype、request、session
bean的生命周期
1.通过构造器创建bean的实例(无参数构造)
2.为bean的属性设置值和对其他bean引用(调用set方法)
3. 调用bean的初始化方法(需要进行配置初始化的方法)
4. bean可以使用了(也就是可以获取到了)
5.当容器关闭的时候,调用bean的销毁方法(需要配置销毁的方法)
但是调用bean的销毁方法并不是真正的销毁bean,需要手动对bean进行一个bean的销毁
((ClassPathXMLContext) context).close();
bean 的后置处理器BeanPostProcessor
可以通过实现这个接口,对每一个bean进行初始化之前要做的和初始化之后要做的事,进行一个增强
@Autowire:针对属性类型进行注入
@Qualifier:针对属性名字进行注入,需要搭配@Autowire一起使用
@Resource:可以针对属性名字进行注入,也可以通过属性类型进行注入
@Value:普通属性进行注入
Spring AOP
aop是面向切面编程,aop可以针对业务逻辑的各个部分进行一个隔离,从而使得业务的耦合度降低,从而使得代码的重用性提高
底层实现
第一种,通过JDK动态代理
通过创建接口实现类的代理对象,来对对象进行一个增强
创建接口实现类的代理类
直接创建一个匿名内部类来进行对象的增强
public class JDKProxy{
public static void main(String[] args){
Class[] interface={UserService.class};
Proxy.newInstance(JDKProxy.class.getClassLoader,interfaces,new InvocationHandler{
public Object invoke(Object obj,Method method,Object[] args){
//方法之前要做的事
//方法的执行
method.invoke(obj,args);
//方法之后要做的事
}
});
}
}
通过创建一个实现类来做实际代理
public class ProxyClass implements InvocationHandler{
private Object obj;
public ProxyClass(Object obj){
this.obj=obj;
}
public Object invoke(Object obj,Method method,Object[] args){
//方法之前要做的事
//方法的执行
method.invoke(obj,args);
//方法之后要做的事
}
}
第二种,通过CGLIB动态代理
在没有接口的时候,使用cglib动态代理,可以通过创建当前类的子类的代理对象,实现增强
Spring AOP操作术语
连接点
类中的那些方法可以被增强,这些方法被称为连接点
切入点
类中实际被增强的方法,称为切入点
切面
执行的具体业务逻辑
织入
将通知应用到目标对象,来创建新的代理对象的过程
编译期织入,这要求使用特殊的Java编译器。
类装载期织入,这要求使用特殊的类装载器。
动态代理织入,在运行期为目标类添加增强生成子类的方式。
Spring采用动态代理织入,而AspectJ采用编译期织入和类装载期织入。
通知
实际增强的逻辑部分,被称为通知
通知可以被分为
开启aspect生成代理对象
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>
通过注解的方式
@EnableAspectjAutoProxy(proxyTargetClass=true)
前置通知
基于注解实现
public class Sloution{
@Before(value="execution(* com.alpaca.aop.User.add(..))")
public void before(){
System.out.println("before .... ");
}
}
基于xml实现
<bean id="userService" class="com.alpaca.aop.UserService"></bean>
<bean id="userServicePorxy" class="com.alpaca.aop.UserServiceProxy"></bean>
<aop:config>
<!-- 切入点-->
<aop:pointcut id="p" expression="* com.alpaca.aop.UserService.buy(..)" />
<!-- 切面-->
<aop:aspect ref="userServiceProxy">
<!-- 具体实现的方法-->
<aop:before method="before" pointcut-ref="p"></aop:before>
</aop:aspect>
</aop:config>
后置通知
基于注解实现
public class Sloution{
@After(value="execution(* com.alpaca.aop.User.add(..))")
public void after(){
System.out.println("after .... ");
}
}
环绕通知
基于注解实现
public class Sloution{
@Around(value="execution(* com.alpaca.aop.User.add(..))")
public void around(){
System.out.println("around.... ");
}
}
异常通知
基于注解实现
public class Sloution{
@AfterThrowing(value="execution(* com.alpaca.aop.User.add(..))")
public void afterThrow(){
System.out.println("throw.... ");
}
}
注解中的切入点,可以声明一个公共的切入点 ,也就是抽取公共的切入点
public class slution{
@PoinCut(vlaue="execution(* com.alpaca.aop.User.add)")
public void demo(){
}
@Before(value="demo")
public void before(){
System.out.println("before .... ");
}
}
如果有多个增强类对同一个方法进行增强,可以设置优先级
通过@Order(1) 数字越小,优先级越高
切面
是动作,将通知应用到切入点中
具体实现
通过AspectJ 和 SpringAOP搭配使用
Spring JDBC(不再细说)
通过jdbcTemplate实现增删改查
配置数据源
创建JDBCTemplate对象
<!-- 设置数据源 -->
<bean class="com.alibaba.druid.pool.DruidDataSource" id="dataSource">
<property name="username" value="${jdbc-username}"></property>
<property name="password" value="${jdbc-password}"></property>
<property name="driverClassName" value="${jdbc-driverClassName}"></property>
<property name="url" value="${jdbc-url}"></property>
</bean>
<!-- 配置spring事务管理器 -->
<bean class="org.springframework.jdbc.datasource.DataSourceTransactionManager"
id="DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"></property>
</bean>
Spring orm
orm
对象和数据库之间的映射框架,早期的Hibernate和现在的Mybatis。MybatisPlus,
其实现的规范也就是JPA(sun官方提出的Java持久化规范)
JPA
它本质上也就是一种orm规范(注意:是规范不是框架),他只提供了相应规范的接口,也就是api
Spring Context
Spring容器的上下文,在soring启动时,会加载所有的bean,将其定义和封装
大体刷新流程
首先准备刷新上下文环境,初始化并加载定义信息(初始化BeanFactory、并加载bean definitions信息)
然后对beanFactory进行系统配置,提交给BeanFactoryPostProcessors,初始化信息资源,也就是MessageSource、事件广播器、注册事件监听器,最后构造加载单例的Bean,通知生命周期处理器,如果刷新错误就销毁并且修改bean
Spring 事务
事务数据库逻辑上的一组操作,要么都执行,要么都不执行
主要表现为ACID四种特性
原子性:这组操作,不可分割,要么都执行,要么都不执行
一致性:数据库数据的完整性,在事务之前和事务之后,都没有被破坏
隔离性:防止事务在数据库并发执行时,由于相互交叉执行,导致数据不一致
持久性:数据库数据在修改后是永久的,即使数据库宕机
事务的七种传播行为
指当前事务被执行时,采用哪种方式
对事务的要求程度从大到小排序:
mandatory / supports / required / requires_new / nested / not supported / never
PROPAGATION_REQUIRED:如果当前没有事务,就创建一个新事务,有的话就加入,是Spring默认的事务传播行为。
PROPAGATION_SUPPORTS:如果当前存在事务,就加入该事务,如果当前不存在事务,就以非事务执行。(supports支持,有则加入,没有就不管了,非事务运行)
PROPAGATION_MANDATORY:如果当前存在事务,就加入该事务,如果当前不存在事务,就抛出异常。(mandatory强制性,有则加入,没有异常)
PROPAGATION_REQUIRES_NEW:无论当前存不存在事务,都创建新事务。(requires_new需要新的,不管有没有,直接创建新事务)
PROPAGATION_NOT_SUPPORTED:以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。(not supported不支持事务,存在就挂起)
PROPAGATION_NEVER:以非事务方式执行,如果当前存在事务,则抛出异常。(never不支持事务,存在就异常)
PROPAGATION_NESTED:如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则按REQUIRED属性执行。(nested存在就在嵌套的执行,没有就找是否存在外面的事务,有则加入,没有则新建)
事务的四种隔离级别
读未提交
当前事务在写操作时,不允许其他事务写操作,但允许其他事务读操作,那么就容易出现脏读
提交读
事务在读操作,允许其他事务进行读操作,也就是读完数据后,数据可能已经发生改变,会出现幻读和不可重复读,而写事务不允许其他事务进行读写,可以避免脏读
可重复读
事务在读操作时,允许其他事务读,但不允许写,允许多次读,在写事务时,不允许读写,但偶尔可能会出现幻读
串行化(序列化)
最高级的隔离级别,它要求事务序列化执行,事务只能一个一个执行,无法并发执行
编程式事务
xml配置方式
<!-- 配置spring事务管理器 -->
<bean class="org.springframework.jdbc.datasource.DataSourceTransactionManager" id="DataSourceTransactionManager">
<!-- 数据源 -->
<property name="dataSource" ref="dataSource"></property>
</bean>
<tx:advice id="txadvice" transaction-manager="DataSourceTransactionManager">
<tx:attributes>
<tx:method name="*" propagation="REQUIRED"/>
</tx:attributes>
</tx:advice>
<!-- 如何配置事务管理器的工作 -->
<aop:config proxy-target-class="true">
<aop:pointcut expression="execution(* com.hx.service.*.*(..))" id="txcut"/>
<aop:advisor advice-ref="txadvice" pointcut-ref="txcut"/>
</aop:config>
代码方式
public void testTransaction() {
transactionTemplate.execute(new TransactionCallbackWithoutResult() {
@Override
protected void doInTransactionWithoutResult(TransactionStatus transactionStatus) {
try {
// .... 业务代码
} catch (Exception e){
//回滚
transactionStatus.setRollbackOnly();
}
}
});
}
注解式事务
@Transaction失效场景
在方法修饰符不是public时,会失效
在rooback回滚时,捕捉到异常,会失效
在同个类中调用被@Transaction标识的方法,不会执行,只有在当前事务方法被其他类以外的方法调用时,才会被spring aop的代理对象进行拦截管理
spring web(MVC执行流程在webFLux中)
spring web MVC和spring web的区别
sprin web mvc 主要是对mvc的支持,包括restful协议、国际化等模块,依赖web模块,在引入webmvc就会传递引入web模块
spring-web提供核心的HTTP集成,包括一些方便的Servlet过滤器,Spring HTTP Invoker,用于与其他Web框架和HTTP技术集成的基础设施。
spring5新功能(WebFlux)
webflux和spring mvc功能相似
异步和同步:主要针对调用者而言,调用者发送请求后,一直等待对方回应,就是同步,如果发送后,不等对方回应就去做其他事情就是异步
阻塞和非阻塞:主要针对被调用者,被调用者在接收到请求后,直到处理完任务后,才会给出反应,就是阻塞,如果是在未做完任务,就给出反应就是非阻塞
webFlux新特点:
非阻塞式
函数式
函数式,就不多提,之前写过
stream流
public class Test{
public static void main(String[] args){
//数组
Long[] arr={"","",""};
Arrays.stream(arr).forEach(System.out:println); 、
//集合
List<Integer> list=new ArrayList<>();
list.stream.forEach(System.out:println);
//
Stream.of(arr).forEach(System.out:println);
//迭代器打印10个元素
Stream.iterate(1,i->i+1).limit(10).forEach(System.out:println);
//generate
Stream.generate(new Random().nextInt(10)).limit(10).forEach(System.out:println);
}
}
采用异步响应式编程
响应式:面向数据流和变化的范式
SpringMVC和webflux的区别
mvc是命令式编程,而webflux是响应式编程
Spring Web MVC 是 专为 Servlet API 和 Servlet 容器构建。反应式堆栈 Web 框架, Spring WebFlux,后来在 5.0 版中添加。它是完全非阻塞的,支持反应流背压,并在以下服务器上运行: Netty、Undertow 和 Servlet 3.1+ 容器。
Observe和Observeable的理解
这里涉及到小丑和观众,小丑的数量是1,而观众的数量是n,观众会对小丑的举动做出相应的反应。这里很符合观察者模式的场景,因此,我们需要创建一个小丑类Clown,作为被观察对象,因此需要继承Observable类,同时具有表演的行为,退场的行为;同时需要创建一个观众类Viewer,作为观察者,因此需要实现Observer接口,同时观众具有喝彩的行为,倒喝彩的行为,退场的行为,每个观众还对应一个座位号。
public class Observe extends Observable {
public static void main(String[] args) {
Observe observe = new Observe();
observe.addObserver((o,arg)->{
System.out.println("发生变化");
});
observe.addObserver((o,arg)->{
System.out.println("被观察者通知,发生变化");
});
observe.setChanged();//告知数据要发生变化
observe.notifyObservers();//告知所有观察者
}
}
webflux是基于reactor实现,而reactor是满足Reactive规范的框架
reactor的观察者模式废弃了java8的是基于java9的java.util.current.flux实现的,是发布和订阅模式
reactor有两个核心类Mono 和Flux,这两个类实现接口Publisher,提供丰富操作符。Flux对象实现发布者,返回N个元素;Mono实现发布者,返回0或1个元素
Flux和Mono都是数据流的发布者,使用Flux和Mono可以发出三中信号:元素值、错误信号、完成信号,错误信号和完成信号都是终止信号,终止信号是告诉订阅者数据流结束了,错误信号终止数据流同时会把错误信息传递给订阅者
使用时,需要添加依赖
<dependency>
<groupId>io.projectreactor</groupId>
<artifactId>reactor-core</artifactId>
<version>3.3.4.RELEASE</version>
</dependency>
三中信号流的特点
错误信号和完成信号都是终止信号,不能直接共享
如果没有发送任何元素值,而是直接发送错误或者完成信号,表示是空数据流
如果没有错误信号,没有完成信号,表示是无限数据流
just声明
调用just琥珀这其他方法只是声明数据流,数据流并没有发出,只是进行订阅之后才会触发数据流,不订阅什么都不会发生
public class Test{
public static void main(String[] args){
//只是声明 ,输出还需要订阅
Flux.just(1,2,3,4,5).subscribe(System.out::print);
Flux.just(1).subscribe(System.out::print);
}
}
其他方式声明
public class Test{
public static void main(String[] args){
//需要包装类数组
Integer[] array={1,2,3,4,5};
//声明数组
Flux.fromArray(array);
//声明集合
Flux.fromIterable(Arrays.asList(array));
//声明Stream流
Flux.fromStream(Stream.of(array));
}
}
操作符
对数据流进行一道道操作,称为操作符,比如工厂流水线
1.map将元素映射为新元素
2.flatMap将元素映射为流(先将各个元素转变为各个流,再将流合并成一个大流,最后输出)
Netty
springWebFlux是基于reactor的,而reactor是默认使用Netty容器的,Netty是一个异步非阻塞框架,采用的是NIO模型
NIO(同步非阻塞)
WebFlux
底层使用的DispatcherHandler实现了 webHandler
public class DispatcherHandler implements WebHandler, ApplicationContextAware {
@Nullable
private List<HandlerMapping> handlerMappings;//负责查询相应的处理器
@Nullable
private List<HandlerAdapter> handlerAdapters; //真正负责响应处理
@Nullable
private List<HandlerResultHandler> resultHandlers; //响应结果处理
}
public interface WebHandler {
Mono<Void> handle(ServerWebExchange var1);
}
执行的Handler如下
public class DispatcherHandler implements WebHandler, ApplicationContextAware {
public Mono<Void> handle(ServerWebExchange exchange) { //获取http请求响应消息
//判断是否映射器为空,为空就创建一个createNotFoundError()异常
return this.handlerMappings == null ? this.createNotFoundError() :
Flux.fromIterable(this.handlerMappings).concatMap((mapping) -> {
return mapping.getHandler(exchange);
}).next().switchIfEmpty(this.createNotFoundError()).flatMap((handler) -> {
//执行具体的Mapping,也就是业务
return this.invokeHandler(exchange, handler);
}).flatMap((result) -> {
return this.handleResult(exchange, result); //将执行结果返回
});
}
}
WebFlux写法
import com.middletest.jhdmiddletest.entity.User;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
public interface UserService {
/**
* 根据id查询用户
* @param id
* @return
*/
public Mono<User> getUserById(Integer id);
/**
* 返回全部用户
* @return
*/
public Flux<User> getAllUser();
/**
* 添加用户
* @param userMono
* @return
*/
public Mono<Void> addUser(Mono<User> userMono);
}
import org.springframework.stereotype.Service;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import java.util.HashMap;
import java.util.Map;
@Service
public class UserServiceImpl implements UserService {
private static Map<Integer,User> users= new HashMap<Integer,User>(){
{
put(1,new User(1,"lisi",26,"呼兰区24号大街"));
put(2,new User(2,"lisi",27,"呼兰区24号大街"));
put(3,new User(3,"lisi",24,"呼兰区24号大街"));
}
};
@Override
public Mono<User> getUserById(Integer id) {
return Mono.just(users.get(id));
}
@Override
public Flux<User> getAllUser() {
return Flux.fromIterable(users.values());
}
@Override
public Mono<Void> addUser(Mono<User> userMono) {
return userMono.doOnNext(user->{
Integer id=users.size()+1;
users.put(id,user);
//记得写终止信号,不然是一个无线数据流
}).thenEmpty(Mono.empty());
}
}
WebFlux函数式编程
在使用函数式编程时,需要自己初始化服务器
它的函数式编程实现了两个接口
RouterFunction(路由处理,实现路由功能,请求转发给对应的Handler)
HandlerFunction(处理函数,处理请求生成响应的函数)
webflux的请求和相应已经变,是ServeRequest、ServeResponse
第一步创建注解编程模型
第二步创建handler
package com.alpaca.handler;
import com.alpaca.entity.User;
import com.alpaca.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.MediaType;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.web.reactive.function.BodyInserter;
import org.springframework.web.reactive.function.server.ServerRequest;
import org.springframework.web.reactive.function.server.ServerResponse;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
public class UserHandler {
@Autowired
private UserService userService;
/**
* 查询
* @param request
* @return
*/
public Mono<ServerResponse> getUserById(ServerRequest request){
Integer id= Integer.parseInt(request.pathVariable("id"));
Mono<User> userMono = userService.getUserById(id);
Mono<ServerResponse> notfound = ServerResponse.notFound().build();
return userMono.flatMap(user->ServerResponse.ok()
.contentType(MediaType.APPLICATION_JSON).body(BodyInserters.fromObject(user)))
.switchIfEmpty(notfound);
}
public Mono<ServerResponse> getAllUsers(){
Flux<User> allUser = userService.getAllUser();
return ServerResponse.ok().contentType(MediaType.APPLICATION_JSON).body(allUser,User.class);
}
public Mono<ServerResponse> addUser(ServerRequest request){
Mono<User> userMono = request.bodyToMono(User.class);
return ServerResponse.ok().build(userService.addUser(userMono));
}
}
第三步 初始化服务器,完成Router
package com.alpaca.route;
import com.alpaca.handler.UserHandler;
import com.alpaca.service.UserService;
import com.alpaca.service.impl.UserServiceImpl;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.MediaType;
import org.springframework.http.server.reactive.HttpHandler;
import org.springframework.http.server.reactive.ReactorHttpHandlerAdapter;
import org.springframework.web.reactive.function.server.RequestPredicates;
import org.springframework.web.reactive.function.server.RouterFunction;
import org.springframework.web.reactive.function.server.RouterFunctions;
import org.springframework.web.reactive.function.server.RequestPredicates;
import org.springframework.web.reactive.function.server.RouterFunctions;
import reactor.netty.http.server.HttpServer;
import java.io.IOException;
public class Server {
private UserService userService=new UserServiceImpl();
private UserHandler userHandler=new UserHandler(userService);
public RouterFunction router(){
return RouterFunctions.route(
RequestPredicates.GET("/getUser/{id}").and(RequestPredicates.accept(MediaType.APPLICATION_JSON)),userHandler::getUserById)
.andRoute(RequestPredicates.GET("/users").and(RequestPredicates.accept(MediaType.APPLICATION_JSON)),userHandler::getAllUsers);
}
/**
* 完成服务器适配
*/
public void createReactorServer(){
RouterFunction router=router();
HttpHandler httpHandler = RouterFunctions.toHttpHandler(router);
ReactorHttpHandlerAdapter handlerAdapter = new ReactorHttpHandlerAdapter(httpHandler);
//创建服务器
HttpServer httpServer=HttpServer.create();
httpServer.handle(handlerAdapter).bindNow();
}
public static void main(String[] args) throws IOException {
Server server = new Server();
server.createReactorServer();
System.out.println("enter to exit");
System.in.read();
}
}
调用客户端
可以直接使用浏览器,也可以自己直接用代码实现客户端
import org.springframework.http.MediaType;
import org.springframework.web.reactive.function.client.WebClient;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
public class Client {
public static void main(String[] args) {
WebClient webClient=WebClient.create("http://localhost:999");
User user=webClient.get().uri("getUser/{id}","1")
.accept(MediaType.APPLICATION_JSON)
.retrieve().bodyToMono(User.class).block();
System.out.println(user);
Iterable<User> users = webClient.get().uri("/users").accept(MediaType.APPLICATION_JSON)
.retrieve().bodyToFlux(User.class).toIterable();
while (users.iterator().hasNext()){
System.out.println(users.iterator().next());
}
Flux<User> userFlux = webClient.get().uri("/users").accept(MediaType.APPLICATION_JSON)
.retrieve().bodyToFlux(User.class);
userFlux.buffer().doOnNext(System.out::println);
}
spring webflux在流程上和spring MVC的区别