Spring AOP以及统一处理
一.Spring AOP
1.什么是Spring AOP
AOP(Aspect Oriented Programming):面向切面编程,它是一种思想,它是对某一类事情的集中处理。
2.AOP的作用
想象一个场景,我们在做后台系统时,除了登录和注册等几个功能不需要做用户登录验证之外,其他几乎所有页面调用的前端控制器( Controller)都需要先验证用户登录的状态,那这个时候我们要怎么处 理呢?
我们之前的处理方式是每个 Controller 都要写一遍用户登录验证,然而当你的功能越来越多,那么你要 写的登录验证也越来越多,而这些方法又是相同的,这么多的方法就会代码修改和维护的成本。那有没 有简单的处理方案呢?答案是有的,对于这种功能统一,且使用的地方较多的功能,就可以考虑 AOP 来统一处理了。
- 除了统一的用户登录判断之外,AOP 还可以实现:
- 统一日志记录
- 统一方法执行时间统计
- 统一的返回格式设置
- 统一的异常处理
- 事务的开启和提交等
- AOP是OOP的补充
3.AOP的相关概念
1.切面(Aspect)
面(Aspect)由切点(Pointcut)和通知(Advice)组成,它既包含了横切逻辑的定义,也包
括了连接点的定义。
2.连接点 (Join Point)
应用执行过程中能够插入面的一个点,这个点可以是方法调用时,抛出异常时,甚至修改字段 时。切面代码可以利用这些点插入到应用的正常流程之中,并添加新的行为
3.切点(Pointcut)
Pointcut 是匹配 Join Point 的谓词。 Pointcut 的作用就是提供一组规则(使用 AspectJ pointcut expression language 来描述)来 匹配 Join Point,给满足规则的 Join Point 添加 Advice
4.通知(Advice)
切面也是有目标的 ——它必须完成的工作。在 AOP 术语中,切面的工作被称之为通知。
通知:定义了切面是什么,何时使用,其描述了面要完成的工作,还解决何时执行这个工作的
问题。
Spring 切面类中,可以在方法上使用以下注解,会设置方法为通知方法,在满足条件后会通知本
方法进行调用:
- 前置通知使用 @Before:通知方法会在目标方法调用之前执行。
- 后置通知使用 @After:通知方法会在目标方法返回或者抛出异常后调用。
- 返回之后通知使用 @AfterReturning:通知方法会在目标方法返回后调用。
- 抛异常后通知使用 @AfterThrowing:通知方法会在目标方法抛出异常后调用。
- 环绕通知使用 @Around:通知包裹了被通知的方法,在被通知的方法通知之前和调用之后执
- 行自定义的行为。
4.Spring AOP的实现
1.添加 AOP 框架支持
在 pom.xml 中添加如下配置:
<dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter</artifactId></dependency><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><optional>true</optional></dependency>
<!-- Springboot test--><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId><scope>test</scope></dependency>
<!-- Spring AOP 框架--><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-aop</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency></dependencies>
2.定义切面和切点
@Component
@Slf4j
@Aspect
public class LoginAspect {@Pointcut("execution(* com.javastudy.springaopdemo5.controller.LoginController.*(..))")public void pointcut() {}}
@Aspect类注解表示LoginAspect是一个切面类,@Pointcut表示定义一个切点,其中的内容表示连接点的规则,也就是包括哪些类或者方法属于这个切点,连接点.
其中 pointcut 方法为空方法,它不需要有方法体,此方法名就是起到⼀个“标识”的作用,标识下面的
通知方法具体指的是哪个切点(因为切点可能有很多个)
AspectJ 支持三种通配符
* :匹配任意字符,只匹配⼀个元素(包,类,或方法,方法参数)
.. :匹配任意字符,可以匹配多个元素 ,在表示类时,必须和 * 联合使用。
+ :表示按照类型匹配指定类的所有类,必须跟在类名后面,如 com.cad.Car+ ,表示继承该类的
所有子类包括本身
切点表达式由切点函数组成,其中 execution() 是最常用的切点函数,用来匹配方法,语法为:
execution(<修饰符><返回类型><包.类.方法(参数)><异常>)
修饰符和异常可以省略,具体含义如下:
上面我们定义的含义为,匹配com.javastudy.springaopdemo5.controller.LoginController类下所有的方法.
3.定义相关通知
先来定义controller层的内容
@RestController
@Slf4j
@RequestMapping("/user")
public class LoginController {@RequestMapping("/login")public String login() {log.info("login...");return "login...";}@RequestMapping("/register")public String register() {log.info("register...");return "register...";}@RequestMapping("/get")public String get() {log.info("get...");return "get...";}}
1.前置通知 @Before
通知方法会在目标方法调用之前执行。
注意:@Before里面的内容表示切点,即在哪些接口中执行这些通知.
@Component
@Slf4j
@Aspect
public class LoginAspect {@Pointcut("execution(* com.javastudy.springaopdemo5.controller.LoginController.*(..))")public void pointcut() {}//前置通知@Before("pointcut()")public void doBefore() {log.info("do before....");}}
当我们访问任意一个页面的时候,控制台打印如下日志
可以看到都会先执行doBefore通知.
2.后置通知 @After
通知方法会在目标方法返回或者抛出异常后调用。
//后置通知@After("pointcut()")public void doAfter() {log.info("do after...");}
模拟异常情况,可以在LoginController中某一个方法加10/0,观察
@RequestMapping("/login")public String login() {log.info("login...");int i=10/0;return "login...";}
可以观察到在方法异常之后, @After通知仍然会执行.
3.返回之后通知 @AfterReturning
通知方法会在目标方法返回后调用。
// return 之前通知@AfterReturning("pointcut()")public void doAfterReturning() {log.info("do after returning...");}
当我们访问login接口的时候(10/0,有异常),观察是否有输出
此时是没有输出的.
访问其它接口,没有异常的接口
此时@AfterReturning通知正常执行
4.抛异常后通知 @AfterThrowing
通知方法会在目标方法抛出异常后调用。
//抛出异常之前通知@AfterThrowing("pointcut()")public void doAfterThrowing() {log.info("do after throwing");}
执行有异常的接口,有日志的打印
执行没有异常的接口,没有日志的打印
5.环绕通知 @Around
通知包裹了被通知的方法,在被通知的方法通知之前和调用之后执行自定义的行为。
@Around("pointcut()")public Object doAround(ProceedingJoinPoint joinPoint) {Object oj = null;log.info("环绕通知执行之前...");log.info(joinPoint.getSignature().toLongString());try {oj = joinPoint.proceed();//调用目标方法} catch (Throwable e) {throw new RuntimeException(e);}log.info("环绕通知执行之后....");return oj;}
执行没有异常的接口:
执行有异常的接口:
接下来我们看看ProceedingJoinPoint 常用方法
toString | 连接点所在位置的相关信息 |
toShortString | 连接点所在位置的简短相关信息 |
toLongString | 连接点所在位置的全部相关信息 |
getThis | 返回AOP代理对象,也就是com.sun.proxy.$Proxy18 |
getTarget | 返回目标对象(定义方法的接口或类) |
getArgs() | 返回被通知方法参数列表 |
getSignature | 返回当前连接点签名,其getName()方法返回方法的FQN |
执行所有的通知,观察环绕通知和前置通知和后置通知的先后顺序
可以观察到,环绕通知先于before,后于after.
如果一个切点只含有一个通知,那么我们可以将切点的规则放在通知上
@Component
@Slf4j
@Aspect
public class LoginAspect {//前置通知@Before("execution(* com.javastudy.springaopdemo5.controller.LoginController.*(..))")public void doBefore() {log.info("do before....");}}
4.Spring AOP 实现原理
Spring AOP 是构建在动态代理基础上,因此 Spring 对 AOP 的支持局限于方法级别的拦截。
Spring AOP 支持 JDK Proxy 和 CGLIB 方式实现动态代理。默认情况下,实现了接口的类,使
用 AOP 会基于 JDK 生成代理类,没有实现接口的类,会基于 CGLIB 生成代理类。
代理模式:
1.静态代理
1.定义接口
public interface PayService {void pay();
}
2.实现接口
public class AliPayService implements PayService{@Overridepublic void pay() {System.out.println("ali pay...");}
}
3.创建代理类, 并同样实现支付接口
public class StaticProxy implements PayService {private final PayService payService;public StaticProxy(PayService payService) {this.payService = payService;}@Overridepublic void pay() {System.out.println("before...");payService.pay();System.out.println("after...");}
}
4.实际使用
public static void main(String[] args) {PayService service = new AliPayService();PayService proxy = new StaticProxy(service);proxy.pay();}
静态代理有个很大的缺点,就是当有很多不同的接口的时候,我们需要定义很多个代理类实现不同的接口,当我们代理实现的功能相同的时候,但是有多个接口,此时完成这么多代理类很麻烦,此时需要我们的动态代理.
2.动态代理
1.JDK动态代理
从 JVM 角度来说,动态代理是在运行时动态生成类字节码,并加载到 JVM 中 的。
就 Java 来说,动态代理的实现方式有很多种,比如 JDK 动态代理、CGLIB 动态 代理等等。
定义JDK动态代理类
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;/*** @author Chooker* @create 2023-07-27 22:36*/public class JDKInvocationHandler implements InvocationHandler {//⽬标对象即就是被代理对象private Object target;public JDKInvocationHandler(Object target) {this.target = target;}//proxy代理对象@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {//1.安全检查System.out.println("安全检查");//2.记录⽇志System.out.println("记录⽇志");//3.时间统计开始System.out.println("记录开始时间");//通过反射调⽤被代理类的⽅法Object retVal = method.invoke(target, args);//4.时间统计结束System.out.println("记录结束时间");return retVal;}
}
创建⼀个代理对象并使用
public static void main(String[] args) {PayService target = new AliPayService();//创建⼀个代理类:通过被代理类、被代理实现的接⼝、⽅法调⽤处理器来创建PayService proxy = (PayService) Proxy.newProxyInstance(target.getClass().getClassLoader(),new Class[]{PayService.class},new JDKInvocationHandler(target));proxy.pay();}
缺点:JDK的动态代理必须有接口
2.CGLIB动态代理
CGLIB 动态代理类使用步骤
1. 定义一个类;
2. 自定义 MethodInterceptor 并重写 intercept 方法,intercept 用于拦截增强
被代理类的方法,和 JDK 动态代理中的 invoke 方法类似;
3. 通过 Enhancer 类的 create()创建代理类
添加依赖(如果创建的是一个Spring项目,不需要引入,因为Spring底层已经引入了cglib框架)
<dependency><groupId>cglib</groupId><artifactId>cglib</artifactId><version>3.3.0</version>
</dependency>
自定义 MethodInterceptor(方法拦截器)
import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;import java.lang.reflect.Method;/*** @author Chooker* @create 2023-07-27 22:48*/
public class CGLIBInterceptor implements MethodInterceptor {//被代理对象private Object target;public CGLIBInterceptor(Object target) {this.target = target;}@Overridepublic Object intercept(Object o, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {//1.安全检查System.out.println("安全检查");//2.记录⽇志System.out.println("记录⽇志");//3.时间统计开始System.out.println("记录开始时间");//通过cglib的代理⽅法调⽤Object retVal = methodProxy.invoke(target, args);//4.时间统计结束System.out.println("记录结束时间");return retVal;}}
1. obj : 被代理的对象(需要增强的对象)
2. method : 被拦截的方法(需要增强的方法)
3. args : 方法入参
4. proxy : 用于调用原始方法
创建代理类, 并使用
public static void main(String[] args) {PayService target = new AliPayService();PayService proxy = (PayService) Enhancer.create(target.getClass(), new CGLIBInterceptor(target));proxy.pay();}
JDK 动态代理和 CGLIB 动态代理对比
1. JDK 动态代理只能代理实现了接口的类或者直接代理接口,而 CGLIB 可以代 理未实现任何接口的类。
2. CGLIB 动态代理是通过生成⼀个被代理类的子类来拦截被代理类的方法调用,因此不能代理声明为 final
性能: 大部分情况都是 JDK 动态代理更优秀,随着 JDK 版本的升级,这个优势更 加明显。
Spring代理选择
1. proxyTargetClass 为false, 目标实现了接口, 用jdk代理
2. proxyTargetClass 为false, 目标未实现接口, 用cglib代理
3. proxyTargetClass 为true, 用cglib代理
织入(Weaving):代理的生成时机
织入是把切面应用到目标对象并创建新的代理对象的过程,切面在指定的连接点被织入到目标对
象中。
在目标对象的生命周期里有多个点可以进行织入:
- 编译期:切面在目标类编译时被织入。这种方式需要特殊的编译器。AspectJ的织入编译器就是以这种方式织入切面的。
- 类加载期:切面在目标类加载到JVM时被织入。这种方式需要特殊的类加载器(ClassLoader),它可以在目标类被引入应用之前增强该目标类的字节码。AspectJ5的加载时织入(load-time weaving. LTW)就支持以这种方式织入切面。
- 运行期:切面在应用运行的某⼀时刻被织入。⼀般情况下,在织入切面时,AOP容器会为目标对象动态创建⼀个代理对象。SpringAOP就是以这种方式织入切面的。
上面我们学习的是Spring AOP的原理,是接下来我们学习内容的底层,SpringBoot一些常见的功能进行了封装,底层使用AOP实现的
二.SpringBoot 统一功能处理
需要实现用户的登录权限的校验功能,在Servlet阶段,我们可以通过在Session中保存用户的信息,之后每个页面先通过session中判断是否存在用户的信息,如果存在说明用户已经登录过了,没有就跳转到登录的页面.
1.Spring AOP 用户统一登录验证的问题
我们第一时间想到的就是通过环绕通知来解决这个问题,可以对除了登录和注册的页面采用环绕通知,用来判断用户是否登录过了.但是会出现以下两个问题
1.. 没办法获取到 HttpSession 对象。
2. 我们要对一部分方法进行拦截,而另一部分方法不拦截,如注册方法和登录方法是不拦截的,这样 的话排除方法的规则很难定义,甚至没办法定义。
那么该如何解决呢?
2.Spring 拦截器
对于以上问题 Spring 中提供了具体的实现拦截器:HandlerInterceptor,拦截器的实现分为以下两个步骤:
1. 创建自定义拦截器,实现 HandlerInterceptor 接口的 preHandle(执行具体方法之前的预处理)方法。
2. 将自定义拦截器加入 WebMvcConfigurer 的 addInterceptors 方法中
1.自定义拦截器
@Component
public class LoginInterceptor implements HandlerInterceptor {@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {HttpSession session = request.getSession(false);if (session != null && session.getAttribute("username") != null) {//通过return true;}//没有权限访问response.setStatus(401);return false;}
}
2.将自定义拦截器加入到系统配置
@Configuration
public class AppConfig implements WebMvcConfigurer {@Autowiredprivate LoginInterceptor loginInterceptor;@Overridepublic void addInterceptors(InterceptorRegistry registry) {registry.addInterceptor(loginInterceptor).//表示拦截所有的路径addPathPatterns("/**").//不拦截login接口excludePathPatterns("/login").//不拦截register接口excludePathPatterns("/register");}
}
其中:
addPathPatterns:表示需要拦截的 URL,“**”表示拦截任意方法(也就是所有方法)。
excludePathPatterns:表示需要排除的 URL。
说明:以上拦截规则可以拦截此项目中的使用 URL,包括静态文件(图片文件、JS 和 CSS 等文件)。
排除所有的静态资源
// 拦截器@Overridepublic void addInterceptors(InterceptorRegistry registry) {registry.addInterceptor(new LoginInterceptor()).addPathPatterns("/**") // 拦截所有接⼝.excludePathPatterns("/**/*.js").excludePathPatterns("/**/*.css").excludePathPatterns("/**/*.jpg").excludePathPatterns("/login.html").excludePathPatterns("/**/login"); // 排除接⼝}
拓展以下,可以在里面添加统一前缀的添加
@Configuration
public class AppConfig implements WebMvcConfigurer {@Autowiredprivate LoginInterceptor loginInterceptor;@Overridepublic void addInterceptors(InterceptorRegistry registry) {registry.addInterceptor(loginInterceptor).addPathPatterns("/**")// 拦截所有url.excludePathPatterns("/api/user/login").excludePathPatterns("/api/user/reg");}@Overridepublic void configurePathMatch(PathMatchConfigurer configurer) {configurer.addPathPrefix("api", c -> true);}
}
3.controller接口模仿登录
@RestController
@Slf4j
@RequestMapping("/user")
public class LoginController {@RequestMapping("/login")public boolean login(HttpServletRequest request, String username, String password) {log.info("login...");if (!StringUtils.hasLength(username) || !StringUtils.hasLength(password)) {return false;}//此时表示账号密码正确if ("admin".equals(username) && "123456".equals(password)) {HttpSession session = request.getSession(true);session.setAttribute("username", username);return true;}return false;}@RequestMapping("/register")public String register() {log.info("register...");return "register...";}@RequestMapping("/get")public String get() {log.info("get...");return "get...";}}
当我们直接访问get接口的时候:显示的是401,表示没有权限
正常访问login和register接口都是可以实现的
此时我们使用正确的账号密码登录:可以看到此时已经正确登录了
此时我们再次访问get接口:可以看到此时正确访问
3.拦截器的实现原理
正常情况下的调用顺序:
然而有了拦截器之后,会在调用 Controller 之前进行相应的业务处理,执行的流程如下图所示:
拦截器是基于AOP的,Spring是基于Servlet的
三.统一异常处理
@ControllerAdvice
@ResponseBody
public class ErrorHandler {@ExceptionHandler(Exception.class)public Object error(Exception e) {HashMap<String, Object> map = new HashMap<>();map.put("success", 0);map.put("status", -1);map.put("msg", e.getMessage());return map;}@ExceptionHandler(NullPointerException.class)public Object error2(NullPointerException e) {HashMap<String, Object> result = new HashMap<>();result.put("success", 0);result.put("status", -2);result.put("message", "空指针异常:" + e.getMessage());return result;}@ExceptionHandler(ArithmeticException.class)public Object error2(ArithmeticException e) {HashMap<String, Object> result = new HashMap<>();result.put("success", 0);result.put("status", -3);result.put("message", "算数异常:" + e.getMessage());return result;}
}
controller
@RestController
@Slf4j
@RequestMapping("/error")
public class ErrorController {@RequestMapping("/test1")public boolean test1() {int i = 10 / 0;return true;}@RequestMapping("/test2")public boolean test2() {String a = null;a.length();return true;}@RequestMapping("/test3")public String test3() {throw new RuntimeException("test3手动创建异常");}
}
访问test1
访问test2
访问test3
可以观察到当错误异常为子类的时候,匹配顺序为当前类及其子类向上依次匹配
三.统一数据返回格式
1.为什么需要统一返回格式
统一数据返回格式的优点有很多,比如以下几个:
- 方便前端程序员更好的接收和解析后端数据接口返回的数据。
- 降低前端程序员和后端程序员的沟通成本,按照某个格式实现就行了,因为所有接口都是这样返回的。
- 有利于项目统一数据的维护和修改。
- 有利于后端技术部门的统一规范的标准制定,不会出现稀奇古怪的返回内容
2.统一数据返回格式的实现
@ControllerAdvice
public class ResponseHandler implements ResponseBodyAdvice {/*** 内容是否需要重写(通过此⽅法可以选择性部分控制器和⽅法进⾏重写)* 返回 true 表示重写*/@Overridepublic boolean supports(MethodParameter returnType, Class converterType) {return true;}/*** ⽅法返回之前调⽤此⽅法*/@SneakyThrows@Overridepublic Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, Class selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) {// 构造统⼀返回对象HashMap<String, Object> result = new HashMap<>();result.put("state", 1);result.put("msg", "");result.put("data", body);if(body instanceof String){ObjectMapper objectMapper = new ObjectMapper();return objectMapper.writeValueAsString(result);}return result;}
}
假如没有if((body instanceof String)这一段代码,会发生如下的错误
controller:
@RestController
@Slf4j
@RequestMapping("/user")
public class LoginController {@RequestMapping("/login")public boolean login(HttpServletRequest request, String username, String password) {log.info("login...");if (!StringUtils.hasLength(username) || !StringUtils.hasLength(password)) {return false;}//此时表示账号密码正确if ("admin".equals(username) && "123456".equals(password)) {HttpSession session = request.getSession(true);session.setAttribute("username", username);return true;}return false;}@RequestMapping("/register")public String register() {log.info("register...");return "register...";}@RequestMapping("/get")public String get() {log.info("get...");return "get...";}}
相关文章:
Spring AOP以及统一处理
一.Spring AOP 1.什么是Spring AOP AOP(Aspect Oriented Programming):面向切面编程,它是一种思想,它是对某一类事情的集中处理。 2.AOP的作用 想象一个场景,我们在做后台系统时,除了登录…...
vue2markdown转思维导图
官网 http://markmap.js.org 按照官网安装markmap-lib,markmap-view两个依赖外,还需要安装markmap-common 如果报错提示vuePdfNoSss相关问题,需要安装vue-pdf 如果报错can’t import the named export ‘xxx’ from non EcmaScript module,需…...
docker下redis备份文件dump.rdb获取
1.查看镜像 docker ps -a 2.进入redis客户端 docker exec -it redis redis-cli 3.保存备份文件 save 4.查看文件存放位置 CONFIG GET dir 5.将docker中文件拷出 docker cp id或name:容器中文件的路径 目标目录地址...
二十一、MySQL(多表)内连接、外连接、自连接实现
1、多表查询 (1)基础概念: (2)多表查询的分类: 2、内连接 (1)基础概念: (2)隐式内连接: 基础语法: select 表1.name,…...
Zookeeper运维
我是一个目录 1. 参数说明2. Zookeeper优化建议3. Zookeeper性能查看4. 建议 1. 参数说明 工作节点瞬间压力大,导致和集群通信出现延迟,被踢出节点,瞬间释放的连接立即又连接到另外节点,最终集群挂掉。加了一些延迟配置后…...
uniapp 点击事件-防重复点击
uniapp 点击事件-防重复点击 1、common文件并创建anti-shake.js文件 // 防止处理多次点击 function noMoreClicks(methods, info) {// methods是需要点击后需要执行的函数, info是点击需要传的参数let that this;if (that.noClick) {// 第一次点击that.noClick f…...
推进“数智+数治”,中期科技智慧公厕驱动城市公厕更新升级发展
随着城市化的快速发展和人口的不断增加,公共厕所这一基础设施的更新升级成为了亟待解决的问题。过去的传统公厕往往存在着环境脏乱差、无法保证使用者的舒适度等诸多问题。而智慧公厕则能够通过互联网和物联网的技术手段,实现智能化的运行管理࿰…...
4、模板(二叉树,红黑树,STL的实现)
1. 泛型编程 2. 模板:参数类型化 3. 模板分类 3.1 函数模板 概念 实例化:隐式实例化,显式实例化 3.2 类模板 4. 在模板参数列表中:class和typename 5.模板参数列表:template <class T , size_t N> 类型参数&#x…...
了解JVM
一.了解JVM 1.1什么是JVM JVM是Java Virtual Machine(Java虚拟机)的缩写,是一个虚构出来的计算机,是通过在实际的计算机上仿真模拟计算机功能来实现的,JVM屏蔽了与具体操作系统平台相关的信息,Java程序只…...
vue2实现组织机构树
参考大佬文章:https://www.cnblogs.com/10ve/p/12573772.html 【vue——实现组织架构图(vue-org-tree)——技能提升 - CSDN App】http://t.csdn.cn/Mpe4B...
JS中BigInt的使用
JS中BigInt的使用 BigInt是一种内置对象,它提供了一种方法来表示大于2^53 - 1的整数,通俗来讲就是提供了一种可以表示任意大整数的方法,当我们使用Number来表示一个超过了2 ^53 - 1的整数的时候,会出错。所以此时我们需要使用Big…...
你的第1个 Unity 游戏!!!
简介 首先新建一个方块添加重力!!!总结首先 首先,你要先打开 U n i t y Unity Unity...
炫云云渲染3ds max效果图渲染教程
很多人在第一次使用炫云云渲染渲染效果图的时候不知道怎么使用,其实现在使用炫云渲染效果图真的很简单,我们一起来看看。 一客户端安装 1、打开炫云云渲染官网,点击右上角的客户端下载,选择炫云客户端(NEXT版…...
Java中数组array和列表list相互转换
在Java中,可以将数组(array)和列表(list)相互转换,但需要注意一些细节和限制。以下是一些示例和说明: 从数组到列表: 使用Arrays.asList()方法:可以使用Arrays.asList()…...
【JavaSE笔记】数据类型与变量
一、前言 在Java这门“啰嗦”的编程语言中,我们必须弄清楚每种数据类型的性质和用途,才能让程序“说人话”。要成为Java高手,就必须与各种数据类型打成一片。 本文则将带你认识Java中常见的两位“角色”—数据类型与变量。 二、数据类型 在Java中数据类型主要分…...
VR全景智慧文旅解决方案,助力文旅产业转型升级
引言: 随着科技的不断发展,虚拟现实(VR)技术正逐渐展露其影响力,改变着旅游业。VR全景智慧文旅解决方案也应运而生,将传统旅游的体验形式从线下扩展到了线上,带来了不一般的文旅体验。 一.VR全…...
采用cv2和默认的人脸识别分类器实现人脸检测功能
人脸识别分类器 haarcascade_frontalface_default 提示:分类器文件地址在这里:https://github.com/opencv/opencv/blob/687fc11626901cff09d2b3b5f331fd59190ad4c7/data/haarcascades/haarcascade_frontalface_default.xml 文章目录 人脸识别分类器 haar…...
C# 实现迷宫游戏
智能提示: /// <summary>/// 迷宫/// </summary>internal class Maze : IDisposable{private MazeCell[,] cells;private readonly Stack<MazeCell> stack new Stack<MazeCell>();private readonly Random rand new Random();private int…...
chales 重写/断点/映射/手机代理/其他主机代理
1 chales 安装和代理配置/手机代理配置/电脑代理配置 chales 安装和代理配置/手机代理配置/电脑代理配置 2 转载:Charles Rewrite重写(详解!必懂系列) Charles Rewrite重写(详解!必懂系列) 1.打开charles,点击菜单栏的Tools选中Rewrite2.…...
django添加数据库字段进行数据迁移
1.修改view.py里面的变量 2.在model.py新增字段 3.打开terminal并将环境切到项目所在环境,切换方式为 4.执行命令 python manage.py makemigrations backend python manage.py migrate...
flink1.15.0消费kafka 报错 The coordinator is not available.
报错 You should retry committing the latest consumed offsets. Caused by: org.apache.kafka.common.errors.CoordinatorNotAvailableException: The coordinator is not available. 但是任务还在正常跑. 开源bug [FLINK-28060] Kafka Commit on checkpointing fails re…...
2023华为杯研究生数学建模F题思路模型代码(9.22早第一时间更新)
目录 1.F题思路模型:9.7晚上比赛开始后,第一时间更新,获取见文末名片 3 全国大学生数学建模竞赛常见数模问题常见模型分类 3.1 分类问题 3.2 优化问题 详细思路见此名片,开赛第一时间更新 1.F题思路模型:9.7晚上比…...
[k8s] pod的创建过程
pod的创建过程 定义 Pod 的规范: apiVersion: v1 kind: Pod metadata:name: my-pod spec:containers:- name: my-containerimage: nginx:latest创建 Pod 对象: 使用 kubectl 命令行工具或其他客户端工具创建 Pod 对象: kubectl create -f…...
[网鼎杯 2020 朱雀组]phpweb call_user_func()
时间一跳一跳的 抓个包 很奇怪 结合上面的 date() 认为第一个是函数 我们随便输一个看看 发现过滤了 随便输一个 linux指令 发现报错了 call_user_func() 看看是啥 很容易理解 第一个参数是函数名 后面是 参数 那么这里就是 func 函数 p 数值 所以我们看看有什么办法可以…...
电脑怎么取消磁盘分区?
有时候,我们的电脑会出现一个磁盘爆满,但另一个却空着,这时我们可以通过取消磁盘分区来进行调整,那么,这该怎么操作呢?下面我们就来了解一下。 磁盘管理取消磁盘分区 磁盘管理是Windows自带的磁盘管理工具…...
Cascade-MVSNet CVPR-2020 学习笔记总结 译文 深度学习三维重建
文章目录 4 Cascade-MVSNet CVPR-20204.0 主要特点4.1 背景介绍4.2 代价体构造回顾4.3 Cascade-MVSNet4.4 Loss的设置4.5 Cascade-MVSNet实战操作4.6 总结MVSNet系列最新顶刊 对比总结4 Cascade-MVSNet CVPR-2020 深度学习三维重建 cascade-MVSNet-CVPR-202(源码、原文、译文 …...
【JVM】Java类的加载机制!
一、类的生命周期 类加载过程包含:加载、验证、准备、解析和初始化 ,一共包括5 个阶段。 (1)加载: 简单来说就是将java类的字节码文件加载到机器内存中。在加载类时,Java虚拟机必须完成以下3件事情&…...
Postman使用_接口导入导出
文章目录 Postman导入数据Collections导出数据Environments导出数据Postman导出所有数据 Postman导入数据 可以导入collections(接口集)、Environments(环境配置)通过分享的链接或导出的JSON文件导入数据(还可以从第三…...
linux下centos7升级python版本
由于项目需要使用爬虫,爬虫框架支撑3.8以上版本。而linux自带的python版本是2.7.*,所以需要升级python版本至3.8 键脚本安装Python3.6-Python3.10 bash <(curl -sSL https://raw.githubusercontent.com/midoks/choose-linux-python/main/install.sh…...
Python空值None的意义
在 Python 中,有一个特殊的常量 None(N 必须大写)。和 False 不同,它不表示 0,也不表示空字符串,而表示没有值,也就是空值。 这里的空值并不代表空对象,即 None 和 [] 以及 "&q…...
app制作网站收费吗/搜索引擎seo如何赚钱
5、安装 PHP 你需要 flex,你需要从 http://www.gnu.org 的镜像站点下载一个该软件的源代码。它被放置于一个 非 gnu 目录的 ftp 服务器上。下载这个文件,并且使用 gunzip 解压缩,然后执行 tar -xvf。进入新创建的 flex 目录并运行 ./configur…...
潍坊做网站联系方式/搭建一个网站
文章目录蓝桥杯模拟赛第二场(web)1 卡片化标签页2 随机数生成器3 个人博客4 学生成绩统计5 水果摆盘6 给页面化个妆7 小兔子爬楼梯8 时间管理大师9 购物车10 菜单树检索蓝桥杯模拟赛第二场(web) 1 卡片化标签页 题目:…...
镇江地区做网站的公司有哪些/苏州seo网站公司
前言 在做Power BI报表统计时候,经常会遇到查看每月活动用户数量的需求 实现方式 1、新建度量值 月活动浏览者统计 CALCULATE (DISTINCTCOUNT (QueryAllActivities[UserInfo.ITCode] ),FILTER ( ALL ( 日期表[Date]), 日期表[Date] < MAX ( 日期表…...
网站开发师是做什么的/外贸营销渠道
今天小编要跟大家分享的文章是关于Linux系统中常用的Vim命令详解。相信熟悉Linux系统的小伙伴一定不会对vim命令陌生。但是关于vim的具体知识和使用方法存在一些疑问。今天小编就来为大家分享一下关于vim命令的详解,让我们一起来看一看吧~一、命令历史以:和/开头的命…...
wordpress外链缩略图/自有品牌如何推广
在linux下有两种访问ftp服务器的方式,一种是图形化界面操作,另一种方式就是用命令行的方式。 Ubuntu图形化界面访问ftp服务器 1、打开文件管理器,点击“其他位置” image.png2、在 连接服务器上 输入对应的ftp地址 image.png点击问号…...
网站建设公司效果/网站广告制作
上拉电阻就是把不确定的信号通过一个电阻钳位在高电平,此电阻还起到限流的作用。同理,下拉电阻是把不确定的信号钳位在低电平。上拉电阻是指器件的输入电流,而下拉指的是输出电流。 一、那么在什么时候使用上、下拉电阻呢? 1、当…...