1.编程中组件的概念:
在编程中,组件(Component)通常指的是一种可重用的、模块化的代码单元,它封装了特定的功能或用户界面元素,并提供了与其他代码进行交互的接口。组件可以看作是对数据和方法的简单封装,具有自己的属性和方法。属性是组件数据的简单访问者,而方法则是组件的一些简单而可见的功能。
在不同的编程语言和框架中,组件的具体实现方式可能有所不同。例如,在C++ Builder中,一个组件就是一个从TComponent派生出来的特定对象;在Delphi中,它被称为部件;而在Visual BASIC中,它被称为控件。
使用组件的主要优点是可以提高代码的可重用性、可维护性和灵活性。通过将复杂的应用程序分解为一系列相互关联的组件,程序员可以更容易地管理和维护代码,同时也可以更方便地进行团队协作和代码共享。此外,组件还可以使应用程序的架构更加清晰和模块化,从而更容易进行扩展和定制。
需要注意的是,虽然组件可以提高代码的效率和可维护性,但过度使用或不当使用组件也可能导致性能损失或结构不合理等问题。因此,在使用组件时需要根据具体情况进行权衡和选择。
2.Java中源文件中可以有多个类,但为什么最多只能有一个public类?![](https://img-blog.csdnimg.cn/direct/e9e42113a6354b46a16c52d5385a789e.png)
3.脚手架的概念
在编程中,脚手架(scaffold)是一个预设的项目结构或代码模板,用于快速搭建起一个新的项目。脚手架可以提供一套约定俗成的目录结构、文件模板以及基本代码,从而帮助开发人员快速开始一个新的项目,减少繁杂的配置和初始化工作。
使用脚手架可以提高开发效率,节省开发人员的时间和精力。一些常见的脚手架工具包括:
1. Angular CLI:用于快速创建Angular项目的命令行工具;
2. Create React App:用于快速创建React项目的脚手架工具;
3. Express Generator:用于生成Express.js应用的代码模板;
4. Vue CLI:用于快速创建Vue.js项目的脚手架工具。
通过使用这些脚手架工具,开发人员可以更加专注于业务逻辑的实现,而不需要手动配置和搭建项目的基础结构。同时,脚手架也提供了一些常用的功能和依赖库的集成,使得开发过程更加高效和规范化。
4.什么是异步操作
异步操作指的是一种非阻塞性的操作方式,它允许程序在执行某个任务时,不必等待该任务完成,而是可以继续执行其他任务,或者执行一些回调函数。这种方式可以提高程序的响应性和效率,因为它避免了因为等待某个任务完成而导致的程序阻塞。
在Vue中,异步操作通常用于处理一些需要花费较长时间完成的任务,例如网络请求、定时器、文件读写等。Vue提供了多种方式来处理异步操作,例如使用axios进行网络请求、使用setTimeout设置定时器、使用Promise处理异步任务等。
需要注意的是,异步操作可能会导致一些问题,例如回调地狱(Callback Hell)、竞态条件(Race Condition)等。因此,在使用异步操作时,需要仔细考虑程序的逻辑和流程,以避免出现这些问题。同时,也需要注意错误处理和异常捕获,以确保程序的稳定性和可靠性。
5.为什么异步操作可以确保页面不会因为等待后端数据而白屏
异步操作可以确保页面不会因为等待后端数据而白屏,主要归功于它们非阻塞性的特性。以下是具体的原因:
-
非阻塞主线程:当执行一个异步操作时,如网络请求,它不会阻塞主线程的执行。这意味着,即使后端数据还没有返回,浏览器的主线程也可以继续执行其他任务,例如渲染页面、处理用户交互等。
-
事件循环:JavaScript是单线程的,但是它使用了一个事件循环(Event Loop)来处理异步操作。当异步操作完成时,它会把一个回调函数放入事件队列中。只有当主线程空闲时,事件循环才会从队列中取出事件并执行相应的回调函数。这种方式允许程序在等待异步操作完成时继续执行其他任务。
-
更好的用户体验:由于页面不会因为等待后端数据而阻塞,用户可以继续浏览页面、与页面交互,甚至进行其他操作,如滚动页面、点击按钮等。这种无延迟的交互给用户一种更流畅的体验。
-
防止线程死锁:在多线程环境中,如果多个线程等待同一个资源,可能会导致线程死锁。而在JavaScript的单线程环境中,由于异步操作的存在,这种死锁问题被避免了。
-
更好的资源利用:如果所有操作都是同步的,那么浏览器可能会花费大量时间等待后端响应,而在此期间,其他可以并行处理的任务将被阻塞。异步操作允许浏览器在等待一个任务完成时,处理其他任务,从而更有效地利用系统资源。
在Web开发中,通过合理地使用异步操作,可以显著提高应用程序的性能和用户体验。这也是现代Web开发中经常使用的技术,如Ajax、Fetch API、Axios等,它们都是基于异步操作来优化Web应用的性能。
6.Docker中镜像的概念
在 Docker 中,镜像(Image)是用于创建容器(Container)的静态文件,它包含了运行容器所需的所有文件系统内容、库、工具以及设置。镜像是一个只读的文件,它由多个层(Layers)组成。每一层都包含了文件系统的一部分,以及定义如何改变文件系统内容的指令。当你运行一个容器时,Docker 引擎会从镜像中创建一个可写容器层作为容器的文件系统,并在其上添加一个读写层,使得容器可以被修改而不影响原始镜像。
镜像可以通过 Dockerfile 来创建,Dockerfile 是一个文本文件,包含了创建镜像所需的指令,比如基于哪个基础镜像、安装什么软件、运行什么命令等。通过执行 `docker build` 命令可以根据 Dockerfile 创建镜像。
镜像通过标签(Tag)来管理,标签一般用来指定镜像的版本号或者其他特征。比如一个镜像可以有多个不同的标签,通常包括最新版本、特定版本、开发版本等。通过执行 `docker tag` 命令可以为镜像添加或修改标签。
总的来说,镜像是 Docker 容器的基础,它定义了容器的文件系统结构和运行环境,用户可以通过构建、拉取、上传和分享镜像来方便地部署和管理应用程序。
7.ip地址中的端口号的作用
IP地址中的端口号用于标识一个网络通信中的特定应用程序或服务。在计算机网络中,一个IP地址可以识别网络中的一台设备,而端口号则可以识别这台设备上运行的具体应用程序。通过将数据包发送到目标设备的特定端口,发送方和接收方可以确保数据包正确地传递给相应的应用程序,从而实现数据交换。常见的端口号包括HTTP的80端口、HTTPS的443端口、FTP的21端口等,不同的应用程序使用不同的端口号来提供服务。
IP地址用于标识计算机网络中的不同设备,类似于人的身份证号码。而端口号则用于标识计算机网络中的不同进程或应用程序,类似于人的不同手机号码。端口号是一个2字节16位的整数,它的主要作用包括:
- 标识进程:告诉操作系统,当前的这个数据要交给哪一个进程来处理。
- 实现网络通信:在计算机网络中,端口号是用于标识应用程序或进程的数字标识符。它们允许在同一个IP地址下运行多个应用程序或服务,并确保数据包被正确地发送到正确的应用程序或服务。
- 提高网络通信效率、可靠性和安全性:通过为每个应用程序或服务分配唯一的数字标识符,网络中的端口号能够实现这一目标。
8.Map中的of方法
在Java 8及以后的版本中,Map
接口提供了一个静态方法of
,它允许你以更简洁的方式创建不可变的Map
实例。这是一个非常实用的方法,尤其是在你需要创建一个小的、不可变的Map
时。
Map.of
方法有几个重载版本,可以接受不同数量的键值对参数。
以下是一些示例:
-
Map.of(K key1, V value1)
:创建一个只包含一个键值对的Map
。
Map<String, Integer> map = Map.of("one", 1);
System.out.println(map); // 输出:{one=1}
-
Map.of(K key1, V value1, K key2, V value2, ...)
:创建一个包含多个键值对的Map
。注意,这种方法最多只接受10个键值对。
Map<String, Integer> map = Map.of("one", 1, "two", 2, "three", 3);
System.out.println(map); // 输出:{one=1, two=2, three=3}
需要注意的是,使用Map.of
创建的Map
实例是不可变的。这意味着一旦创建,你就不能向其中添加或删除键值对。如果尝试这样做,将会抛出UnsupportedOperationException
。
这种方法在你需要创建一个常量映射或者一个不需要修改的映射时非常有用。它也可以用于创建配置映射,这些映射在应用程序的生命周期中不会改变。
如果你需要一个可以修改的Map
,你可以使用new HashMap<>()
或Map.newHashMap()
(取决于你的具体需求和Java版本)来创建一个新的HashMap
实例。
9.代理模式
我们先了解一下代理模式:
在开发中,当我们要访问目标类时,不是直接访问目标类,而是访问器代理类。通过代理类调用目标类完成操作。简单来说就是:把直接访问变为间接访问。
这样做的最大好处就是:我们可以在代理类调用目标类之前和之后去添加一些预处理和后处理操作。来扩展一些不属于目标类的功能。
比如说:我们可以在方法开始和结束前记录日志:在方法执行前进行额外的参数校验,进行事务管理,如手动提交、权限校验等。
代理模式是一种设计思想,实际实现方式上有静态代理和动态代理之分。
9.1.静态代理
静态代理:在程序运行前,我们就给目标类编写了其代理类的代码,然后编译了其代理类。这样在程序运行之前,我们就已经生成了它代理类的字节码文件,即我们事先编写,然后编译,在程序运行的时候直接去读这些字节码文件进行运行。
例:定义静态代理类
使用静态代理类:
这里使用student调用dowork和使用staticProxy调用dowork效果不一样,后者在原有功能基础上增加了调用方法前和调用方法后的打印功能。
这就我们代理模式的最大特点:它可以控制对原有对象的访问。在原有对象的访问的基础上去做一些额外的能力。
这些类已经被编译为字节码文件了,我们可以拿这几个文件去任何一个机器上执行。
如果是静态代理的话,我们需要编写一个与其绑定的代理类。这个类会被编译成字节码文件,然后再运行。
9.2.动态代理
而如果是动态代理的话,我们就不需要是献给目标去编写代理代码,而是在运行中通过反射自动生成代理对象!
在Java中,动态代理主要通过两种机制实现:JDK动态代理和CGLIB动态代理。
9.2.1JDK动态代理
JDK动态代理基于Java的反射机制,它只能为接口创建代理对象。要使用JDK动态代理,需要实现java.lang.reflect.InvocationHandler
接口,并重写其invoke()
方法。invoke()
方法会在代理对象上的方法被调用时被执行。
例:
这里最核心的就是通过Proxy.newProxyInstance方法去生成动态代理类以及访问它的实例。
使用动态代理:
这里dynamicProxyList就是动态代理对象,它会自动调用动态代理类DynamicProxy重写的invoke方法,打印两次开始执行是因为调用了dynamicProxyList的toString方法。
在这里我们并没有编写ArrayList的代理类,但是却把它代理了,这就是动态代理的魅力。
问题来了:这个它的动态代理类生成在哪里呢?我们怎么没看见呢?
原理:
@CallerSensitive
public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h) {
Objects.requireNonNull(h);
Class<?> caller = System.getSecurityManager() == null ? null : Reflection.getCallerClass();
Constructor<?> cons = getProxyConstructor(caller, loader, interfaces);
return newProxyInstance(caller, cons, h);
}
caller
caller是一个内部类,用于代表创建代理实例的调用者。这个caller类实际上是一个
InvocationHandler的实现,它负责在代理实例上转发方法调用。
在newProxyInstance
方法的实现中,caller
类并不是直接作为参数传递给newProxyInstance
方法的,而是在内部被创建和使用。这个类通常是一个匿名内部类,它的主要作用是持有对原始InvocationHandler
的引用,并将方法调用转发给该处理器。
cons
在 Java 中,`Proxy.newProxyInstance()` 方法的源码中的 `cons` 表示 `constructor`,它用来表示代理类的构造函数。在 `Proxy.newProxyInstance()` 方法内部,会调用代理类的构造函数来创建实际的代理对象。
具体来说,`Proxy.newProxyInstance()` 方法的源码中会根据传入的类加载器(`ClassLoader`)、接口数组(`Class[]`)和 `InvocationHandler` 对象来动态生成代理类,并通过代理类的构造函数来创建代理对象。生成的代理类会实现传入的接口,并将接口中的方法调用委托给传入的 `InvocationHandler` 对象来处理。
代理类的构造函数在代理对象实例化时会被调用,并在内部完成代理对象的初始化工作,比如将 `InvocationHandler` 对象赋值给代理对象。
因此,在 `Proxy.newProxyInstance()` 方法源码中的 `cons` 所指的就是代理类的构造函数,用来创建代理对象并完成代理对象的初始化工作。
实现思路:代理类(运行时生成的代理类)和被代理类(这里就是ArrayList)实现同一个接口,有被代理类的所有方法,然后代理类把被代理类所有方法原先的调用通通先去调用代理类的InvocationHandler(也就是我们自己写的那个代理类)
在这个例子中共有32个Method(方法)对象,对应的就是List的32个方法。
如这里某个方法进行代理,我们可以看到这里它调用的是h的invoke方法,这个h就是我们之前写的InvocationHandler的实现类(DynamicProxy(我们自己写的实现Invocation的动态代理类)和这个$Proxy0代理类是两个东西!!!) 这样目标类的所有方法都会走我们写的那个通用代理类(DynamicProxy)的invoke方法。
10.newProxyInstance方法
newProxyInstance
方法是 Java 反射 API 中的一个静态方法,用于在运行时动态地创建一个代理实例。这个方法属于 java.lang.reflect.Proxy
类。当你想要为一个接口动态地生成一个代理类并创建其实例时,你可以使用这个方法。
newProxyInstance
方法的签名如下:
public static Object newProxyInstance(
ClassLoader loader,
Class<?>[] interfaces,
InvocationHandler h
)
这个方法接受三个参数:
-
ClassLoader loader
:类加载器,用于加载代理类。通常,你可以传递目标对象(被代理的对象)的类加载器。 -
Class<?>[] interfaces
:一个接口数组,表示代理类要实现的接口。这些接口定义了代理实例可以调用的方法。 -
InvocationHandler h
:一个InvocationHandler
实例,用于处理代理实例上的方法调用。当代理实例上的方法被调用时,invoke
方法会被执行。
newProxyInstance
方法返回一个实现了给定接口的代理实例,该实例将所有方法调用委托给指定的 InvocationHandler
。
下面是一个简单的例子,展示了如何通过动态代理使用 newProxyInstance
方法来创建一个代理实例:
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
interface MyInterface {
void doSomething();
}
class MyInvocationHandler implements InvocationHandler {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("Before method call");
Object result = method.invoke(null, args); // 注意这里传入null,因为我们没有实现类,只是做演示
System.out.println("After method call");
return result;
}
}
public class Main {
public static void main(String[] args) {
// 创建InvocationHandler实例
MyInvocationHandler handler = new MyInvocationHandler();
// 使用newProxyInstance创建代理实例
MyInterface proxy = (MyInterface) Proxy.newProxyInstance(
MyInterface.class.getClassLoader(),
new Class<?>[] { MyInterface.class },
handler
);
// 调用代理实例的方法,会触发InvocationHandler的invoke方法
proxy.doSomething();
}
}
在这个例子中,MyInvocationHandler
实现了 InvocationHandler
接口,并重写了 invoke
方法来添加额外的逻辑。newProxyInstance
方法被用来创建一个实现了 MyInterface
接口的代理实例,并将 MyInvocationHandler
实例作为处理器传递给它。当代理实例的 doSomething
方法被调用时,MyInvocationHandler.invoke
方法会被执行。
11.过滤器
基本介绍
过滤器,顾名思义就是对事物进行过滤的,在Web中的过滤器,当然就是对请求进行过滤,我们使用过滤器,就可以对请求进行拦截,然后做相应的处理,实现许多特殊功能。如登录控制,权限管理,过滤敏感词汇等.
过滤器原理
当我们使用过滤器时,过滤器会对游览器的请求进行过滤,过滤器可以动态的分为3个部分,1.放行之前的代码,2.放行,3.放行后的代码,这3个部分分别会发挥不同作用。
第一部分代码会对游览器请求进行第一次过滤,然后继续执行
第二部分代码就是将游览器请求放行,如果还有过滤器,那么就继续交给下一个过滤器
第三部分代码就是对返回的Web资源再次进行过滤处理
我们使用过滤器,也就是说,不止请求会经过过滤器,我们的响应也会经过过滤器。
12.拦截器
拦截器 是 SpringMVC 框架自己的,只有使用了 SpringMVC 框架的工程才能用。
作用:可以构成拦截器栈,完成特定功能。比如日志记录、登录判断、权限检查等作用。
好处:拦截器也可以让你将通用的代码模块化并作为可重用的类。
拦截器的应用:AOP、需要有一些业务逻辑(需要注入Bean等)
理解:
拦截器可以说相当于是个过滤器:就是把不想要的或不想显示的内容给过滤掉。拦截器可以抽象出一部分代码可以用来完善原来的方法。同时可以减轻代码冗余,提高重用率。
比如在登入一个页面时,如果要求用户密码、权限等的验证,就可以用自定义的拦截器进行密码验证和权限限制。对符合的登入者才跳转到正确页面。这样如果有新增权限的话,不用在action里修改任何代码,直接在interceptor里修改就行了。
拦截器执行流程:
(1)、程序先执行preHandle()方法,如果该方法的返回值为true,则程序会继续向下执行处理器中的方法,否则将不再向下执行;
(2)、在业务处理器(即控制器Controller类)处理完请求后,会执行postHandle()方法,然后会通过DispatcherServlet向客户端返回响应;
(3)、在DispatcherServlet处理完请求后,才会执行afterCompletion()方法。
13.拦截器和过滤器的区别
过滤器和拦截器的区别:
①拦截器是基于java的反射机制的,而过滤器是基于函数回调。
②拦截器不依赖与servlet容器,过滤器依赖与servlet容器。
③拦截器只能对action请求起作用,而过滤器则可以对几乎所有的请求起作用。
④拦截器可以访问action上下文、值栈里的对象,而过滤器不能访问。
⑤在action的生命周期中,拦截器可以多次被调用,而过滤器只能在容器初始化时被调用一次。 ⑥拦截器可以获取IOC容器中的各个bean,而过滤器就不行,这点很重要,在拦截器里注入一个service,可以调用业务逻辑。
触发时机
有个专业词语叫触发时机
1.过滤器和拦截器触发时间和地点不一样:
过滤器是在请求进入容器后,但请求进入servlet之前进行预处理的。请求结束返回也是,是在servlet处理完后,返回给前端之前。
总结:过滤器包裹住servlet,servlet包裹住拦截器。
如下图所示:
2.过滤器的触发时机是容器后,servlet之前,所以过滤器的doFilter(
ServletRequest request, ServletResponse response, FilterChain chain
)的入参是ServletRequest ,而不是httpservletrequest。因为过滤器是在httpservlet之前。
3.SpringMVC的机制是由同一个Servlet来分发请求给不同的Controller,其实这一步是在Servlet的service()方法中执行的。
4.,SpringMVC的机制是由同一个Servlet来分发请求给不同的Controller,其实这一步是在Servlet的service()方法中执行的。
5.SpringMVC的机制是由同一个Servlet来分发请求给不同的Controller,其实这一步是在Servlet的service()方法中执行的。
6.还有,拦截器是spring容器的,是spring支持的
总结:拦截器功在对请求权限鉴定方面确实很有用处,在我所参与的这个项目之中,第三方的远程调用每个请求都需要参与鉴定,所以这样做非常方便,而且他是很独立的逻辑,这样做让业务逻辑代码很干净。和框架的其他功能一样,原理很简单,使用起来也很简单,大致看了下SpringMVC这一部分的源码,其实还是比较容易理解的。
我们项目中仅仅用到了preHandle这个方法,而未用其他的,框架提供了一个已经实现了拦截器接口的适配器类HandlerInterceptorAdapter,继承这个类然后重写一下需要用到的方法就行了,可以少几行代码,这种方式Java中很多地方都有体现。
14.监听器
一、Listener 监听器简介:
1. Listener监听器是Javaweb的三大组件之一。Javaweb的三大组件分别是:servlet程序,filter过滤器,listener监听器。
2. Listener是JavaEE的规范,就是接口。
3. 监听器的作用:用来监听某种事物的变化,然后通过回调函数,反馈给客户或者程序。
4. 监听器可以监听就是在application,session,request三个对象创建、销毁或者往其中添加修改删除属性时自动执行代码的功能组件。
5. Listener分类:JavaWeb中提供了8个监听器。
二、监听器的分类:
按照监听对象的划分,可以分为三种:
1.ServletContext监控:对应监控application内置对象的创建跟销毁.
当web容器开启时,执行contextInitialized方法;当容器关闭或者重启时,执行contextDestroyed方法.实现方式:直接实现ServletContextListener接口。
2.HttpSession监控:对应监控session内置对象的创建和销毁。
当打开一个新的页面时,开启一个session会话,执行sessionCreated方法;当页面关闭session过期时,或者容器关闭销毁时,执行sessionDestroyed方法实现方式:直接实现HttpSessionListener接口。
3.ServletRequest监控:对应监控request内置对象的创建和销毁。
当访问某个页面时,出发一个request请求,执行requestInitialized方法;当页面关闭时,执行requestDestroyed方法。实现方式,直接实现ServletRequestListener接口。
三、监听器的用途
*监听器利用HttpServletLisener 统计在线人数
*加载初始化信息:利用ServletContextListener
*统计网站的访问量
*实现访问监控
15.ThreadLocal
ThreadLocal叫做线程变量,意思是ThreadLocal中填充的变量属于当前线程,该变量对其他线程而言是隔离的,也就是说该变量是当前线程独有的变量。ThreadLocal为变量在每个线程中都创建了一个副本,那么每个线程可以访问自己内部的副本变量。
ThreadLoal 变量,线程局部变量,同一个 ThreadLocal 所包含的对象,在不同的 Thread 中有不同的副本。这里有几点需要注意:
因为每个 Thread 内有自己的实例副本,且该副本只能由当前 Thread 使用。这是也是 ThreadLocal 命名的由来。
既然每个 Thread 有自己的实例副本,且其它 Thread 不可访问,那就不存在多线程间共享的问题。
ThreadLocal 提供了线程本地的实例。它与普通变量的区别在于,每个使用该变量的线程都会初始化一个完全独立的实例副本。ThreadLocal 变量通常被private static修饰。当一个线程结束时,它所使用的所有 ThreadLocal 相对的实例副本都可被回收。
总的来说,ThreadLocal 适用于每个线程需要自己独立的实例且该实例需要在多个方法中被使用,也即变量在线程间隔离而在方法或类间共享的场景
ThreadLocal 适用于如下两种场景
1、每个线程需要有自己单独的实例
2、实例需要在多个方法中共享,但不希望被多线程共享
对于第一点,每个线程拥有自己实例,实现它的方式很多。例如可以在线程内部构建一个单独的实例。ThreadLoca 可以以非常方便的形式满足该需求。
对于第二点,可以在满足第一点(每个线程有自己的实例)的条件下,通过方法间引用传递的形式实现。ThreadLocal 使得代码耦合度更低,且实现更优雅。