自定义注解及应用场景

2023-11-17

自定义注解及应用场景

——深圳蜗牛学苑

课程目标

  1. 理解自定义注解概念
  2. 掌握自定义注解语法
  3. 自定义注解场景中的使用
  4. 熟练掌握自定义注解+AOP的使用方式
一、自定义注解

我们回顾一下方法的重写,会发现有@Override,我们把它称为重写的注解。那么注解到底什么呢?我们能不能自定义注解。

1、概念:

注解是一种能被添加到java代码中的元数据,类、方法、变量、参数和包都可以用注解来修饰。注解对于它所修饰的代码并没有直接的影响。

* 注解是一种元数据形式。即注解是属于java的一种数据类型,和类、接口、数组、枚举类似。
* 注解用来修饰,类、方法、变量、参数、包。
* 注解不会对所修饰的代码产生直接的影响。

既然注解是一种数据类型,那我们自然可以去自定义的使用它

2、自定义注解的语法

​ 简单来说自定义注解分为三步:

  • 定义注解——相当于定义标记;

  • 配置注解——把标记打在需要用到的程序代码中;

  • 解析注解——在编译期或运行时检测到标记,并进行特殊操作。

    基本语法:

​ 注解在Java中,与类、接口、枚举类似,因此其声明语法基本一致,只是所使用的关键字有所不同@interface

在底层实现上,所有定义的注解都会自动继承java.lang.annotation.Annotation接口

public @interface TestAnnotation {
    
}

​ 根据我们在自定义类的经验,在类的实现部分无非就是书写构造、属性或方法。但是,在自定义注解中,其实现部分只能定义一个东西:注解类型元素(annotation type element)

/**
 * 自定义注解 @interface
 */
public @interface TestAnnotation {
    //定义注解类型元素
    public String name();
    int age() default 18;  //default代表带有默认值为18
    int[] array();
}

注解定义就只包括了上面的两部分内容:

  • 1、注解的名字
  • 2、注解包含的类型元素

定义注解类型元素时需要注意如下几点:

  • 访问修饰符必须为public,不写默认为public;
  • 该元素的类型只能是基本数据类型、String、Class、枚举类型、注解类型(体现了注解的嵌套效果)以及上述类型的一位数组;
  • 该元素的名称一般定义为名词,如果注解中只有一个元素,请把名字起为value(后面使用会带来便利操作);
  • ()不是定义方法参数的地方,也不能在括号中定义任何参数,仅仅只是一个特殊的语法;
  • default代表默认值,值必须和第2点定义的类型一致;
  • 如果没有默认值,代表后续使用注解时必须给该类型元素赋值。

除了定义好注解,在哪用?怎么用?注意些什么?说这些之前,我们还需要补充一个概念:元注解

3、元注解

​ (1) @Target:是专门用来限定某个自定义注解能够被应用在哪些Java元素上面的

public enum ElementType {
    /** 类,接口(包括注解类型)或枚举的声明 */
    TYPE,

    /** 属性的声明 */
    FIELD,

    /** 方法的声明 */
    METHOD,

    /** 方法形式参数声明 */
    PARAMETER,

    /** 构造方法的声明 */
    CONSTRUCTOR,

    /** 局部变量声明 */
    LOCAL_VARIABLE,

    /** 注解类型声明 */
    ANNOTATION_TYPE,

    /** 包的声明 */
    PACKAGE
}
//@CherryAnnotation被限定只能使用在类、接口或方法上面
@Target(value = {ElementType.TYPE,ElementType.METHOD})
public @interface TestAnnotation {
    String name();
    int age() default 18;
    int[] array();
}

(2)@Retention注解,翻译为持久力、保持力。即用来修饰自定义注解的生命力。
注解的生命周期有三个阶段:1、Java源文件阶段;2、编译到class文件阶段->默认;3、运行期阶段。

​ 同样使用了RetentionPolicy枚举类型定义了三个阶段:

public enum RetentionPolicy {
    /**
     * Annotations are to be discarded by the compiler.
     * (注解将被编译器忽略掉)
     */
    SOURCE,

    /**
     * Annotations are to be recorded in the class file by the compiler
     * but need not be retained by the VM at run time.  This is the default
     * behavior.
     * (注解将被编译器记录在class文件中,但在运行时不会被虚拟机保留,这是一个默认的行为)
     */
    CLASS,

    /**
     * Annotations are to be recorded in the class file by the compiler and
     * retained by the VM at run time, so they may be read reflectively.
     * (注解将被编译器记录在class文件中,而且在运行时会被虚拟机保留,因此它们能通过反射被读取到)
     * @see java.lang.reflect.AnnotatedElement
     */
    RUNTIME
}
  • 如果一个注解被定义为RetentionPolicy.SOURCE,则它将被限定在Java源文件中,那么这个注解即不会参与编译也不会在运行期起任何作用,这个注解就和一个注释是一样的效果,只能被阅读Java文件的人看到;
  • 如果一个注解被定义为RetentionPolicy.CLASS,则它将被编译到Class文件中,那么编译器可以在编译时根据注解做一些处理动作,但是运行时JVM(Java虚拟机)会忽略它,我们在运行期也不能读取到;
  • 如果一个注解被定义为RetentionPolicy.RUNTIME,那么这个注解可以在运行期的加载阶段被加载到Class对象中。那么在程序运行阶段,我们可以通过反射得到这个注解,并通过判断是否有这个注解或这个注解中属性的值,从而执行不同的程序代码段。我们实际开发中的自定义注解几乎都是使用的RetentionPolicy.RUNTIME;
  • 在默认的情况下,自定义注解是使用的RetentionPolicy.CLASS。

(3)@Documented,是被用来指定自定义注解是否能随着被定义的java文件生成到JavaDoc文档当中。

(4)@Inherited注解,是指定某个自定义注解如果写在了父类的声明部分,那么子类的声明部分也能自动拥有该注解。@Inherited注解只对那些@Target被定义 为ElementType.TYPE的自定义注解起作用。

说完了元注解,我们可以看到元注解其实就是用来配置我们的自定义注解,让自定义注解更加精确的使用

8、自定义注解补充

@Retention(RetentionPolicy.RUNTIME) //规定运行时
@Target(value = {ElementType.METHOD}) //规定在方法上使用
@Documented//JavaDoc文档
public @interface CherryAnnotation {
    String name();
    int age() default 18;
    int[] score();
}

简单分析下:

  • CherryAnnotation的@Target定义为ElementType.METHOD,那么它书写的位置应该在方法定义的上方
  • 由于我们在CherryAnnotation中定义的有注解类型元素,而且有些元素是没有默认值的,这要求我们在使用的时候必须在标记名后面打上(),并且在()内以“元素名=元素值“的形式挨个填上所有没有默认值的注解类型元素(有默认值的也可以填上重新赋值),中间用“,”号分割;

9、使用注解

public class Student {
    @CherryAnnotation(name = "cherry-peng",age = 23,score = {99,66,77})
    public void study(int times){
        for(int i = 0; i < times; i++){
            System.out.println("Good Good Study, Day Day Up!");
        }
    }
}
4、自定义注解的value成员:

(1)如果注解本身没有注解类型元素,那么在使用注解的时候可以省略(),直接写为:@注解名,它和标准语法@注解名()等效!

@Retention(RetentionPolicy.RUNTIME)
@Target(value = {ElementType.TYPE})
@Documented
public @interface FirstAnnotation {
}
//等效于@FirstAnnotation()
@FirstAnnotation
public class JavaBean{
	//省略实现部分
}

(2)如果注解本本身只有一个注解类型元素,而且命名为value,那么在使用注解的时候可以直接使用:@注解名(注解值),其等效于:@注解名(value = 注解值)

@Retention(RetentionPolicy.RUNTIME)
@Target(value = {ElementType.TYPE})
@Documented
public @interface SecondAnnotation {
	String value();
}
//等效于@ SecondAnnotation(value = "this is second annotation")
@SecondAnnotation("this is annotation")
public class JavaBean{
	//省略实现部分
}

(3)如果注解中的某个注解类型元素是一个数组类型,在使用时又出现只需要填入一个值的情况,那么在使用注解时可以直接写为:@注解名(类型名 = 类型值),它和标准写法:@注解名(类型名 = {类型值})等效!

@Retention(RetentionPolicy.RUNTIME)
@Target(value = {ElementType.TYPE})
@Documented
public @interface ThirdAnnotation {
	String[] name();
}
//等效于@ ThirdAnnotation(name = {"this is third annotation"})
@ThirdAnnotation(name = "this is third annotation")
public class JavaBean{
	//省略实现部分
}
二、自定义注解应用场景

1、自定义注解的使用场景很多,我们在造轮子写框架的过程经常会使用到。在我们平时的项目场景中,也可以随处可以看到自定义注解可以发挥作用的场景,例如:

​ 1)全局日志的打印

​ 2)用户权限的校验

​ 3)登录的校验

​ 4)数据脱敏

等等场景。

2、而在我们后面的微服务阶段,自定义注解也可以用来测试和熔断服务。

我们也在这里列举出几个业务场景,来模拟一下自定义注解在我们实际项目场景中的使用:

1、业务场景1:权限校验

自定义注解经常和springAOP一起配合使用,能让aop的增强更加轻松和优雅,消除冗余代码。例如日志,登录校验,权限校验等。

(1)首先我们定义了一个缓存类CacheManager来模拟保存两个用户zhangsan和lisi,并给予他们一定的角色

package com.woniu.test4;

import java.util.*;

public class CacheManager {
    //初始化一个map集合来存储用户名和该用户所拥有的的角色
    public static Map<String, List<String>> USER_ROLES = new HashMap<>();
    static {
        //初始化一个list集合来存储角色
        List<String> roles = new ArrayList<>();
        roles.add("admin");
        roles.add("user");

        List<String> roles2 = new ArrayList<>();
        roles2.add("user");

        USER_ROLES.put("zhangsan",roles);
        USER_ROLES.put("lisi",roles2);
    }
}

(2)controller 层中设计删除,新增,查询方法,我们将要实现使得这些方法拥有一些角色限定,如果没有该角色的用户,是无法执行该方法。

@RestController
public class UserController2 {
    @GetMapping("/deleteUser")
    public String deleteUser(){
        return "执行了删除用户的方法!";
    }

    @GetMapping("/addUser")
    public String addUser(){
        return "执行了新增用户的方法!";
    }

    @GetMapping("/list")
    public String list(){
        return "执行了查询用户的方法!";
    }
}

(3)首先定义一个 自定义注解HasRole,并且保存一个成员value来存储角色名称

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface HasRole {
    //存储角色名称
    String value();
}

(3)在controller上面增加自定义注解,并且表名该方法所具有的权限名称

@RestController
public class UserController2 {
    @HasRole("admin")
    @GetMapping("/deleteUser")
    public String deleteUser(){
        return "执行了删除用户的方法!";
    }

    @HasRole("user")
    @GetMapping("/addUser")
    public String addUser(){
        return "执行了新增用户的方法!";
    }

    @GetMapping("/list")
    public String list(){
        return "执行了查询用户的方法!";
    }
}

(4)通过springAOP 来定义一个切面类,并且使用前置通知,获取用户名称从而来判定该用户是否具有响应的权限

@Aspect //定义切面类
@Component
public class AppConfig {
    //定义切点
    @Pointcut("@annotation(com.woniu.test4.HasRole)")
    public void pointcut(){}

    //前置通知
    @Before("pointcut()")
    public void before(JoinPoint joinPoint){
        //通过RequestContextHolder来获得ServletRequestAttributes
        ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        //通过ServletRequestAttributes来获得请求对象request
        HttpServletRequest request = requestAttributes.getRequest();
        String username = request.getParameter("username");

        //获取当前角色的权限集合
        List<String> strings = CacheManager.USER_ROLES.get(username);
        //获取当前方法
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        //反射获取当前方法对象
        Method method = signature.getMethod();
        //获取当前方法头顶的注解
        HasRole hasRole = method.getDeclaredAnnotation(HasRole.class);

        //判断是否为空
        if(hasRole!=null&&(strings==null || !strings.contains(hasRole.value()))){
            throw new RuntimeException("用户没有访问权限!");
        }

    }

}

(5)测试

当我们使用lisi用户时,李四用户只有user角色,所以是不能执行delete方法,报出异常

image-20230417173349841

image-20230417173432683

如果,我们传入的用户名为zhangsan,则具有admin权限,页面正常展示。

这样我们就用自定义注解来实现了角色权限的简单校验,当然,直接报出异常的方式不是很友好,我们只需要写出一个全局异常处理就能友好的解决了。

2、业务场景2:敏感数据的脱敏

在很多项目中,存储了大量的用户敏感信息,而这些信息如果直接显示在网页上,是具有很大的安全问题,所以我们需要在数据向网页展示时,先经过脱敏的处理。

所谓脱敏其实就是敏感数据的半隐藏,例如身份证,手机号,用户名等等。

image-20230417173918032

(1)首先我们准备好一个实体类

@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {
    // 用户手机号
    private String userPhone;
    // 用户身份证号
    private String id_card;
}

(2)在controller层初始化一个对象,并且将信息返回

@RestController
public class UserController {
    @GetMapping("/user2")
    public User getUserInfo(){
        return new User("13239687323","6229923199910010014");
    }
}

此时测试,数据是会完整的展现在页面上

image-20230417174435763

那么此处,我们同样可以使用springAOP+自定义注解来获取到属性中的数据,并且手动脱敏,这次我们可以尝试一下不一样的写法。

2.1 认识 @JsonSerialize注解的使用

在开发中,我们将对象序列化为JSON传输给前端,有时候我们的某个或者某些字段需要特殊处理。

比如我们有一个日期字段,我们希望当日期为NULL时给前端不返回NULL而返回为未完成等信息,或者我们有一些状态字段,我们需要给前端返回状态对应的信息,此时我们就需要自定义字段的序列化。这就是@JsonSerialize的用处

第一步,我们先写一个指定的处理类

import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.JsonSerializer;
import com.fasterxml.jackson.databind.SerializerProvider;

import java.io.IOException;
import java.util.Date;

//该类需要继承JsonSerializer
public class DateJsonSerialize extends JsonSerializer<Date> {
 		
    //重写serialize方法
    @Override
    public void serialize(Date date, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException {
        //自定义处理方式
        jsonGenerator.writeString("未填写");
    }
}

第二步,在此字段上加上@JsonSerialize注解,并且使用nullsUsing属性,即当此字段为NULL时就使用我们自定义的解析类序列化此字段(注意当此字段不为NULL时不执行此配置)

import java.util.Date;

public class Student {
    private String stuName;
    private Integer stuAge;
    //当该属性为null时,会触发json的序列化
    @JsonSerialize(nullsUsing = DateJsonSerialize.class)
    private Date birthday;
}

测试

image-20230417180206600

既然JsonSerialize可以帮助我们重新序列化属性数据,我们正好可以使用这个机制,配合自定义注解,来完成我们数据脱敏

2.2 重新封装一个SensitiveJsonSerializer类来处理user的数据脱敏
public class SensitiveJsonSerializer extends JsonSerializer<String>  {
    @Override
    public void serialize(String value, JsonGenerator gen, SerializerProvider serializers) throws IOException {
        //手动脱敏
        String substring = value.substring(value.length() - 4);
        value = "***"+ substring;
        gen.writeString(value);
    }
2.3 封装出自定义注解
//自定义注解
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
@JacksonAnnotationsInside //这个注解用来标记Jackson复合注解,当你使用多个Jackson注解组合成一个自定义注解时会用到它
@JsonSerialize(using = SensitiveJsonSerializer.class) //指定使用自定义的序列化器
public @interface Sensitive {
}

@JsonSerialize来指明我们自己的序列化器

当我们自定义的注解要和Jackson注解组合时,需要标注@JacksonAnnotationsInside,来表明这是一个符合注解

测试

image-20230417182017563

2.4 优化代码

现在已经实现出了我们的效果,但是我们的代码仍可以做进一步的优化,因为现在数据脱敏的格式比较单一,而且如果当需要脱敏的规则改变或者需要脱敏的属性增加时,我们仍然需要回去修改源代码,不如我们用一个枚举enum来专门存储所有的脱敏规则

2.4.1 新建一个enum类SensitiveStrategy

在这个枚举中,我们需要定义出各种类型属性的脱敏规则

思考—enum中,成员的类型应该是什么?

既然每个成员除了脱敏规则不一样,脱敏的流程都是一样的,那我们就可以设计成jdk8中Function<T,R>作为成员类型,这样我们就可以使用Fuction中的apply方法来消除掉重复的调用脱敏方法的代码。

public enum SensitiveStrategy {
    //常量成员
    //正则表达式来存储脱敏规则,格式规定为:132***7323
    USERNAME(s -> s.replaceAll("(\\S)\\S(\\S*)", "$1*$2")),
    ID_CARD(s -> s.replaceAll("(\\d{3})\\d{13}(\\w{2})", "$1****$2")),
    PHONE(s -> s.replaceAll("(\\d{3})\\d{4}(\\d{4})", "$1****$2")),
    ADDRESS(s -> s.replaceAll("(\\S{3})\\S{2}(\\S*)\\S{2}", "$1****$2****"));
    //构造方法和get/set
    private Function<String, String> desensitizes;

    SensitiveStrategy(Function<String, String> desensitizes) {
        this.desensitizes = desensitizes;
    }

    public Function<String, String> getDesensitizes() {
        return desensitizes;
    }

    public void setDesensitizes(Function<String, String> desensitizes) {
        this.desensitizes = desensitizes;
    }
}

提示:正则表达式,大家只需要看懂即可,不需要记忆~

2.4.2 优化SensitiveJsonSerializer类

既然脱敏的代码封装到枚举中,那在SensitiveJsonSerializer类的serialize序列化方法中,只需要调用一下function的apply方法即可

public class SensitiveJsonSerializer extends JsonSerializer<String>  {
    //提供枚举成员属性
    private SensitiveStrategy strategy;
    @Override
    public void serialize(String value, JsonGenerator gen, SerializerProvider serializers) throws IOException {
        //调用enum中function的apply方法,即 脱敏方法,传入原数据
        gen.writeString(strategy.getDesensitizes().apply(value));
    }
}
2.4.3 优化自定义注解
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
@JacksonAnnotationsInside //这个注解用来标记Jackson复合注解,当你使用多个Jackson注解组合成一个自定义注解时会用到它
@JsonSerialize(using = SensitiveJsonSerializer.class) //指定使用自定义的序列化器
public @interface Sensitive {
    SensitiveStrategy value();   //该自定义注解需要的参数   strategy-参数名称    SensitiveStrategy-参数类型
}

在自定义注解中加入enum的成员属性

原因:之前我们是统一将数据脱敏程 ***XXX的格式,这样显然和真实场景不同。加入strategy成员,来标注出该属性需要走哪个类型的脱敏,从而让枚举自动识别出来并且进行脱敏

2.4.4 实体类
@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {
    // 用户手机号
    @Sensitive(SensitiveStrategy.PHONE)
    private String userPhone;
    // 用户身份证号
    @Sensitive(SensitiveStrategy.ID_CARD)
    private String id_card;
}

测试,发现报出NullPointer异常,debug一下,不难发现如下问题:

image-20230418103716480

我们需要获取到属性头顶的注解对象,并且赋值到这个属性中,才可以正确执行

//ContextualSerializer是 Jackson 提供的另一个序列化相关的接口,它的作用是通过字段已知的上下文信息定制JsonSerializer,只需要实现createContextual方法即可
public class SensitiveJsonSerializer extends JsonSerializer<String> implements ContextualSerializer{
    private SensitiveStrategy strategy;
    @Override
    public void serialize(String value, JsonGenerator gen, SerializerProvider serializers) throws IOException {
        //调用fuction apply方法,传入数据
        gen.writeString(strategy.getDesensitizes().apply(value));
    }

    @Override
    public JsonSerializer<?> createContextual(SerializerProvider prov, BeanProperty property) throws JsonMappingException {
        // 通过反射获取注解对象
        Sensitive annotation = property.getAnnotation(Sensitive.class);
        if (Objects.nonNull(annotation)) {
            this.strategy = annotation.value();
            return this;
        }
        //如果为null,则说明没有添加注解对象,则返回默认的序列化的对象即可
        return prov.findValueSerializer(property.getType(), property);
    }
}

测试

image-20230418120830440

3、业务场景3:全局日志
3.1 封装记录日志实体类
@Data
@AllArgsConstructor
@NoArgsConstructor
public class MaterialEamLog {
    private String params; //参数
    private String method; //方法
    private String result; //返回结果
    private String url; //URL地址
    private String ip; // ip地址
    private String time; //执行时间
}
3.2 定义serviceImpl层(模拟入库)
@Service
public class IMaterialEamLogService {
    public boolean insert(MaterialEamLog materialEamLog){
        System.out.println(materialEamLog);
        System.out.println("执行新增方法,将日志入库");
        return true;
    }
}
3.3 自定义注解
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
@Documented
public @interface RequiredLog {
}
3.4 定义AOP切面类
@Aspect
@Component
@Slf4j
public class SysLogAspect {
    @Autowired
    private IMaterialEamLogService materialEamLogService;

    /**
     * @Pointcut 注解用于描述或定义一个切入点
     *  切入点的定义需要遵循spring中指定的表达式规范
     */
    @Pointcut("@annotation(com.woniu.test6.RequiredLog)")
    public void logPointCut() {}

    /**
     * @Around 注解描述的方法为一个环绕通知方法,
     * 在此方法中可以添加扩展业务逻辑,可以调用下一个
    切面对象或目标方法
     * @param jp 连接点(此连接点只应用@Around描述的方法)
     * @return
     * @throws Throwable
     */
    @Around("logPointCut()")
    public Object aroundAdvice(ProceedingJoinPoint jp)
            throws Throwable{
        long start=System.currentTimeMillis();
        log.info("start:"+start);
        try {
            //调用下一个切面或目标方法
            result = jp.proceed();
        }finally {
            long end=System.currentTimeMillis();
            log.info("end:"+end);
            //记录日志(用户行为信息)
            saveLog(jp,result,String.valueOf(end-start));
        }
        return result;
    }


    //日志记录
    private void saveLog(ProceedingJoinPoint jp,Object result,String time) {
        //1.获取用户行为日志(ip,username,operation,method,params,time,createdTime)
        //获取类的字节码对象,通过字节码对象获取方法信息
        Class<?> targetCls=jp.getTarget().getClass();
        //获取方法签名(通过此签名获取目标方法信息)
        MethodSignature ms=(MethodSignature)jp.getSignature();
        //获取目标方法名(目标类型+方法名)
        String targetClsName=targetCls.getName();
        String targetObjectMethodName=targetClsName+"."+ms.getName();
        //获取请求参数
        String targetMethodParams= Arrays.toString(jp.getArgs());
        String url = "";
        String ipAddr = "";
        if(RequestContextHolder.getRequestAttributes() != null){
            //获取请求url
            HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
            url = request.getRequestURL().toString();
            ipAddr = request.getRemoteHost();
        }
        //2.封装用户行为日志(SysLog)
        //插入日志--start
        MaterialEamLog log = new MaterialEamLog();
        log.setParams(targetMethodParams);
        log.setMethod(targetObjectMethodName);
        //获取注解中的参数
        log.setResult(String.valueOf(result));
        log.setUrl(url);
        log.setIp(ipAddr);
        log.setTime(time);
        //3.调用业务层对象方法(saveObject)将日志写入到数据库
        materialEamLogService.insert(log);
    }
3.5 测试

image-20230420152925336

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

自定义注解及应用场景 的相关文章

  • JavaMail Gmail 问题。 “准备启动 TLS”然后失败

    mailServerProperties System getProperties mailServerProperties put mail smtp port 587 mailServerProperties put mail smtp
  • AES 加密 Java/plsql

    我需要在Java和plsql DBMS CRYPTO for Oracle 10g 上实现相同的加密 解密应用程序 两种实现都工作正常 但这里的问题是我对相同纯文本的加密得到了不同的输出 下面是用于加密 解密过程的代码 Java 和 PLS
  • Android Studio 在编译时未检测到支持库

    由于 Android Studio 将成为 Android 开发的默认 IDE 因此我决定将现有项目迁移到 Android studio 中 项目结构似乎不同 我的项目中的文件夹层次结构如下 Complete Project gt idea
  • 一种使用 Java Robot API 和 Selenium WebDriver by Java 进行文件上传的解决方案

    我看到很多人在使用 Selenium WebDriver 的测试环境中上传文件时遇到问题 我使用 selenium WebDriver 和 java 也遇到了同样的问题 我终于找到了解决方案 所以我将其发布在这里希望对其他人有所帮助 当我需
  • Java 页面爬行和解析之 Crawler4j 与 Jsoup

    我想获取页面的内容并提取其中的特定部分 据我所知 此类任务至少有两种解决方案 爬虫4j https github com yasserg crawler4j and Jsoup http jsoup org 它们都能够检索页面的内容并提取其
  • Spring引导@Transactional

    spring boot会自动在controller层添加 Transactional注解吗 我尝试将 Transactional 放在服务层 但似乎控制器层覆盖了注释 我有这个配置
  • 请求位置更新参数

    这就是 requestLocationUpdates 的样子 我使用它的方式 requestLocationUpdates String provider long minTime float minDistance LocationLis
  • 迁移到 java 17 后有关“每个进程的内存映射”和 JVM 崩溃的 GC 警告

    我们正在将 java 8 应用程序迁移到 java 17 并将 GC 从G1GC to ZGC 我们的应用程序作为容器运行 这两个基础映像之间的唯一区别是 java 的版本 例如对于 java 17 版本 FROM ubuntu 20 04
  • 检查 Android 手机上的方向

    如何查看Android手机是横屏还是竖屏 当前配置用于确定要检索的资源 可从资源中获取Configuration object getResources getConfiguration orientation 您可以通过查看其值来检查方向
  • 反思 Groovy 脚本中声明的函数

    有没有一种方法可以获取 Groovy 脚本中声明的函数的反射数据 该脚本已通过GroovyShell目的 具体来说 我想枚举脚本中的函数并访问附加到它们的注释 Put this到 Groovy 脚本的最后一行 它将作为脚本的返回值 a la
  • 归并排序中的递归:两次递归调用

    private void mergesort int low int high line 1 if low lt high line 2 int middle low high 2 line 3 mergesort low middle l
  • Java中未绑定通配符泛型的用途和要点是什么?

    我不明白未绑定通配符泛型有什么用 具有上限的绑定通配符泛型 stuff for Object item stuff System out println item Since PrintStream println 可以处理所有引用类型 通
  • 运行 Jar 文件时出现问题

    我已将 java 项目编译成 Jar 文件 但运行它时遇到问题 当我跑步时 java jar myJar jar 我收到以下错误 Could not find the main class myClass 类文件不在 jar 的根目录中 因
  • Keycloak - 自定义 SPI 未出现在列表中

    我为我的 keycloak 服务器制作了一个自定义 SPI 现在我必须在管理控制台上配置它 我将 SPI 添加为模块 并手动安装 因此我将其放在 module package name main 中 并包含 module xml 我还将其放
  • Netbeans 8 不会重新加载静态 Thymeleaf 文件

    我通过 Maven 使用 Spring Boot 和 Thymeleaf 当我进行更改时 我似乎无法让 Netbeans 自动重新部署我的任何 Thymeleaf 模板文件 为了看到更改 我需要进行完整的清理 构建 运行 这需要太长的时间
  • 查看Jasper报告执行的SQL

    运行 Jasper 报表 其中 SQL 嵌入到报表文件 jrxml 中 时 是否可以看到执行的 SQL 理想情况下 我还想查看替换每个 P 占位符的值 Cheers Don JasperReports 使用 Jakarta Commons
  • 如何测试 spring-security-oauth2 资源服务器安全性?

    随着 Spring Security 4 的发布改进了对测试的支持 http docs spring io spring security site docs 4 0 x reference htmlsingle test我想更新我当前的
  • 如何修复“sessionFactory”或“hibernateTemplate”是必需的问题

    我正在使用 Spring Boot JPA WEB 和 MYSQL 创建我的 Web 应用程序 它总是说 sessionFactory or hibernateTemplate是必需的 我该如何修复它 我已经尝试过的东西 删除了本地 Mav
  • java8 Collectors.toMap() 限制?

    我正在尝试使用java8Collectors toMap on a Stream of ZipEntry 这可能不是最好的想法 因为在处理过程中可能会发生异常 但我想这应该是可能的 我现在收到一个我不明白的编译错误 我猜是类型推理引擎 这是
  • Jackson 将单个项目反序列化到列表中

    我正在尝试使用一项服务 该服务为我提供了一个带有数组字段的实体 id 23233 items name item 1 name item 2 但是 当数组包含单个项目时 将返回该项目本身 而不是包含一个元素的数组 id 43567 item

随机推荐

  • 【Linux旅行记】进度条小程序

    文章目录 一 预备知识 1 1回车换行 1 2缓冲区 二 倒计时 三 进度条 3 1普通版本源代码 3 2高级版本源代码 小结 博客主页 小智 x0 0x 欢迎关注 点赞 收藏 留言 系列专栏 Linux入门到精通 代码仓库 小智的代码仓库
  • word中目录右边页码对不齐解决方法

    这个目录对不齐原因未知 解决方法 1 在视图中打开标尺 2 选择对不齐的目录项 如果整个目录都有出现不对齐 选择整个目录 3 拖动标尺 进行对齐 4 被治愈了
  • GDB调试的基本使用、GDB调试多进程

    1 编译时加选项 g 生成具有调试信息的程序 gcc g test c o test 2 启动GDB 1 启动GDB gdb test 2 设置运行时参数 主函数中可接收运行时参数 set args 设置运行时参数 如set args 10
  • i.mx287学习笔记9-编译mplayer源码

    上面是我的微信和QQ群 欢迎新朋友的加入 1 下载资源 mplayer http www mplayerhq hu MPlayer releases 这个我编译没用到 但是我看很多帖子都要这个东西 不管他 也找个资源过来 编码库 http
  • C/C++打开目录、读取目录、获取目录下文件状态

    1 程序示例 lstat 或者 stat 需要包含的头文件 include
  • Composite:组合模式

    将对象组合成树形结构以表示 部分 整体 的层次结构 组合模式使得用户对单个对象和组合对象的使用具有一致性 处理树中的每个节点时 其实不用考虑他是叶子节点还是根节点 即模糊了简单元素和复杂元素的概念 客户端可以像处理简单元素一样来处理复杂元素
  • BP神经网络识别手写数字项目解析及代码

    这两天在学习人工神经网络 用传统神经网络结构做了一个识别手写数字的小项目作为练手 点滴收获与思考 想跟大家分享一下 欢迎指教 共同进步 平常说的BP神经网络指传统的人工神经网络 相比于卷积神经网络 CNN 来说要简单些 人工神经网络具有复杂
  • 结构体中的函数指针

    C语言中的类 大家知道C 是面向对象的语言 有很多优良特性 而在C语言中 我们也可以用结构体类似的实现面向对象 成员函数 既然说了用结构体类似的实现某种类 结构体中的变量就可以看做类的变量 实现类的成员函数就要用到函数指针了 一般的函数指针
  • Siebel EAI- Web Service 常见错误汇总

    由于项目原因部分代码被模糊处理 有些问题待补充 欢迎指出错误 很多试探着理解的 研究的不够深入 问题1 Error invoking service XXX Service method XXXXX at step Invoke WebSe
  • C# 文件与Base64的相互转换

    一 转换工具 1 在线图片转Base64编码 2 BeJson在线JSON校验格式化工具 3 Base64在线加密 解密 二 Base64转文件代码 这个案例是 将已经获取到的Base64字符串 转换成文件 保存到服务器的某个文件路径下面
  • linux下rdkafka编译,Linux下librdkafka编译安装使用学习

    Kafka分为服务端和客户端 服务端集群一般称为brokers 客户端分为生产者 producer 和消费者 consumer 开发者通常用客户端从kafka生产消息或消费消息 不同的语言使用不同的客户端 具体信息参见这个页面 https
  • nginx配置非80端口

    user nobody worker processes 2 events worker connections 1024 http include mime types default type application octet str
  • leetcode刷题python之有效的括号

    class Solution def isValid self s str gt bool dict stack for i in s if i in dict top stack pop if stack else if dict i t
  • 微信小程序:消息提示框(wx.showToast)和 交互提示框(wx.showModal)

    摘要 有时需要提示框来提醒我们相应信息 以及交互提示框来让我们是否继续进行操作 一 消息提示框 1 参数介绍 消息提示框只有提示作用 不能交互 函数是wx showToast 下面列出几个主要且常用参数 2 代码 wx showToast
  • 如何把IE浏览器快速变成灰色来哀悼在汶川地震中遇难的同胞们!

    如何把IE浏览器快速变成灰色来哀悼在汶川地震中遇难的同胞们 把IE浏览器快速变成灰色只要作以下二点即可 第一点 在html页面中 在body中加入下面语句 第二点 在css控制表中 加入下面语句 html filter progid DXI
  • c++11中四种类型转换

    1 static cast 功能 完成编译器认可的隐式类型转换 格式type1 a type2 b staic cast
  • HiveQL中如何排查数据倾斜问题

    如果某个key下记录数远超其他key 在join或group的时候可能会导致某个reduce任务特别慢 本文分析下join的场景 本例子SQL如下 查询每个appid打开的次数 需要排除掉作弊的imei 说明 表cheat imei 750
  • Basic Level 1016 部分A+B (15分)

    题目 正整数 A A A的 D A D A DA 为1位整数 部分 定义为由 A
  • Unity进阶--声音管理器学习笔记

    文章目录 声音管理器 using System Collections using System Collections Generic using UnityEngine public class AudioManager MyrSing
  • 自定义注解及应用场景

    自定义注解及应用场景 深圳蜗牛学苑 课程目标 理解自定义注解概念 掌握自定义注解语法 自定义注解场景中的使用 熟练掌握自定义注解 AOP的使用方式 一 自定义注解 我们回顾一下方法的重写 会发现有 Override 我们把它称为重写的注解