AOP

代理模式

提出问题

现有缺陷

假设我们有一个计算类,里面有加减乘除四个方法,现在我们要为这四个方法添加日志,即在方法执行的前后分别输出一句话,这时我们会发现如下缺陷:

1.对核心业务有干扰。核心业务是加减乘除的实现,而我们现在还要额外编写日志的代码。

2.附加功能分散在各个业务功能方法中,不利于统一维护。

解决思路

解决这两个问题的核心就是解耦。我们需要把附加功能从业务功能代码中提取出来

困难

要抽取的代码在方法内部,无法像以前一样将子类中重复的代码抽取到父类来解决。因此要引入新的技术。

概念

代理模式:二十三种设计模式中的一种,属于结构型模式。它的作用就是通过提供一个代理类,让我们在调用目标方法的时候,不再是直接对目标方法进行调用,而是通过代理类间接调用。让不属于目标方法核心逻辑的代码从目标方法中剥离出来——解耦。调用目标方法时先调用代理对象的方法,减少对目标方法的调用和打扰,同时让附加功能能够集中在一起也有利于统一维护。

我们之前使用计算类的过程为:

创建计算类对象,调用对象的方法,获取返回值

现在我们创建了一个代理类,使用的过程为:

创建代理类对象,调用代理类的加减乘除方法,代理类调用计算类的对应方法并获取返回值,代理类将返回值返回给我们

即代理类中有着目标类的相同方法,且核心业务的代码是调用目标类来实现的,这样就可以在核心代码的前后加上附加代码

代理模式类似于Servlet的过滤器:在访问对应业务前进行处理,访问完业务后再进行处理

相关术语:

代理:将非核心逻辑剥离出来以后,封装这些非核心逻辑的类、对象、方法。

目标:被代理“套用”非核心逻辑代码的类、对象、方法。

静态代理

静态代理的实现:

1.创建代理类

2.目标类若实现了接口,代理类也要实现对应接口并重写方法

3.在代理类中创建目标类的成员变量

4.代理类在对应方法中调用目标类的方法实现核心功能

5.代理类在对应方法的前后添加附加功能

注:一个代理类对应一个目标类

静态代理的缺点:静态代理确实实现了解耦,但是由于代码都写死了,完全不具备任何的灵活性。

提出进一步的需求:将日志功能集中到一个代理类中,将来有任何日志需求,都通过这一个代理 类来实现。这就需要使用动态代理技术了。

动态代理

我们可以创建一个代理类工厂,然后在工厂创建时获取目标对象,然后使用 newProxyInstance 方法创建代理对象并返回

newProxyInstance():创建一个代理对象,其中有三个参数:

1.classLoader:加载动态生成的代理类的类加载器

2.interfaces:目标对象实现的所有接口的class对象所组成的数组

3.invocationHandler:设置代理对象实现目标对象方法的过程,即代理类中如何重写接口中的抽象方法

其中,classLoader一般是一个应用程序类加载器,我们自己编写的类一般都使用这个加载器,因此我们可以直接使用this.getClss().getClassLoader()获取

interfaces可以直接使用目标对象.getClss().getInterfaces()获取目标对象的所有接口

invocationHandler则是需要使用匿名内部类创建,其内部只有一个invoke方法

invoke():代理对象内部的方法的实现,有三个参数

proxy:代理对象

method:代理对象需要实现的方法,即其中需要重写的方法

args:method所对应方法的参数

注:由于代理对象和代理类都是自动生成的,我们不知道代理类的名称,但是我们知道代理类继承的接口,因此创建的代理对象我们都会使用接口类型的变量接,因此method的方法是接口中对应的方法,这个方式是可以使用目标类的对象调用的

在invoke内部核心业务的实现就是通过method.invoke(target,args)实现的

target为目标对象

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

public class ProxyFactory{

    private Object target;

    public ProxyFactory(Object target) {
        this.target = target;
    }

    public Object getProxy(){
        ClassLoader classLoader = this.getClass().getClassLoader();
        Class<?>[] interfaces = target.getClass().getInterfaces();
        InvocationHandler invocationHandler = new InvocationHandler() {
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                Object result = method.invoke(target, args);
                return result;
            }
        };

        return Proxy.newProxyInstance(classLoader,interfaces,invocationHandler);
    }

}
public void test1(){
    ProxyFactory factory = new ProxyFactory(new CalculatorPureImpl());

    Calculator proxy = (Calculator) factory.getProxy();

    System.out.println(proxy.add(1, 1));

}

动态代理有两种:

1.jdk动态代理,要求必须有接口,最终生成的代理类和目标类实现相同的接口,生成的代理类在com.sun.proxy包下,类名为$proxy+数字

2.cglib动态代理,最终生成的代理类会继承目标类,并且和目标类在相同的包下

AOP概念及相关术语

概念

AOP(Aspect Oriented Programming)是一种设计思想,是软件设计领域中的面向切面编程,它是面向对象编程(oop)的一种补充和完善,它以通过预编译方式和运行期动态代理方式实现在不修改源代码的情况下给程序动态统一添加额外功能的一种技术。

相关术语

横切关注点

从目标类中抽取出来的同一类的非核心业务。在同一个项目中,我们可以使用多个横切关注点对相关方法进行多个不同方面的增强。

通知

每一个横切关注点的功能都需要写一个方法来实现,这样的方法就叫通知方法

前置通知:在被代理的目标方法前执行

返回通知:在被代理的目标方法成功结束后执行

异常通知:在被代理的目标方法异常结束后执行

后置通知:在被代理的目标方法最终结束后执行

环绕通知:使用try...catch...finally结构围绕整个被代理的目标方法,包括上面四种通知对应的所有位置

切面

封装通知方法的类

目标

被代理的目标对象

代理

向目标对象应用通知之后创建的代理对象

连接点

横切关注点在目标类的位置,即我们要从哪里抽取非核心业务代码,那里就是连接点

注:连接点是一个纯逻辑概念,从哪抽取,哪就是连接点

切入点

定位连接点的方式

连接点只是一个逻辑概念,为了找到连接点的具体位置,需要用到切入点

切点通过 org.springframework.aop.Pointcut 接口进行描述,它使用类和方法作为连接点的查询条件。

基于注解的AOP

技术说明

动态代理(InvocationHandler):JDK原生的实现方式,需要被代理的目标类必须实现接口。因 为这个技术要求代理对象和目标对象实现同样的接口。

cglib:通过继承被代理的目标类实现代理,所以不需要目标类实现接口。

AspectJ:本质上是静态代理,将代理逻辑“织入”被代理的目标类编译得到的字节码文件,所以最终效果是动态的。weaver就是织入器。Spring只是借用了AspectJ中的注解。

使用步骤

  1. 在IOC所需依赖的基础上加入下述依赖
<!-- spring-aspects会帮我们传递过来aspectjweaver -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>5.3.1</version>
</dependency>
  1. 创建切面类,将目标类和切面类交给IOC容器管理
  2. 使用@Aspect对切面类进行注解,在Spring中开启Aspectj的自动代理
<aop:aspectj-autoproxy />
  1. 配置切面类
    1. 在切面类内部创建通知方法,并编写内部代码
    2. 使用通知注解对通知方法进行标记,如@Before:前置通知
    3. 配置注解的value属性,指定要处理哪个类的哪个方法
@Before("execution(public int com.CalculatorPureImpl.add(int,int))")
  1. 在测试类中创建ApplicationContext对象,并获取目标类继承的接口类型的代理类对象,使用代理类对象调用方法

注:目标类在被代理过后,无法再通过IOC容器获取,因此获取代理类对象时可以直接使用根据类型获取bean的方式获取代理类对象,类型为接口的类型

各种通知

  • 前置通知:使用@Before注解标识,在被代理的目标方法前执行
  • 返回通知:使用@AfterReturning注解标识,在被代理的目标方法成功结束后执行
  • 异常通知:使用@AfterThrowing注解标识,在被代理的目标方法异常结束后执行
  • 后置通知:使用@After注解标识,在被代理的目标方法最终结束后执行
  • 环绕通知:使用@Around注解标识,使用try...catch...finally结构围绕整个被代理的目标方法,包括上面四种通知对应的所有位置

各种通知的执行顺序:

  • Spring版本5.3.x以前:
    • 前置通知
    • 目标操作
    • 后置通知
    • 返回通知或异常通知
  • Spring版本5.3.x以后:
    • 前置通知
    • 目标操作
    • 返回通知或异常通知
    • 后置通知

切入点表达式语法

我们在使用注解将一个方法标记为通知后,还需要通过注解的value属性设置该通知的生效范围

其具体语法如下:

基础结构:

execution(权限修饰符 方法返回值 方法所在类型的全类名.方法名(参数列表))

@Before("execution(public int com.CalculatorPureImpl.add(int,int))")

其中,"execution()"是固定格式,参数列表只填参数的类型,使用逗号分隔

我们也可以使用通配符*代表任意,如

@Before("execution(* com.CalculatorPureImpl.*(..))")

上述代码代表权限修饰符和方法返回值任意,com包下的CalculatorPureImpl类,方法随意,方法的参数随意

具体细节:

  • 用*号代替“权限修饰符”和“返回值”部分表示“权限修饰符”和“返回值”不限
  • 在包名的部分,一个“*”号只能代表包的层次结构中的一层,表示这一层是任意的。
    • 例如:*.Hello匹配com.Hello,不匹配com.atguigu.Hello
  • 在包名的部分,使用“*..”表示包名任意、包的层次深度任意
  • 在类名的部分,类名部分整体用*号代替,表示类名任意
  • 在类名的部分,可以使用*号代替类名的一部分
    • 例如:*Service匹配所有名称以Service结尾的类或接口
  • 在方法名部分,可以使用*号表示方法名任意
  • 在方法名部分,可以使用*号代替方法名的一部分
    • 例如:*Operation匹配所有方法名以Operation结尾的方法
  • 在方法参数列表部分,使用(..)表示参数列表任意
  • 在方法参数列表部分,使用(int,..)表示参数列表以一个int类型的参数开头
  • 在方法参数列表部分,基本数据类型和对应的包装类型是不一样的
    • 切入点表达式中使用 int 和实际方法中 Integer 是不匹配的
  • 在方法返回值部分,如果想要明确指定一个返回值类型,那么必须同时写明权限修饰符
    • 例如:execution(public int ..Service.*(.., int)) 正确
    • 例如:execution(* int ..Service.*(.., int)) 错误

重用切入点表达式

我们希望设置一个切入点表达式,然后在任意切面类中都可以使用,降低代码的重复性

声明:

@Pointcut(切面表达式)

public void xxx(){}

@Pointcut("execution(* com.CalculatorPureImpl.*(..))")
public void pointCut(){}

在同一个切面的使用:

使用xxx()来代表这里的切面表达式,例:

@Before("pointCut()")

在不同切面使用:
需要使用全类名得到切面表达式所在的类,然后使用.xxx()使用切面表达式。例:

@Before("com.CommonPointCut.pointCut()")

获取通知的相关信息

获取连接点信息

获取连接点信息可以在通知方法的参数位置设置 JoinPoint 类型的形参

@Before("pointCut()")
public void beforeMethod(JoinPoint joinPoint){
    //获取连接点的签名信息
    String methodName = joinPoint.getSignature().getName();
    //获取目标方法得到的实参信息
    String args = Arrays.toString(joinPoint.getArgs());
    System.out.println("Logger-->前置通知,方法名:" + methodName + ",参数:" + args);
}

获取目标方法的返回值

@AfterReturning中的属性 returning ,可以设置通知方法的某个形参来接收目标方法的返回值

@AfterReturning(value = "pointCut()",returning = "result")
public void afterReturningMethod(JoinPoint joinPoint,Object result){
    String methodName = joinPoint.getSignature().getName();
    System.out.println("Logger-->返回通知,方法名:"+methodName+",结果:"+result);
}

获取目标方法的异常

@AfterThrowing中的属性throwing,可以设置通知方法的某个形参来接收目标方法的异常

@AfterThrowing(value = "pointCut()",throwing = "ex")
public void afterThrowingMethod(JoinPoint joinPoint, Throwable ex){
    String methodName = joinPoint.getSignature().getName();
    System.out.println("Logger-->异常通知,方法名:"+methodName+",异常:"+ex);
}

注:通知方法中接收异常的形参的类型也可以是Exception

环绕通知

环绕通知相当于另外四种通知的总和

使用环绕通知时需要设置通知方法的形参为 ProceedingJoinPoint 类型

该类型的对象有一个方法:proceed(),代表目标方法的执行

注:当我们使用环绕通知时,通知方法需要返回 目标方法的返回值

@Around("pointCut()")
public Object aroundMethod(ProceedingJoinPoint joinPoint){
    Object result = null;
    try {
        System.out.println("环绕通知-->目标对象方法执行之前");
        joinPoint.proceed();
        System.out.println("环绕通知-->目标对象方法返回值之后");
    } catch (Throwable throwable) {
        throwable.printStackTrace();
        System.out.println("环绕通知-->目标对象方法出现异常时");
    }finally {
        System.out.println("环绕通知-->目标对象方法执行完毕");
    }
    return result;
}

切面的优先级

当一个目标方法上同时存在多个切面时,优先级高的切面先执行

我们可以在切面上通过@Order注解的value属性设置切面的优先级

  • value属性为一个int类型的数字,数字越小,优先级越高,默认值为Integer类型的最大值

基于xml的AOP

注:一般使用注解的方式

<context:component-scan base-package="com.atguigu.aop.xml"></context:componentscan>
  <aop:config>
    <!--配置切面类-->
    <aop:aspect ref="loggerAspect">
      <!--配置公共的切入点表达式-->
      <aop:pointcut id="pointCut" expression="execution(*
        com.atguigu.aop.xml.CalculatorImpl.*(..))"/>
      <aop:before method="beforeMethod" pointcut-ref="pointCut"></aop:before>
      <aop:after method="afterMethod" pointcut-ref="pointCut"></aop:after>
      <aop:after-returning method="afterReturningMethod" returning="result"
        pointcut-ref="pointCut"></aop:after-returning>
      <aop:after-throwing method="afterThrowingMethod" throwing="ex" pointcut-ref="pointCut"></aop:after-throwing>
      <aop:around method="aroundMethod" pointcut-ref="pointCut"></aop:around>
    </aop:aspect>
    <aop:aspect ref="validateAspect" order="1">
      <aop:before method="validateBeforeMethod" pointcut-ref="pointCut">
      </aop:before>
    </aop:aspect>
  </aop:config>

声明式事务

概念

编程式事务:事务功能的相关操作全部通过自己编写代码来实现

声明式事务:

事务控制的代码有规律可循,代码的结构基本是确定的,所以框架就可以将固定模式的代码抽取出来,进行相关的封装。

有以下两个概念:

编程式:自己写代码实现功能

声明式:通过配置让框架实现功能

基于注解的声明式事务

使用步骤

1.添加IOC容器的依赖和Spring持久化层支持jar包,以及mysql相关依赖

<!-- Spring 持久化层支持jar包 -->
<!-- Spring 在执行持久化层操作、与持久化层技术进行整合过程中,需要使用orm、jdbc、tx三个
jar包 -->
<!-- 导入 orm 包就可以通过 Maven 的依赖传递性把其他两个也导入 -->
<dependency>
  <groupId>org.springframework</groupId>
  <artifactId>spring-orm</artifactId>
  <version>5.3.1</version>
</dependency>

<!-- 基于Maven依赖传递性,导入spring-context依赖即可导入当前所需所有jar包 -->
<dependency>
  <groupId>org.springframework</groupId>
  <artifactId>spring-context</artifactId>
  <version>5.3.1</version>
</dependency>

<!-- MySQL驱动 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.33</version>
</dependency>
<!-- 数据源 -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.0.31</version>
</dependency>

注:由于事务已经被封装为框架,不需要我们手动创建切面,因此可以不导入AOP的依赖

2.添加事务管理器

<!-- 导入外部属性文件 -->
<context:property-placeholder location="classpath:jdbc.properties" />
<!-- 配置数据源 -->
<bean id="druidDataSource" class="com.alibaba.druid.pool.DruidDataSource">
  <property name="url" value="${jdbc.url}"/>
  <property name="driverClassName" value="${jdbc.driver}"/>
  <property name="username" value="${jdbc.username}"/>
  <property name="password" value="${jdbc.password}"/>
</bean>
<!-- 配置事务管理器-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
  <property name="dataSource" ref="druidDataSource"></property>
</bean>

添加事务管理器时还需要配置数据源

3.开启事务驱动

<!-- 开启事务的注解驱动-->
<!-- transaction-manager属性的默认值是transactionManager,
如果事务管理器bean的id正好就是这个默认值,则可以省略这个属性 -->
<tx:annotation-driven transaction-manager="transactionManager"></tx:annotation-driven>

注:

1.开启事务的注解驱动时需要指定一个事务管理器的id,且默认值为 transactionManager

2.开启事务驱动时需要导入的名称空间为tx结尾的那个

4.添加事务注解

在需要被事务管理的方法上添加@Transactional注解

@Transactional注解的位置:

1.标识在需要被事务管理的方法上

2.标识在类上,则类中的所有方法都会被事务管理

事务属性

我们可以通过设置@Transactional注解的各个属性来更改事务属性

只读

对于一个查询操作来说,如果我们把它设置成只读,就能明确的告诉数据库,这个操作不涉及写操作。这样数据库就能够针对查询操作来进行优化

@Transactional(readOnly = true)

readOnly的默认值为false

超时

事务在执行过程中,有可能会遇到某些问题,导致程序卡住,从而长时间占用数据库资源。而长时间占用资源,大概率时因为程序运行出现了问题。

此时这个很可能出现问题的程序应该被回滚,撤销它已做的操作,把资源让出来。

即超时回滚,释放资源

@Transactional(timeout = 3)

timeout的值代表超时多少秒就回滚并释放资源并抛出异常

回滚策略

声明式事务默认只针对运行时异常回滚,编译时异常不回滚

可以通过以下属性设置回滚策略

  • rollbackFor属性:需要设置一个Class类型的对象
  • rollbackForClassName属性:需要设置一个字符串类型的全类名
  • noRollbackFor属性:需要设置一个Class类型的对象
  • rollbackFor属性:需要设置一个字符串类型的全类名

注:前两个代表出现那些异常时回滚,后两个代表出现那些异常时不回滚

前两个很少使用,因为默认是出现任何运行时异常都进行回滚

@Transactional(noRollbackFor = ArithmeticException.class)
//@Transactional(noRollbackForClassName = "java.lang.ArithmeticException"

上述代码代表的是出现数学运算异常(ArithmeticException)时不进行回滚 的两种形式

事务隔离级别
@Transactional(isolation = Isolation.DEFAULT)//使用数据库默认的隔离级别
@Transactional(isolation = Isolation.READ_UNCOMMITTED)//读未提交
@Transactional(isolation = Isolation.READ_COMMITTED)//读已提交
@Transactional(isolation = Isolation.REPEATABLE_READ)//可重复读
@Transactional(isolation = Isolation.SERIALIZABLE)//串行化

事务传播行为

当事务方法被另一个事务方法调用时,必须指定事务应该如何传播。

例如:方法可能继续在现有事务中运行,也可能开启一个新事务,并在自己的事务中运行

可以通过@Transactional中的propagation属性设置事务传播行为

注:应该修改被调用的事务方法的propagation属性

@Transactional(propagation = Propagation.REQUIRED),默认情况,表示如果当前线程上有已经开启的事务可用,那么就在这个事务中运行。此时,只要大的事务中出现异常,即使调用的小的事务方法正常执行,也会被回滚。

@Transactional(propagation = Propagation.REQUIRES_NEW),表示不管当前线程上是否有已经开启 的事务,都要开启新事务。此时,如果大的事务中出现异常,但是调用的小的事务方法正常执行,那么小的事务方法的结果会被提交,而大的事务中其他操作正常回滚。

基于xml的声明式事务

使用步骤

1.相对于注解需要多加入下面的依赖

<!-- spring-aspects会帮我们传递过来aspectjweaver -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>5.3.1</version>
</dependency>

2.添加事务管理器

<!-- 导入外部属性文件 -->
<context:property-placeholder location="classpath:jdbc.properties" />
<!-- 配置数据源 -->
<bean id="druidDataSource" class="com.alibaba.druid.pool.DruidDataSource">
  <property name="url" value="${jdbc.url}"/>
  <property name="driverClassName" value="${jdbc.driver}"/>
  <property name="username" value="${jdbc.username}"/>
  <property name="password" value="${jdbc.password}"/>
</bean>
<!-- 配置事务管理器-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
  <property name="dataSource" ref="druidDataSource"></property>
</bean>

添加事务管理器时还需要配置数据源

3.配置事务通知和切入点表达式

<!-- 配置切入点表达式 -->
<aop:config>
<aop:advisor advice-ref="txAdvice" pointcut="execution(*
com.atguigu.spring.tx.xml.service.impl.*.*(..))"></aop:advisor>
</aop:config>

<!-- tx:advice标签:配置事务通知 -->
<!-- id属性:给事务通知标签设置唯一标识,便于引用 -->
<!-- transaction-manager属性:关联事务管理器 -->
<tx:advice id="txAdvice" transaction-manager="transactionManager">
<tx:attributes>
<!-- tx:method标签:配置具体的事务方法 -->
<!-- name属性:指定方法名,可以使用星号代表多个字符 -->
<tx:method name="get*" read-only="true"/>
<tx:method name="query*" read-only="true"/>
<tx:method name="find*" read-only="true"/>
<!-- read-only属性:设置只读属性 -->
<!-- rollback-for属性:设置回滚的异常 -->
<!-- no-rollback-for属性:设置不回滚的异常 -->
<!-- isolation属性:设置事务的隔离级别 -->
<!-- timeout属性:设置事务的超时属性 -->
<!-- propagation属性:设置事务的传播行为 -->
<tx:method name="save*" read-only="false" rollbackfor="java.lang.Exception" propagation="REQUIRES_NEW"/>
<tx:method name="update*" read-only="false" rollbackfor="java.lang.Exception" propagation="REQUIRES_NEW"/>
<tx:method name="delete*" read-only="false" rollbackfor="java.lang.Exception" propagation="REQUIRES_NEW"/>
</tx:attributes>
</tx:advice>

注:配置事务通知时,需要通过 tx:method 标签的 name 属性指定通知方法名来表明那些方法需要开启事务,可以使用 * 通配符,如果方法未被包含在内,那么将表示不开启事务

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/557701.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Transformer中的位置编码详解

什么是位置编码 位置编码概述 位置编码的目的是为了补充序列的位置信息&#xff0c;这是因为自注意力机制本身不包含位置的概念&#xff08;例如顺序信息&#xff09;。位置编码的具体作用是&#xff0c;对于不同的输入序列成分&#xff0c;赋予其不同的位置标识&#xff0c;确…

C++-命名空间

C 命名空间是一种用于组织代码的机制&#xff0c;可以帮助避免命名冲突&#xff0c;提高代码的可读性和可维护性。命名空间将代码分组到逻辑单元中&#xff0c;允许在不同的代码单元中使用相同的名称而不会产生冲突。 命名空间通过将代码放置在一个命名空间内部来实现。在 C 中…

被Claude3的图生代码技术秀到了,前端开发效率,提升到秒级

被Claude3的图生代码技术秀到了&#xff01;前端开发效率&#xff0c;提升到秒级 上传一张网站图片&#xff0c;用Claude3 生成实现这个网站的代码的教程来啦&#xff01; 在Claude3 的中文网站上一分钟就能实现&#xff0c;生成前端代码。中文网站地址是https://askmanyai.c…

探索 IntelliJ IDEA 2024.1最新变化:全面升级助力编码效率

探索 IntelliJ IDEA 2024.1最新变化&#xff1a;全面升级助力编码效率 文章目录 探索 IntelliJ IDEA 2024.1最新变化&#xff1a;全面升级助力编码效率摘要引言 IntelliJ IDEA 2024.1 最新变化关键亮点全行代码补全 Ultimate对 Java 22 功能的支持新终端 Beta编辑器中的粘性行 …

解决跨域和https不能访问的问题。

本地安装了项目,是一键安装的,安装之后还是apache的web服务器,有个视频服务用的是https的服务,要对这个项目进行二次开发,本地调用没问题,可是别人已调用就跨域。只能本地访问。 现在有两个问题:1.解决跨域问题 2.还要解决https访问的问题。 解决思路,用nginx 的ssl证…

本地项目如何设置https——2024-04-19

问题&#xff1a;由于项目引用了html5-qrcode插件&#xff0c;但是该插件在本地移动端调试时只能使用https访问&#xff0c;所有原本的本地地址是http&#xff0c;就需要改成https以方便调试。 解决方法&#xff1a;使用本地https证书 1&#xff09;从项目文件下打开cmd逐步输…

Springboot配置文件(application.yml)的加载顺序

spring boot 启动会扫描一下位置的application.properties或者application.yml文件作为Spring boot的默认配置文件 file…/config/ file…/ classpath:/config classpath:/ 以上是按照优先级从高到低的顺序&#xff0c;所有位置的文件都会被加载&#xff0c;高优先级配置内容会…

代码随想录算法训练营第四十四天| LeetCode70. 爬楼梯 (进阶)、322. 零钱兑换、279.完全平方数

一、LeetCode 70. 爬楼梯 &#xff08;进阶&#xff09; 题目链接/文章讲解/视频讲解&#xff1a;https://programmercarl.com/0070.%E7%88%AC%E6%A5%BC%E6%A2%AF%E5%AE%8C%E5%85%A8%E8%83%8C%E5%8C%85%E7%89%88%E6%9C%AC.html 状态&#xff1a;已解决 1.思路 这道题跟70.爬楼…

突破“三个九”!离子阱量子计算再创新高

如果把量子计算比作一场球赛&#xff0c;Quantinuum无疑又打了一记漂亮的好球。实际上&#xff0c;结合今年春季在量子体积、逻辑量子比特和布线问题等方面的进展&#xff0c;这个团队已经接近于完成一场完美的比赛。 3月&#xff0c;Quantinuum的研究人员证明了QCCD架构的可扩…

JavaScript 流程控制-循环

一、循环 二、 for 循环 重复执行的语句被称为循环体&#xff0c;能否继续重复执行&#xff0c;取决于循环的终止条件。 由循环体及循环的终止条件组成的语句被称为循环语句 1、语法结构 for 循环 主要用于把某些代码循环若干次&#xff0c;通常跟计数有关 for &#xff08…

springboot结合vue实现文件上传下载功能

紧接着上一次的博客&#xff0c;这次来实现一下文件(主要是图片)的上传和下载功能&#xff0c;上一次的博客如下所示&#xff1a; Springboot集成JWT token实现权限验证-CSDN博客 其实文件的上传和下载功能(后端的部分)&#xff0c;在我之前的博客就已经有写了&#xff0c;所以…

力扣经典150题第三十题:长度最小的子数组

目录 力扣经典150题解析之三十&#xff1a;长度最小的子数组1. 介绍2. 问题描述3. 示例4. 解题思路方法一&#xff1a;滑动窗口 5. 算法实现6. 复杂度分析7. 测试与验证测试用例设计测试结果分析 8. 进阶9. 总结10. 参考文献感谢阅读 力扣经典150题解析之三十&#xff1a;长度最…

重构国内游戏账号登录系统的思考和实践

本期作者 背景 账号登录系统&#xff0c;作为游戏发行平台最重要的应用之一&#xff0c;在当前的发行平台的应用架构中&#xff0c;主要承载的是用户的账号注册、登录、实名、防沉迷、隐私合规、风控等职责。合规作为企业经营的生命线&#xff0c;同时&#xff0c;账号登录作为…

数据结构系列-堆的实现

&#x1f308;个人主页&#xff1a;羽晨同学 &#x1f4ab;个人格言:“成为自己未来的主人~” 堆的实现&#xff0c;其实也就是二叉树的实现&#xff0c;我们在这里是基于数组对其进行实现的&#xff01; typedef struct Heap {HPDataType* a;int size;int capacity; }HP;…

毕业设计做一个linux操作系统怎么样?

毕业设计选择做操作系统的话&#xff0c;不太建议做的规模太大&#xff0c;你可以参考一下Linux内核的代码量&#xff0c;完全从头写的工作量还是挺大的。如果是一行一行从头写&#xff0c;学生期间&#xff0c;一学期写10000-20000行有效代码就很强了&#xff0c;而且还要学习…

【面经】2024春招-云计算后台研发工程师1(3个问题,移动TW等)

【面经】2024春招-云计算后台研发工程师1&#xff08;3个问题&#xff0c;移动&TW等&#xff09; 文章目录 岗位与面经基础1&#xff1a;数据库 & 网络&#xff08;3个问题&#xff09;基础2&#xff1a;系统 & 语法模板3&#xff1a;算法 & 项目&#xff08;移…

探索人工智能绘图的奇妙世界

探索人工智能绘图的奇妙世界 人工智能绘图的基本原理机器之美&#xff1a;AI绘图作品AI绘图对艺术创作的影响未来展望与挑战图书推荐&#x1f449;AI绘画教程&#xff1a;Midjourney使用方法与技巧从入门到精通内容简介获取方式&#x1f449;搜索之道&#xff1a;信息素养与终身…

Timelapse - 2024.04.09 -Win

阅读须知&#xff1a; 探索者安全团队技术文章仅供参考,未经授权请勿利用文章中的技术资料对任何计算机系统进行入侵操作,由于传播、利用本公众号所提供的技术和信息而造成的任何直接或者间接的后果及损失&#xff0c;均由使用者 本人负责&#xff0c;作者不为此承担任何责任,如…

centos6.5重启docker容器死机问题

概述 近期在整理服务问题&#xff0c;使用docker容器重新部署服务。 过程中有不少坑&#xff0c;主要是系统配置和系统版本的问题。 环境 CentOS release 6.5 (Final) docker version 1.7.1 问题现象 使用restart命令重启docker容器&#xff0c;系统突然卡死&#xff0c…

protobuf抓包,读包

protobuf抓包 有时候会遇到使用protobuf协议的http请求, 而protobuf封包后的二进制几乎不可读, 如何调试呢 protobuf就是类似一个json的数据传输协议, 相比json更快, 体积更小; 缺点就是不可读 Content-Type: application/x-protobuf数据大概是下面这样的(浏览器开发者工具 自…