《Spring技术内幕》学习笔记17——Spring HTTP调用器实现远程调用
1.Spring中,HTTPInvoker(HTTP调用器)是通过基于HTTP协议的分布式远程调用解决方案,和java RMI一样,HTTP调用器也需要使用java的对象序列化机制完成客户端和服务器端的通信。HTTP调用器的远程调用工作原理如下:
(1).客户端:
a.向服务器发送远程调用请求:
远程调用信息——>封装为远程调用对象——>序列化写入到远程调用HTTP请求中——>向服务器端发送。
b.接收服务器端返回的远程调用结果:
服务器端返回的远程调用结果HTTP响应——>反序列化为远程调用结果对象。
(2).服务器端:
a.接收客户端发送的远程调用请求:
客户端发送的远程调用HTTP请求——>反序列化为远程调用对象——>调用服务器端目标对象的目标方法处理。
b.向客户端返回远程调用结果:
服务器端目标对象方法的处理结果——>序列化写入远程调用结果HTTP响应中——>返回给客户端。
接下来我们将从客户端和服务器端分别分析HTTP调用器远程调用的具体实现。
2.HTTP调用器客户端配置:
使用HTTP调用器之前,首先需要对客户端其进行如下的配置:
- <!--客户端HTTP调用器代理-->
- <bean id=”proxy” class=”org.springframework.remoting.httpinvoker.HttpInvokerProxyFactoryBean”>
- <property name=”serviceUrl”>
- <value>http://yourhost:8080/远程调用URL</value>
- </property>
- <property name=”serviceInterface”>
- <value>远程调用服务接口全路径</value>
- </property>
- </bean>
- <bean id=”客户端bean” class=”客户端Bean全路径”>
- <property name=”remoteService”>
- <ref bean=”proxy”/>
- </property>
- </bean>
在HTTP调用器客户端代理HttpInvokerProxyFactoryBean中封装远程调用服务URL和服务接口,客户端程序通过HTTP调用代理可以调用实现了指定接口的目标服务端对象。
3.HttpInvokerProxyFactoryBean创建远程调用代理对象:
HTTP调用器客户端代理HttpInvokerProxyFactoryBean是一个实现了Spring FactoryBean接口的IoC容器,其作用是对远程服务客户端封装,源码如下:
- public class HttpInvokerProxyFactoryBean extends HttpInvokerClientInterceptor implements FactoryBean<Object> {
- //远程对象的代理
- private Object serviceProxy;
- //在IoC容器注入完成之后调用
- public void afterPropertiesSet() {
- //调用父类容器的回调方法
- super.afterPropertiesSet();
- //getServiceInterface()方法用于获取配置的远程调用接口
- if (getServiceInterface() == null) {
- throw new IllegalArgumentException("Property 'serviceInterface' is required");
- }
- //使用ProxyFactory代理工厂生成远程代理对象,注意第二个参数this,因为
- //HttpInvokerProxyFactoryBean继承了HttpInvokerClientInterceptor,
- //所以代理对象的拦截器设置为HttpInvokerClientInterceptor
- this.serviceProxy = new ProxyFactory(getServiceInterface(), this).getProxy(getBeanClassLoader());
- }
- //向IoC容器索取被管理对象的方法,获取产生的远程调用代理对象
- public Object getObject() {
- return this.serviceProxy;
- }
- //获取对象类型,返回配置的远程调用接口
- public Class<?> getObjectType() {
- return getServiceInterface();
- }
- //是否是单态类型,默认Spring IoC容器产生的都是单态类型
- public boolean isSingleton() {
- return true;
- }
- }
通过上面对HttpInvokerProxyFactoryBean源码的分析我们看到,当通过getObject方法向Spring IoC容器索取远程调用对象时,触发afterPropertiesSet回调方法,创建远程调用的代理对象,最后将该远程调用代理对象返回。在创建远程调用代理对象时,使用其父类HttpInvokerClientInterceptor作为远程调用代理对象的拦截器,该拦截器将拦截对代理对象的方法调用。下面我们分析HttpInvokerClientInterceptor代理拦截器对代理对象的方法拦截处理。
4.HttpInvokerClientInterceptor拦截对远程调用代理的方法调用:
当客户端通过HTTP请求调用远程调用代理的方法时,将会触发HttpInvokerClientInterceptor拦截器的invoke方法对当前的请求进行封装处理,将客户端的java对象序列化传输到服务器端,在远程服务器端执行完请求之后,又将处理结果java对象序列化返回给客户端。其源码如下:
- public class HttpInvokerClientInterceptor extends RemoteInvocationBasedAccessor
- implements MethodInterceptor, HttpInvokerClientConfiguration {
- private String codebaseUrl;
- //HTTP调用请求执行器
- private HttpInvokerRequestExecutor httpInvokerRequestExecutor;
- public void setCodebaseUrl(String codebaseUrl) {
- this.codebaseUrl = codebaseUrl;
- }
- public String getCodebaseUrl() {
- return this.codebaseUrl;
- }
- public void setHttpInvokerRequestExecutor(HttpInvokerRequestExecutor httpInvokerRequestExecutor) {
- this.httpInvokerRequestExecutor = httpInvokerRequestExecutor;
- }
- //获取HTTP调用请求执行器,如果HTTP调用请求执行器没有设置,则使用
- //SimpleHttpInvokerRequestExecutor作为HTTP调用请求执行器
- public HttpInvokerRequestExecutor getHttpInvokerRequestExecutor() {
- if (this.httpInvokerRequestExecutor == null) {
- SimpleHttpInvokerRequestExecutor executor = new SimpleHttpInvokerRequestExecutor();
- executor.setBeanClassLoader(getBeanClassLoader());
- this.httpInvokerRequestExecutor = executor;
- }
- return this.httpInvokerRequestExecutor;
- }
- //IoC容器初始化完成回调方法
- public void afterPropertiesSet() {
- //调用父类的初始化回调方法
- super.afterPropertiesSet();
- //获取HTTP调用请求执行器
- getHttpInvokerRequestExecutor();
- }
- //拦截器代理对象方法调用入口,拦截器将客户端对远程调用代理的调用封装为
- //MethodInvocation对象。
- public Object invoke(MethodInvocation methodInvocation) throws Throwable {
- if (AopUtils.isToStringMethod(methodInvocation.getMethod())) {
- return "HTTP invoker proxy for service URL [" + getServiceUrl() + "]";
- }
- //创建远程调用对象,封装了远程调用
- RemoteInvocation invocation = createRemoteInvocation(methodInvocation);
- //远程调用结果
- RemoteInvocationResult result = null;
- try {
- //远程调用入口
- result = executeRequest(invocation, methodInvocation);
- }
- catch (Throwable ex) {
- throw convertHttpInvokerAccessException(ex);
- }
- try {
- //返回远程调用结果
- return recreateRemoteInvocationResult(result);
- }
- catch (Throwable ex) {
- if (result.hasInvocationTargetException()) {
- throw ex;
- }
- else {
- throw new RemoteInvocationFailureException("Invocation of method [" + methodInvocation.getMethod() +
- "] failed in HTTP invoker remote service at [" + getServiceUrl() + "]", ex);
- }
- }
- }
- //执行远程调用入口
- protected RemoteInvocationResult executeRequest(
- RemoteInvocation invocation, MethodInvocation originalInvocation) throws Exception {
- return executeRequest(invocation);
- }
- //通过HTTP调用请求执行器执行远程调用
- protected RemoteInvocationResult executeRequest(RemoteInvocation invocation) throws Exception {
- return getHttpInvokerRequestExecutor().executeRequest(this, invocation);
- }
- //将远程调用异常转换成Spring异常
- protected RemoteAccessException convertHttpInvokerAccessException(Throwable ex) {
- if (ex instanceof ConnectException) {
- throw new RemoteConnectFailureException(
- "Could not connect to HTTP invoker remote service at [" + getServiceUrl() + "]", ex);
- }
- else if (ex instanceof ClassNotFoundException || ex instanceof NoClassDefFoundError ||
- ex instanceof InvalidClassException) {
- throw new RemoteAccessException(
- "Could not deserialize result from HTTP invoker remote service [" + getServiceUrl() + "]", ex);
- }
- else {
- throw new RemoteAccessException(
- "Could not access HTTP invoker remote service at [" + getServiceUrl() + "]", ex);
- }
- }
- }
通过上面对HttpInvokerClientInterceptor拦截器的源码分析,我们可以看出,拦截器将客户端对远程调用的HTTP请求封装成了MethodInvocation对象,拦截器的在调用远程调用的代理对象时,又将方法调用封装成了RemoteInvocation远程调用,RemoteInvocation数据对象中封装了调用的具体信息,如方法名、方法参数以及参数类型等。
真正执行远程调用的是HTTP调用请求执行器SimpleHttpInvokerRequestExecutor,下面我们继续分析SimpleHttpInvokerRequestExecutor远程调用的具体过程。
5.SimpleHttpInvokerRequestExecutor远程调用:
SimpleHttpInvokerRequestExecutor封装了基于HTTP协议的远程调用过程,具体源码如下:
- public class SimpleHttpInvokerRequestExecutor extends AbstractHttpInvokerRequestExecutor {
- //HTTP调用请求执行器真正进行远程调用的方法,该方法有其父类//AbstractHttpInvokerRequestExecutor的executeRequest方法调用
- protected RemoteInvocationResult doExecuteRequest(
- HttpInvokerClientConfiguration config, ByteArrayOutputStream baos)
- throws IOException, ClassNotFoundException {
- //打开一个标准的J2SE HttpURLConnection
- HttpURLConnection con = openConnection(config);
- //准备连接
- prepareConnection(con, baos.size());
- //远程调用被封装成了RemoteInvocation对象,它通过序列化被写到对应的//HttpURLConnection中
- writeRequestBody(config, con, baos);
- //获取远程调用的结果,校验返回的结果
- validateResponse(config, con);
- InputStream responseBody = readResponseBody(config, con);
- //将远程调用结果转换成RemoteInvocationResult返回
- return readRemoteInvocationResult(responseBody, config.getCodebaseUrl());
- }
- //打开一个HttpURLConnection
- protected HttpURLConnection openConnection(HttpInvokerClientConfiguration config) throws IOException {
- //getServiceUrl()方法获取配置的远程调用URL,打开一个URL连接
- URLConnection con = new URL(config.getServiceUrl()).openConnection();
- if (!(con instanceof HttpURLConnection)) {
- throw new IOException("Service URL [" + config.getServiceUrl() + "] is not an HTTP URL");
- }
- return (HttpURLConnection) con;
- }
- //准备HTTP请求连接
- protected void prepareConnection(HttpURLConnection con, int contentLength) throws IOException {
- con.setDoOutput(true);
- //HTTP调用器只支持POST请求方法
- con.setRequestMethod(HTTP_METHOD_POST);
- //设置HTTP请求头内容类型,设置为:application/x-java-serialized-object
- con.setRequestProperty(HTTP_HEADER_CONTENT_TYPE, getContentType());
- //设置HTTP请求头内容长度
- con.setRequestProperty(HTTP_HEADER_CONTENT_LENGTH, Integer.toString(contentLength));
- LocaleContext locale = LocaleContextHolder.getLocaleContext();
- //设置HTTP请求的Locale
- if (locale != null) {
- con.setRequestProperty(HTTP_HEADER_ACCEPT_LANGUAGE, StringUtils.toLanguageTag(locale.getLocale()));
- }
- //设置HTTP请求压缩方式
- if (isAcceptGzipEncoding()) {
- con.setRequestProperty(HTTP_HEADER_ACCEPT_ENCODING, ENCODING_GZIP);
- }
- }
- //把序列化对象输出到HTTP请求体中
- protected void writeRequestBody(
- HttpInvokerClientConfiguration config, HttpURLConnection con, ByteArrayOutputStream baos)
- throws IOException {
- baos.writeTo(con.getOutputStream());
- }
- //校验远程调用的HTTP响应
- protected void validateResponse(HttpInvokerClientConfiguration config, HttpURLConnection con)
- throws IOException {
- //如果HTTP响应状态码大于等于300,则证明调用发生错误
- if (con.getResponseCode() >= 300) {
- throw new IOException(
- "Did not receive successful HTTP response: status code = " + con.getResponseCode() +
- ", status message = [" + con.getResponseMessage() + "]");
- }
- }
- //提取远程调用结果的HTTP响应信息
- protected InputStream readResponseBody(HttpInvokerClientConfiguration config, HttpURLConnection con)
- throws IOException {
- //如果响应信息是Gzip压缩的,则需要先解压
- if (isGzipResponse(con)) {
- return new GZIPInputStream(con.getInputStream());
- }
- //正常的HTTP响应
- else {
- return con.getInputStream();
- }
- }
- //是否是Gzip格式压缩
- protected boolean isGzipResponse(HttpURLConnection con) {
- //获取HTTP响应头信息中的压缩方式
- String encodingHeader = con.getHeaderField(HTTP_HEADER_CONTENT_ENCODING);
- return (encodingHeader != null && encodingHeader.toLowerCase().indexOf(ENCODING_GZIP) != -1);
- }
- }
通过对SimpleHttpInvokerRequestExecutor的分析,我们看到,HTTP调用请求执行器的处理逻辑是:首先,打开指定URL的HTTP连接,设置连接属性。其次,将封装请求的RemoteInvocation对象序列化到请求体中,请HTTP请求发送到服务器端。最后,从服务器端的HTTP响应中读取输入流,并将响应结果转换成RemoteInvocationResult。
将远程调用的HTTP响应转换为RemoteInvocationResult是由AbstractHttpInvokerRequestExecutor的readRemoteInvocationResult方法实现,下面我们将分析其将HTTP响应结果转换成RemoteInvocationResult的实现。
6.AbstractHttpInvokerRequestExecutor其将HTTP响应结果转换成RemoteInvocationResult:
AbstractHttpInvokerRequestExecutor中处理远程调用结果,并HTTP响应转换为RemoteInvocationResult的主要方法如下:
- //从HTTP响应中读取远程调用结果入口方法
- protected RemoteInvocationResult readRemoteInvocationResult(InputStream is, String codebaseUrl)
- throws IOException, ClassNotFoundException {
- //根据给定的输入流和类创建对象输入流
- ObjectInputStream ois = createObjectInputStream(decorateInputStream(is), codebaseUrl);
- try {
- //从对象输入流中读取远程调用结果
- return doReadRemoteInvocationResult(ois);
- }
- finally {
- ois.close();
- }
- }
- //从对象输入流中读取远程调用结果
- protected RemoteInvocationResult doReadRemoteInvocationResult(ObjectInputStream ois)
- throws IOException, ClassNotFoundException {
- //获取对象输入流中的对象
- Object obj = ois.readObject();
- if (!(obj instanceof RemoteInvocationResult)) {
- throw new RemoteException("Deserialized object needs to be assignable to type [" +
- RemoteInvocationResult.class.getName() + "]: " + obj);
- }
- //将获取到的对象封装为RemoteInvocationResult
- return (RemoteInvocationResult) obj;
- }
7.HTTP调用器的服务器端配置:
和HTTP调用器客户端类似,服务器端也需要进行如下的配置:
- <bean name=”/客户端配置的远程调用URL” class=”org.springframework.remoting.httpinvoker.HttpInvokerServiceExporter”>
- <property name=”service”>
- <ref bean=”服务器端实现bean”/>
- </property>
- <property name=”serviceInterface”>
- <value>远程调用服务接口全路径</value>
- </property>
- </property>
通过对服务器端配置的例子,我们可以看出,真正处理远程调用的服务器端实现是由service属性中指定的服务器端bean提供的,HttpInvokerServiceExporter将远程调用服务接口和服务实现类进行封装,主要提HTTP协议封装和java对象序列化功能。
Spring的HttpInvokerServiceExporter是与Spring的MVC结合在一起的,它本质上是Spring MVC的一个Controller,客户端发来的远程调用HTTP请求有Spring MVC的中央控制器DispatcherServlet转发到指定URL的HttpInvokerServiceExporter上。
8.HttpInvokerServiceExporter导出和执行远程调用服务:
HttpInvokerServiceExporter响应客户端发送的远程调用HTTP请求,它从HTTP请求中读取远程调用并将其反序列化为RemoteInvocation对象,然后调用目标服务对象的目标方法完成远程调用服务,当服务执行完成之后,通过HTTP响应把执行结果对象序列化输出到客户端。器源码如下:
- public class HttpInvokerServiceExporter extends RemoteInvocationSerializingExporter implements HttpRequestHandler {
- //处理客户端发来的远程调用HTTP请求
- public void handleRequest(HttpServletRequest request, HttpServletResponse response)
- throws ServletException, IOException {
- try {
- //从HTTP请求中反序列化出RemoteInvocation远程调用对象
- RemoteInvocation invocation = readRemoteInvocation(request);
- //调用目标服务对象,完成远程调用请求,并创建调用结果
- RemoteInvocationResult result = invokeAndCreateResult(invocation, getProxy());
- //将调用结果写到HTTP响应中
- writeRemoteInvocationResult(request, response, result);
- }
- catch (ClassNotFoundException ex) {
- throw new NestedServletException("Class not found during deserialization", ex);
- }
- }
- //从HTTP请求中读取RemoteInvocation远程调用对象入口方法
- protected RemoteInvocation readRemoteInvocation(HttpServletRequest request) throws IOException, ClassNotFoundException {
- //将从HTTP请求中读取远程调用对象
- return readRemoteInvocation(request, request.getInputStream());
- }
- //从HTTP请求中读取远程调用对象
- protected RemoteInvocation readRemoteInvocation(HttpServletRequest request, InputStream is) throws IOException, ClassNotFoundException {
- //根据HTTP请求输入流创建对象输入流
- ObjectInputStream ois = createObjectInputStream(decorateInputStream(request, is));
- try {
- //从对象输入流中读取远程调用对象
- return doReadRemoteInvocation(ois);
- }
- finally {
- ois.close();
- }
- }
- //获取HTTP请求输入流
- protected InputStream decorateInputStream(HttpServletRequest request, InputStream is) throws IOException {
- return is;
- }
- //将远程调用执行结果写到HTTP响应中
- protected void writeRemoteInvocationResult(
- HttpServletRequest request, HttpServletResponse response, RemoteInvocationResult result) throws IOException {
- //设置HTTP响应的内容类型为:application/x-java-serialized-object
- response.setContentType(getContentType());
- //将远程调用结果写到HTTP响应中
- writeRemoteInvocationResult(request, response, result, response.getOutputStream());
- }
- //将远程调用执行结果写入HTTP响应中
- protected void writeRemoteInvocationResult(
- HttpServletRequest request, HttpServletResponse response, RemoteInvocationResult result, OutputStream os)
- throws IOException {
- //获取HTTP响应对象输出流
- ObjectOutputStream oos = createObjectOutputStream(decorateOutputStream(request, response, os));
- try {
- //将远程调用执行结果写到HTTP响应对象输出流中
- doWriteRemoteInvocationResult(result, oos);
- }
- finally {
- oos.close();
- }
- }
- //获取HTTP响应对象输入流
- protected OutputStream decorateOutputStream(
- HttpServletRequest request, HttpServletResponse response, OutputStream os) throws IOException {
- return os;
- }
- }
通过对HttpInvokerServiceExporter的源码分析,我们可以看出,真正执行远程对象调用的是RemoteInvocationResultresult = invokeAndCreateResult(invocation, getProxy());它调用了RemoteInvocationBasedExporter的invokeAndCreateResult方法调用远程目标对象方法,并创建远程调用执行结果,下面我们继续分析执行服务器端远程调用目标对象方法的实现。
9.RemoteInvocationBasedExporter调用服务器目标对象:
RemoteInvocationBasedExporter的invokeAndCreateResult方法调用服务器目标对象方法,RemoteInvocationBasedExporter源码如下:
- public abstract class RemoteInvocationBasedExporter extends RemoteExporter {
- //远程调用执行器
- private RemoteInvocationExecutor remoteInvocationExecutor = new DefaultRemoteInvocationExecutor();
- public RemoteInvocationExecutor getRemoteInvocationExecutor() {
- return this.remoteInvocationExecutor;
- }
- protected RemoteInvocationResult invokeAndCreateResult(RemoteInvocation invocation, Object targetObject) {
- try {
- //调用服务器端目标对象的方法
- Object value = invoke(invocation, targetObject);
- //根据执行结果创建RemoteInvocationResult
- return new RemoteInvocationResult(value);
- }
- catch (Throwable ex) {
- return new RemoteInvocationResult(ex);
- }
- }
- //调用目标对象的方法
- protected Object invoke(RemoteInvocation invocation, Object targetObject)
- throws NoSuchMethodException, IllegalAccessException, InvocationTargetException {
- if (logger.isTraceEnabled()) {
- logger.trace("Executing " + invocation);
- }
- try {
- //获取远程调用执行器,由远程调用执行器调用目标对象的方法,即通过
- //DefaultRemoteInvocationExecutor了调用目标对象的方法
- return getRemoteInvocationExecutor().invoke(invocation, targetObject);
- }
- catch (NoSuchMethodException ex) {
- if (logger.isDebugEnabled()) {
- logger.warn("Could not find target method for " + invocation, ex);
- }
- throw ex;
- }
- catch (IllegalAccessException ex) {
- if (logger.isDebugEnabled()) {
- logger.warn("Could not access target method for " + invocation, ex);
- }
- throw ex;
- }
- catch (InvocationTargetException ex) {
- if (logger.isDebugEnabled()) {
- logger.debug("Target method failed for " + invocation, ex.getTargetException());
- }
- throw ex;
- }
- }
- }
通过上面对RemoteInvocationBasedExporter源码分析我们看到,真正调用目标对象的是DefaultRemoteInvocationExecutor的invoke方法,下面我们继续分析DefaultRemoteInvocationExecutor调用目标对象方法的实现。
10.DefaultRemoteInvocationExecutor调用目标对象的方法实现远程调用:
DefaultRemoteInvocationExecutor用于调用目标对象的指定方法实现远程对象调用服务,其源码如下:
- public class DefaultRemoteInvocationExecutor implements RemoteInvocationExecutor {
- //调用目标对象的方法
- public Object invoke(RemoteInvocation invocation, Object targetObject)
- throws NoSuchMethodException, IllegalAccessException, InvocationTargetException{
- Assert.notNull(invocation, "RemoteInvocation must not be null");
- Assert.notNull(targetObject, "Target object must not be null");
- //调用RemoteInvocation的invoke方法
- return invocation.invoke(targetObject);
- }
- }
- RemoteInvocation的invoke方法源码如下:
- public Object invoke(Object targetObject)
- throws NoSuchMethodException, IllegalAccessException, InvocationTargetException {
- //获取远程调用对象的方法名称和参数类型
- Method method = targetObject.getClass().getMethod(this.methodName, this.parameterTypes);
- //利用JDK反射机制,调用目标对象指定参数的方法
- return method.invoke(targetObject, this.arguments);
- }
转载于:https://www.cnblogs.com/exmyth/p/5156053.html
《Spring技术内幕》学习笔记17——Spring HTTP调用器实现远程调用相关推荐
- 资深架构师推荐Spring技术内幕:深入了解Spring的底层机制
程序员都很崇拜技术大神,很大一部分是因为他们发现和解决问题的能力,特别是线上出现紧急问题时,总是能够快速定位和解决. 一方面,他们有深厚的技术基础,对应用的技术知其所以然,另一方面,在采坑的过程中不断 ...
- MAC OS X 技术内幕 学习笔记之四 MAC OS系统的启动引导
MAC OS X 技术内幕 学习笔记之四 MAC OS系统的启动引导 MAC OS的启动非常快,同样的运行硬件环境,运行MAC系统感觉比运行windows系统要快不少.在使用笔记本时,同样的电池容量, ...
- Spring 技术内幕读书笔记
Spring的设计理念和整体架构 1.1 spring的各个子项目 1.1.1 spring framwork 核心, IoC容器设计,控制反转,AOP ,MVC ,JDBC ,事务处理 1.1.2 ...
- Spring源码学习笔记:Spring设计模式对比和Spring的OOB,BOP,AOP,IOC,DI/DL
1.博客内容均出自于咕泡学院架构师第三期 2.架构师系列内容:架构师学习笔记(持续更新) 1.GOF 23总设计模式归纳 分类 设计模式 创建型 工厂方法模式(Factory Method).抽象工厂 ...
- 深入立即Linux网络技术内幕学习笔记第十六章:桥接:Linux实现
网桥设备抽象: 对Linux而言,网桥是虚拟设备,要想传输或接收数据,需要将真实设备绑定到虚拟网桥上. 上图中,有几点需要注意: LAN1和LAN2通过网桥连接在一起,子网都是一样的. 网桥连接到路由 ...
- Spring Boot基础学习笔记17:Spring Boot默认缓存
文章目录 零.学习目标 一.缓存概述 (一)引入缓存管理的重要性 (二)Spring Boot的缓存管理 二.Spring Boot默认缓存 (一)数据准备 (二)创建Spring Boot项目 - ...
- Spring技术内幕(3)Spring AOP的实现
本章内容 Spring AOP概述.Spring AOP的设计与实现.建立AopProxy代理对象.Spring AOP拦截器调用的实现.Spring AOP的高级特性. 3.1 Spring AOP ...
- C#技术内幕 学习笔记
引用类型是类型安全的指针,它们的内存是分配在堆(保存指针地址)上的. String.数组.类.接口和委托都是引用类型. 强制类型转换与as类型转换的区别:当类型转换非法时,强制类型转换将抛出一个Sys ...
- 深入理解Linux网路技术内幕学习笔记第四章:通知链
第四章:通知链 内核很多子系统之间具有很强的依赖性,其中一个子系统侦测到的或者产生的事件,其他子系统可能都感兴趣,为了实现这种需求,Linux使用了通知链.通知链只在内核子系统之间使用. 通知链就是一 ...
最新文章
- OSChina 周一乱弹 —— 嫂子我帮你们照顾放心吧
- leetcode第二题java_LeetCode第二题,Java实现
- 速卖通运营之如何提升商品动销率
- hdu 2045(递推)
- python array按行归一化_机器学习 第40集:特征不归一化有什么危害?特征归一化公式是什么?( 含有笔记、代码、注释 )...
- 如何编写配置文件 JAVA_SpringBoot 如何编写配置文件
- ubuntu19.10安装remarkable
- 移动端click延迟和tap事件
- VUE搭建开发,以及打包。
- vijos 1082
- 串灯控制盒去掉怎么接_彩灯控制器原理图大全
- 【谷歌浏览器】扫码登录不上解决方案
- 《大道至简》,悟在天成——读后有感
- 利用python对包含离散型特征和连续型特征的数据进行预处理
- Linux下实现双机互信
- Java大作业——手把手教你写俄罗斯方块
- iOS 之电影播放器
- 极客日报:HarmonyOS 2.0用户数升至5000万;腾讯起诉抖音侵权《王者荣耀》,获赔60万?抖音:没侵权,已上诉!
- 解决NVIDIA显卡驱动 图形驱动程序安装失败 问题
- 英语教师计算机研修总结,英语教师个人研修总结范文
热门文章
- android导航使用教程,android BottomNavigationView的简单使用教程
- mysql中只运行一部分数据_MySQL(一)——MySQL基础和部分面试题
- 深度学习根据文字生成图片教程(附python代码)
- 笔记、代码清晰易懂!李航《统计学习方法》最新资源全套!
- android 美团滑动停止,cc美团 滑动删除(SwipeListView)
- matlab的HDLcoder,MATLAB发布新产品模块——Simulink HDL Coder_虚拟与仿真
- django xadmin 默认密码_Pycharm创建Django项目讲解 python django
- OPENGL 点击与选取的基础---坐标变换
- windows10彻底杀死卡死的顽固进程
- Flutter教程(1)——快速预览