springboot-自定义注解
1.注解的概念
注解是一种能被添加到java代码中的【元数据,类、方法、变量、参数和包】都可以用注解来修饰。用来定义一个类、属性或一些方法,以便程序能被捕译处理。 相当于一个说明文件,告诉应用程序某个被注解的类或属性是什么,要怎么处理。注解对于它所修饰的代码并没有直接的影响。
2.注解的使用范围
1)为编译器提供信息:注解能被编译器检测到错误或抑制警告。
2)编译时和部署时的处理: 软件工具能处理注解信息从而生成代码,XML文件等等。
3)运行时的处理:有些注解在运行时能被检测到。
3.自定义注解的步骤
第一步:定义注解
第二步:配置注解
第三步:解析注解
4.注解的基本语法
4.1最基本的注解定义
package com.example.demo.config;public @interface MyAnnotation {public String name();int age();String sex() default "女";
}
注意:自定义注解要用【@interface】
在自定义注解中,其实现部分只能定义注解类型元素!
说明:
a.访问修饰符必须为public,不写默认为public;
b.该元素的类型只能是基本数据类型、String、Class、枚举类型、注解类型以及一维数组;
c.该元素的名称一般定义为名词,如果注解中只有一个元素,名字起为value最好;
d.()不是定义方法参数的地方,也不能在括号中定义任何参数,仅仅只是一个特殊的语法;
e.default
代表默认值,值必须定义的类型一致;
f.如果没有默认值,代表后续使用注解时必须给该类型元素赋值。
4.2常用的元注解
元注解:专门修饰注解的注解。
@Retention:定义注解的保留策略
@Retention(RetentionPolicy.SOURCE) //注解仅存在于源码中,在class字节码文件中不包含
@Retention(RetentionPolicy.CLASS) //默认的保留策略,注解会在class字节码文件中存在,但运行时无法获得,
@Retention(RetentionPolicy.RUNTIME) //注解会在class字节码文件中存在,在运行时可以通过反射获取到@Target:指定被修饰的Annotation可以放置的位置(被修饰的目标)
@Target(ElementType.TYPE) //接口、类
@Target(ElementType.FIELD) //属性
@Target(ElementType.METHOD) //方法
@Target(ElementType.PARAMETER) //方法参数
@Target(ElementType.CONSTRUCTOR) //构造函数
@Target(ElementType.LOCAL_VARIABLE) //局部变量
@Target(ElementType.ANNOTATION_TYPE) //注解
@Target(ElementType.PACKAGE) //包
注:可以指定多个位置,例如:
@Target({ElementType.METHOD, ElementType.TYPE}),也就是此注解可以在方法和类上面使用@Inherited:指定被修饰的Annotation将具有继承性
@Documented:指定被修饰的该Annotation可以被javadoc工具提取成文档.
## 2.读入数据
4.2.1@Target
@Target是专门用来限定某个自定义注解能够被应用在哪些Java元素上面的。
因此,我们可以在使用@Target时指定注解的使用范围,示例如下:
//@MyAnnotation被限定只能使用在类、接口或方法上面
@Target(value = {ElementType.METHOD,ElementType.TYPE})
public @interface MyAnnotation {public String name();int age();String sex() default "女";
}
4.2.2@Retention
@Retention注解,用来修饰自定义注解的生命力。
a.如果一个注解被定义为RetentionPolicy.SOURCE,则它将被限定在Java源文件中,那么这个注解即不会参与编译也不会在运行期起任何作用,这个注解就和一个注释是一样的效果,只能被阅读Java文件的人看到;
b.如果一个注解被定义为RetentionPolicy.CLASS,则它将被编译到Class文件中,那么编译器可以在编译时根据注解做一些处理动作,但是运行时JVM(Java虚拟机)会忽略它,我们在运行期也不能读取到,是默认的;
c.如果一个注解被定义为RetentionPolicy.RUNTIME,那么这个注解可以在运行期的加载阶段被加载到Class对象中。那么在程序运行阶段,我们可以通过反射得到这个注解,并通过判断是否有这个注解或这个注解中属性的值,从而执行不同的程序代码段。
注意:我们实际开发中的自定义注解几乎都是使用的RetentionPolicy.RUNTIME。
使此注解修饰自定义注解生命力的示例如下:
//设置注解的生命力在运行期
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnnotation {public String name();int age();String sex() default "女";
}
4.2.3@Documented
@Documented注解,是被用来指定自定义注解是否能随着被定义的java文件生成到JavaDoc文档当中。源码如下:
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Documented {
}
4.2.4@Inherited
@Inherited注解,是指定某个自定义注解如果写在了父类的声明部分,那么子类(继承关系)的声明部分也能自动拥有该注解。该注解只对@Target被定义为ElementType.TYPE的自定义注解起作用。下面对其进行详细说明:
(1)该注解作用于整个程序运行中(@Retention(RetentionPolicy.RUNTIME);
(2)该注解只能修饰注解(@Target({ElementType.ANNOTATION_TYPE})),它是一个元注解。
此注解的中文翻译是继承的意思,那么究竟是什么意思呢?通过示例进行演示
第一:先定义两个注解@HasInherited 和 @NoInherited,前者注解包含@Inherited 注解,后者反之
第二:再新建两个类,让其具有继承关系(Father类和Child类),类上暂时不添加任何注解
第三:给父类添加注解@HasInherited,子类不加,然后来获取两个类的注解信息
通过测试类获取注解信息
@Testpublic void test() {// 打印父类注解信息Annotation[] fatherAnnotations = Father.class.getAnnotations();log.info("------- 父类 Father 信息 --------");log.info("父类注解个数:" + fatherAnnotations.length);for (Annotation fa : fatherAnnotations) {log.info(fa.annotationType().getSimpleName());}// 打印子类注解信息Annotation[] childAnnotations = Child.class.getAnnotations();log.info("------- 子类 Child 信息 --------");log.info("子类注解个数:" + childAnnotations.length);for (Annotation ca : childAnnotations) {log.info(ca.annotationType().getSimpleName());}}
打印信息如下
第四:给父类添加注解@NoInherited,子类不加,然后来获取两个类的注解信息
运行测试方法后打印信息如下
也就是说,被 @Inherited 注解修饰的注解,如果作用于某个类上,其子类是可以继承的该注解的。否则,若一个注解没有被 @Inherited注解所修饰,那么其作用范围只能是当前类,其子类是不能被继承的。
熟悉@SpringBootApplication注解的都知道,对于SpringBoot的启动类上的@SpringBootApplication注解也使用了@Inherited 注解,那么按道理也遵循上述的验证结果,不如就来验证一下吧
自定义一个类继承项目的SpringBoot的启动类,并修改启动的main方法,看是否可以启动(可以把项目的SpringBoot的启动类的main方法注释)
可以看到使用子类也能启动成功,猜想得到验证。
5.自定义注解举例
第一步:自定义的注解如下
package com.example.demo.config;import java.lang.annotation.*;@Target(value={ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface MyAnnotation {public String name();int age();String sex() default "女";String[] hobby();
}
第二步:创建一个类,新建方法使用该注解
package com.example.demo.controller;import com.example.demo.config.MyAnnotation;public class UserController {@MyAnnotation(name = "张三",age = 18,hobby = {"跑步,打游戏"})public String get(){return "Hello Annotation";}
}
第三步:利用反射获取注解。创建一个类,代码如下:
package com.example.demo.test;import com.example.demo.config.MyAnnotation;import java.lang.annotation.Annotation;
import java.lang.reflect.Method;public class Test {public static void main(String[] args) {try {//获取Class对象Class mylass = Class.forName("com.example.demo.controller.UserController");//获得该对象身上配置的所有的注解Annotation[] annotations = mylass.getAnnotations();System.out.println(annotations.toString());//获取里面的一个方法Method method = mylass.getMethod("get");//判断该元素上是否配置有某个指定的注解if(method.isAnnotationPresent(MyAnnotation.class)){System.out.println("UserController类的get方法上配置了MyAnnotation注解!");//获取该元素上指定类型的注解MyAnnotation myAnnotation = method.getAnnotation(MyAnnotation.class);System.out.println("name: " + myAnnotation.name() + ", age: " + myAnnotation.age()+ ",sex:"+myAnnotation.sex()+", hobby: " + myAnnotation.hobby()[0]);}else{System.out.println("UserController类的get方法上没有配置MyAnnotation注解!");}} catch (Exception e) {e.printStackTrace();}}
}
打印结果如下:
如果要获得的注解是配置在方法上的,从Method对象上获取;如果是配置在属性上,就需要从该属性对应的Field对象上去获取,如果是配置在类型上,需要从Class对象上去获取。
6.注解的特殊语法
特殊的语法是基于5的,这里就直接讲述特殊的定义和使用。
1)如果注解没有注解类型元素,那么在使用注解时可省略(),直接写为:@注解名。
定义如下:
@Target(value={ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface MyAnnotation {
}
使用如下:
public class UserController {@MyAnnotationpublic String get(){return "Hello Annotation";}
}
2)如果注解只有一个注解类型元素,且命名为value,那么在使用注解时可直接写为:@注解名(注解值)。
定义如下:
@Target(value={ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface MyAnnotation {String value();
}
使用如下:
public class UserController {@MyAnnotation("hello")public String get(){return "Hello Annotation";}
}
3)如果注解中的某个注解类型元素是一个数组类型,在使用时又出现只需要填入一个值的情况,那么在使用注解时可直接写为:@注解名(类型名 = 类型值),和标准的@注解名(类型名 = {类型值})等效!
定义如下:
@Target(value={ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface MyAnnotation {String[] arr();
}
使用如下:
public class UserController {@MyAnnotation(arr = "hello")public String get(){return "Hello Annotation";}
}
4)如果注解的@Target定义为Element.PACKAGE,那么这个注解是配置在package-info.java中的,而不能直接在某个类的package代码上面配置。
7.在项目中使用自定义的注解
7.1环境搭建
1)新建一个SpringBoot的项目,导入jar座标
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-aop</artifactId></dependency><dependency><groupId>org.mybatis.spring.boot</groupId><artifactId>mybatis-spring-boot-starter</artifactId><version>1.3.2</version></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-jdbc</artifactId></dependency><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><scope>runtime</scope></dependency><dependency><groupId>com.alibaba</groupId><artifactId>druid</artifactId><version>1.1.9</version></dependency><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><version>1.18.8</version></dependency>
2)配置application.yml
#数据源配置
spring:datasource:#使用阿里巴巴的druidtype: com.alibaba.druid.pool.DruidDataSource#配置数据库的路径和用户名密码url: jdbc:mysql://127.0.0.1:3306/annotation?useUnicode=true&characterEncoding=UTF-8&serverTimezone=CTT&zeroDateTimeBehavior=convertToNull&useSSL=false&allowMultiQueries=trueusername: rootpassword: 123456mybatis:mapperLocations: classpath*:mapper/*Mapper.xml#开启日志打印
logging:level:com.zys.training: debug
3)执行sql脚本
create database annotation;
use annotation;
CREATE TABLE `systemlog` (`id` varchar(200) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '日志主键',`title` varchar(200) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '标题',`describe` varchar(200) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '模块描述',`create_time` datetime NULL DEFAULT NULL COMMENT '记录时间',`method` varchar(200) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '调用方法',`error` longtext CHARACTER SET utf8 COLLATE utf8_general_ci NULL COMMENT '错误信息',PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Compact;
7.2创建日志的MVC
1)创建日志类
package com.zys.springboot.annotationdemo.entity;import lombok.Getter;
import lombok.Setter;
import lombok.ToString;import java.util.Date;@Getter
@Setter
@ToString
public class SystemLog {private String id;private String title;private String describe;private Date create_time;private String method;private String error;
}
2)创建service
package com.zys.springboot.annotationdemo.service;import com.zys.springboot.annotationdemo.entity.SystemLog;public interface SystemLogService {int createLog(SystemLog log);
}
3)创建impl
package com.zys.springboot.annotationdemo.service.impl;import com.zys.springboot.annotationdemo.dao.SystemLogDao;
import com.zys.springboot.annotationdemo.entity.SystemLog;
import com.zys.springboot.annotationdemo.service.SystemLogService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;@Service
public class SystemLogServiceImpl implements SystemLogService {@Autowiredprivate SystemLogDao systemLogDao;@Overridepublic int createLog(SystemLog log) {return systemLogDao.createLog(log);}
}
4)创建dao
package com.zys.springboot.annotationdemo.dao;import com.zys.springboot.annotationdemo.entity.SystemLog;
import org.apache.ibatis.annotations.Mapper;@Mapper
public interface SystemLogDao {int createLog(SystemLog log);
}
5)创建mapper
在resources目录下新建mapper目录,然后创建文件SystemLogMapper.xml
<?xml version="1.0" encoding="uTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN""http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.zys.springboot.annotationdemo.dao.SystemLogDao"><!--插入系统日志--><insert id="createLog" parameterType="com.zys.springboot.annotationdemo.entity.SystemLog">insert into systemLog values(#{id},#{title},#{describe},sysdate(),#{method},#{error})</insert></mapper>
7.3自定义注解
1)创建注解
package com.zys.springboot.annotationdemo.config;import java.lang.annotation.*;/*** 自定义日志注解*/@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Log {String title() default "";//模块名称String describe() default "";//描述
}
2)创建aop切面
package com.zys.springboot.annotationdemo.config;import com.zys.springboot.annotationdemo.entity.SystemLog;
import com.zys.springboot.annotationdemo.service.SystemLogService;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;import java.lang.reflect.Method;
import java.util.UUID;@Aspect
@Component("logAspect")
public class LogAspect {@Autowiredprivate SystemLogService logService;private static final Logger log = LoggerFactory.getLogger(LogAspect.class);// 配置织入点@Pointcut("@annotation(com.zys.springboot.annotationdemo.config.Log)")public void logPointCut() {}/*** 前置通知 用于拦截操作,在方法返回后执行** @param joinPoint 切点*/@AfterReturning(pointcut = "logPointCut()")public void doBefore(JoinPoint joinPoint) {handleLog(joinPoint, null);}/*** 拦截异常操作,有异常时执行** @param joinPoint* @param e*/@AfterThrowing(value = "logPointCut()", throwing = "e")public void doAfter(JoinPoint joinPoint, Exception e) {handleLog(joinPoint, e);}private void handleLog(JoinPoint joinPoint, Exception e) {MethodSignature signature = (MethodSignature) joinPoint.getSignature();SystemLog systemLog = new SystemLog();//获取方法名String functionName = signature.getDeclaringTypeName() + "." + signature.getName() + "()";//获取注解对象Log annotation = signature.getMethod().getAnnotation(Log.class);if (annotation != null) {systemLog.setId(UUID.randomUUID().toString().replace("-", ""));systemLog.setMethod(functionName);//获取注解中对方法的描述信息systemLog.setTitle(annotation.title());systemLog.setDescribe(annotation.describe());if (e != null) {String err = e.getMessage();if (err != null && err.length() > 4000) {err = err.substring(0, 4000);}systemLog.setError(err);}}//记录到数据库logService.createLog(systemLog);}/*** 是否存在注解,如果存在就获取*/private static Log getAnnotationLog(JoinPoint joinPoint) throws Exception {Signature signature = joinPoint.getSignature();MethodSignature methodSignature = (MethodSignature) signature;Method method = methodSignature.getMethod();if (method != null) {return method.getAnnotation(Log.class);}return null;}}
7.4创建测试接口
在controller包下创建UserController类,用于测试注解。
package com.zys.springboot.annotationdemo.controller;import com.zys.springboot.annotationdemo.config.Log;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;@RestController
public class UserController {//使用日志注解@Log(title = "用户模块",describe = "获取用户列表")@GetMapping("/get")public String get(){return "Hello word!";}@Log(title = "用户模块",describe = "测试接口")@GetMapping("/test")public String test(){return "Hello Test!";}}
7.5测试
启动项目,访问http://localhost:8080/get,然后查询数据库,发现日志已经记录了,如下图,同理访问http://localhost:8080/test。
回到顶部
8.结合SpEL实现注解动态参数传值
不知道开发的小伙伴有没有遇到这样的情况,在自定义注解后,使用注解时其参数必须是固定不变的。那么能不能实现动态传参呢?答案是肯定的。
下面通过一个案例进行说明:对于资源文档,不同的人有不同的访问权限,比如A用户可以访问文档001,002和003,B用户只能访问001和003,C用户不能访问任何文档。对于给不同用户分配不同的文档权限,这属于系统功能,非常简单就能实现,而难点是权限分配后,后端如何进行权限验证,来保证文档的安全性呢?
这时便可以结合SpEL表达式+注解方式实现。简单来说,就是在查阅文档时,需要传入用户的编号,把用户编号作为权限注解的参数,在切面中去获取用户编号去验证此用户是否有查阅权限,如果有则放行,反之则抛出异常,进行全局异常捕获后指定返回的信息。相比于拦截器,个人更推荐这种方式。具体实现如下:
1)自定义注解
package com.zxh.test.annotation;import java.lang.annotation.*;/*** 资源访问权限注解*/
@Inherited
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface ResourceAccessPermission {/*** 用户编号,可支持SpEL表达式** @return*/String value();
}
2)创建SpEL表达式解析器
package com.zxh.test.config;import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.ToString;
import org.springframework.aop.support.AopUtils;
import org.springframework.context.expression.AnnotatedElementKey;
import org.springframework.context.expression.CachedExpressionEvaluator;
import org.springframework.context.expression.MethodBasedEvaluationContext;
import org.springframework.core.DefaultParameterNameDiscoverer;
import org.springframework.core.ParameterNameDiscoverer;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.Expression;import java.lang.reflect.Method;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;/*** SpEL表达式解析器,根据传入的类型进行解析** @param <T>*/
public class ExpressionEvaluator<T> extends CachedExpressionEvaluator {private final ParameterNameDiscoverer paramNameDiscoverer = new DefaultParameterNameDiscoverer();private final Map<ExpressionKey, Expression> conditionCache = new ConcurrentHashMap<>(64);private final Map<AnnotatedElementKey, Method> targetMethodCache = new ConcurrentHashMap<>(64);/*** 创建表达式上下文** @param object* @param targetClass* @param method* @param args* @return*/public EvaluationContext createEvaluationContext(Object object, Class<?> targetClass, Method method, Object[] args) {Method targetMethod = getTargetMethod(targetClass, method);ExpressionRootObject root = new ExpressionRootObject(object, args);return new MethodBasedEvaluationContext(root, targetMethod, args, this.paramNameDiscoverer);}/*** 获取目标对象的方法** @param targetClass* @param method* @return*/private Method getTargetMethod(Class<?> targetClass, Method method) {AnnotatedElementKey methodKey = new AnnotatedElementKey(method, targetClass);Method targetMethod = this.targetMethodCache.get(methodKey);if (targetMethod == null) {targetMethod = AopUtils.getMostSpecificMethod(method, targetClass);this.targetMethodCache.put(methodKey, targetMethod);}return targetMethod;}/*** 根据条件表达式获SpEL取值** @param conditionExpression* @param elementKey* @param evalContext* @param clazz* @return*/public T getValueByConditionExpression(String conditionExpression, AnnotatedElementKey elementKey, EvaluationContext evalContext, Class<T> clazz) {return getExpression(this.conditionCache, elementKey, conditionExpression).getValue(evalContext, clazz);}
}@Getter
@ToString
@AllArgsConstructor
class ExpressionRootObject {private final Object object;private final Object[] args;
}
3)配置切面,进行权限认证
package com.zxh.test.config;import cn.hutool.core.util.StrUtil;
import com.zxh.test.annotation.ResourceAccessPermission;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.context.expression.AnnotatedElementKey;
import org.springframework.expression.EvaluationContext;
import org.springframework.stereotype.Component;import java.lang.reflect.Method;/*** 资源访问权限切面处理*/
@Component
@Aspect
@Slf4j
public class ResourceAccessPermissionAspect {private ExpressionEvaluator<String> evaluator = new ExpressionEvaluator<>();@Pointcut("@annotation(com.zxh.test.annotation.ResourceAccessPermission)")private void pointCut() {}@Before("pointCut()")public void doPermission(JoinPoint joinPoint) {MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();Method method = methodSignature.getMethod();Object[] args = joinPoint.getArgs();ResourceAccessPermission permission = method.getAnnotation(ResourceAccessPermission.class);if (args == null) {return;}// 读取参数,如果以#开头则按照EL处理,否则按照普通字符串处理String userNo = permission.value();if (StrUtil.startWith(userNo, "#")) {Object pointTarget = joinPoint.getTarget();Class<?> aClass = pointTarget.getClass();// SpEL表达式的方式读取对应参数值EvaluationContext evaluationContext = evaluator.createEvaluationContext(pointTarget, aClass, method, args);AnnotatedElementKey methodKey = new AnnotatedElementKey(method, aClass);//根据条件读取注解中的参数值userNo = evaluator.getValueByConditionExpression(userNo, methodKey, evaluationContext, String.class);}log.info("参数:{}", userNo);// TODO 按照业务自定义逻辑处理/*** 这里为了演示,假设用户编号不等于3时显示无权限,实际需要去查询用户与权限分配表* 不符合要求的就抛出指定的异常,再捕获全局异常返回相应提示信息即可*/if (!userNo.equals("3")) {throw new RuntimeException("无权访问");}}
}
4)在接口中使用注解
/*** 这里将查询条件中的用户编号作为参数传入注解** @param queryBody* @return*/@PostMapping("/read")@ResourceAccessPermission("#queryBody.userNo")public String read(@RequestBody QueryBody queryBody) {return "查询成功";}
在上述示例中,关于通过查询数据库来获取用户的文档权限和异常捕获的步骤并未体现,这些可通过实际的需要进行自定义。通过此示例已经学会了如何给注解动态传递参数,那么就可以举一反三。
相关文章:
springboot-自定义注解
1.注解的概念 注解是一种能被添加到java代码中的【元数据,类、方法、变量、参数和包】都可以用注解来修饰。用来定义一个类、属性或一些方法,以便程序能被捕译处理。 相当于一个说明文件,告诉应用程序某个被注解的类或属性是什么,…...
Pytorch实现之特征损失与残差结构稳定GAN训练,并训练自己的数据集
简介 简介:生成器和鉴别器分别采用了4个新颖设计的残差结构实现,同时在损失中结合了鉴别器层的特征损失来提高模型性能。 论文题目:Image Generation by Residual Block Based Generative Adversarial Networks(基于残留块的生成对抗网络产生图像) 会议:2022 IEEE Int…...
微信小程序模仿快播标签云滚动特效
说到快播,故事肯定就不少。用过的人都知道快播首页有个标签云的特效效果,就是渐隐渐显外加上下滚动,其实还挺好看的。至于其他故事嘛,因为没有酒,所以,还是来说说代码吧~ 一开始不是做这个特效需求…...
XUnity.AutoTranslator-deepseek——调用腾讯的DeepSeek V3 API,实现Unity游戏中日文文本的自动翻译
XUnity.AutoTranslator-deepseek 本项目通过调用腾讯的DeepSeek V3 API,实现Unity游戏中日文文本的自动翻译。 准备工作 1. 获取API密钥 访问腾讯云API控制台申请DeepSeek的API密钥(限时免费)。也可以使用其他平台提供的DeepSeek API。 …...
对比机器学习揭示了跨物种共享与特异性的脑功能结构|文献速递-医学影像人工智能进展
Title 题目 Contrastive machine learning reveals species -shared and -specific brainfunctional architecture 对比机器学习揭示了跨物种共享与特异性的脑功能结构 01 文献速递介绍 猕猴作为人类的动物模型,广泛用于研究大脑和行为的关键方面(G…...
Vue 和 React 响应式的区别
React 和 Vue 在响应式机制上的核心区别主要体现在数据变化侦测方式、更新触发逻辑和设计理念上,具体如下: 一、数据变化侦测方式 Vue 的响应式 原理:通过 Proxy(Vue3)或 Object.defineProperty(Vue2&#…...
MySQL主从架构
MySQL主从架构 MySQL REPLICATION 在实际生产环境中,如果对数据库的读和写都在一个数据库服务器中操作。无论是在安全性、高可用性,还是高并发等各个方面都是完全不能满足实际需求的,因此,一般来说都是通过主从复制(…...
基于ros2与gazebo的导航仿真案例
文章目录 前言操作1、创建docker容器2、安装ROS23、Gazebo安装4、Nav2安装5、测试 前言 导航的入门小案例 参考: Ubuntu24.04 ROS2 Jazzy Gazebo Harmonic安装教程Docs / Gazebo Harmonic 注意选择版本 ROS 2 documentation 操作 1、创建docker容器 sudo docke…...
《Python实战进阶》专栏 No.3:Django 项目结构解析与入门DEMO
《Python实战进阶》专栏 第3集:Django 项目结构解析与入门DEMO 在本集中,我们将深入探讨 Django 的项目结构,并实际配置并运行一个入门DEMO博客网站,帮助你在 Web 开发中更高效地使用 Django。Django 是一个功能强大的 Python Web…...
基于WebGIS技术的校园地图导航系统架构与核心功能设计
本文专为IT技术人员、地理信息系统(GIS)开发者、智慧校园解决方案架构师及相关领域的专业人士撰写。本文提出了一套基于WebGIS技术的校园地图导航系统构建与优化方案,旨在为用户提供高效、智能、个性化的导航体验。如需获取校园地图导航系统技…...
开源且免费的CMS系统有哪几个可以放心用?
既开源又免费的两全其美的CMS不多见,不过总会存在一些个例,给用户们带来更具有建设性的选择,以下是一些开源免费且值得信赖的CMS系统,可以根据你的需求选择合适的平台: 1、WordPress ▷ 特点:全球最流行的…...
逻辑架构与软件架构在PREEvision中的设计关系
1 Introduction 在如今汽车电子系统的开发过程中,系统架构设计是至关重要的环节。无论是汽车控制系统、信息娱乐系统,还是电动驱动系统,架构设计都决定了整个系统的功能、性能以及后期的可维护性和可扩展性。 在往期文章中,我们…...
DeepSeek vs ChatGPT:AI 领域的华山论剑,谁主沉浮?
一、引言 在当今科技飞速发展的时代,人工智能(AI)已然成为推动各领域变革的核心力量。而在人工智能的众多分支中,自然语言处理(NLP)因其与人类日常交流和信息处理的紧密联系,成为了最受瞩目的领…...
现场可以通过手机或者pad实时拍照上传到大屏幕的照片墙现场大屏电子照片墙功能
现场可以通过手机或者pad实时拍照上传到大屏幕的照片墙现场大屏电子照片墙功能,每个人都可以通过手机实时拍照上传到大屏幕上,同时还可以发布留言内容,屏幕上会同步滚动播放展示所有人的照片和留言。相比校传统的照片直播功能更加灵活方便,而…...
AF3 _process_single_hit 函数解读
AlphaFold3 中templates模块的_process_single_hit函数处理单个 HHsearch 比对的模板 TemplateHit,并从相应的 mmCIF 文件中提取模板特征,返回包含模板位置信息、比对质量等特征的 SingleHitResult 对象。它是 AlphaFold3 在模板模块中生成模板特征结构输入的重要步骤。 源代…...
go 模块管理
go version 查看版本 go version go1.21.12 windows/amd64 需要保证:go的版本升级为1.11以上,go mod依赖的最底版本 go env 查看go的环境变量 go env 开启go mod # 标识开启go的模块管理 set GO111MODULE=on GO111MODULE有三个值:off, on和auto(默认值)。 GO111M…...
23种设计模式 - 命令模式
模式定义 命令模式(Command Pattern)是一种行为型设计模式,它将请求封装为独立对象,使请求的发送者与接收者解耦。通过将操作抽象为命令对象,支持命令的存储、传递、撤销和重做,增强系统的灵活性和可扩展性…...
php-fpm
摘要 php-fpm(fastcgi process manager)是PHP 的FastCGI管理器,管理PHP的FastCGI进程,提升PHP应用的性能和稳定性 php-fpm是一个高性能的php FastCGI管理器,提供了更好的php进程管理方式,可以有效的控制内存和进程,支…...
Visual Studio 2022配置网址参考
代码格式化和清理冗余代码选项的配置: 代码样式选项和代码清理 - Visual Studio (Windows) | Microsoft Learn 调试时传递参数: 调试时传递命令行参数(C) - Visual Studio (Windows) | Microsoft Learn...
【含文档+PPT+源码】基于Django的新闻推荐系统的设计与实现
项目介绍 本课程演示的是一款基于Django的新闻推荐系统的设计与实现,主要针对计算机相关专业的正在做毕设的学生与需要项目实战练习的 Python学习者。 1.包含:项目源码、项目文档、数据库脚本、软件工具等所有资料 2.带你从零开始部署运行本套系统 3.…...
2025年02月21日Github流行趋势
项目名称:source-sdk-2013 项目地址url:https://github.com/ValveSoftware/source-sdk-2013项目语言:C历史star数:7343今日star数:929项目维护者:JoeLudwig, jorgenpt, narendraumate, sortie, alanedwarde…...
后端开发:开启技术世界的新大门
在互联网的广阔天地中,后端开发宛如一座大厦的基石,虽不直接与用户 “面对面” 交流,却默默地支撑着整个互联网产品的稳定运行。它是服务器端编程的核心领域,负责处理数据、执行业务逻辑以及与数据库和其他后端服务进行交互。在当…...
Apache Doris 实现毫秒级查询响应
1. 引言 1.1 数据分析的重要性 随着大数据时代的到来,企业对实时数据分析的需求日益增长。快速、准确地获取数据洞察成为企业在竞争中脱颖而出的关键。传统的数据库系统在处理大规模数据时往往面临性能瓶颈,难以满足实时分析的需求。例如,一个电商公司需要实时监控销售数据…...
【Python项目】基于Python的语音数据及标注核对审核系统
【Python项目】基于Python的语音数据及标注核对审核系统 技术简介: 采用Python技术、MySQL数据库、Django框架等实现。 系统简介: 语音数据及标注核对审核系统是一个基于B/S架构的语音数据处理平台,旨在通过自动化的方式对语音数据进行标…...
路由基本配置
学习目标 • 根据拓扑图进行网络布线。 • 清除启动配置并将路由器重新加载为默认状态。 • 在路由器上执行基本配置任务。 • 配置并激活以太网接口。 • 测试并检验配置。 • 思考网络实施方案并整理成文档。 任务 1:网络布线 使用适当的电缆类型连接网络设备。…...
从WebRTC到EasyRTC:嵌入式适配的视频通话SDK实现低延迟、高稳定性音视频通信
WebRTC最初是为浏览器之间的实时通信设计的,其资源需求和复杂性可能对嵌入式设备的性能提出较高要求,因此在嵌入式系统中应用时面临一些挑战: 1)资源消耗较高 CPU和内存占用:WebRTC是一个功能强大的实时通信框架&…...
【Blender】二、建模篇--05,阵列修改器与晶格形变
阵列修改器是bender里面一个比较常用的修改器,所以我们单独开口来讲,我们会先从几片树叶出发,然后我们用阵列修改器把这几片树叶变成这样的造型和这样的造型。这两个造型分别就代表着阵列修改器最常用的两种偏移方法,我们现在就开始我们先来做几个树叶。 1.树叶建模 首先…...
Python爬虫实战:获取12306特定日期、城市车票信息,并做数据分析以供出行参考
注意:以下内容仅供技术研究,请遵守目标网站的robots.txt规定,控制请求频率避免对目标服务器造成过大压力! 1. 核心思路 需求:获取明天(2025 年 2 月 21 日)从北京到上海的车次、票价、出发时间、硬卧二等卧信息,并保存到 CSV 文件,然后分析出价格最低的 10 趟车次。目…...
C++ 设计模式-策略模式
支付策略 #include <iostream> #include <memory> #include <unordered_map> #include <vector> #include <ctime>// 基础策略接口 class PaymentStrategy { public:virtual ~PaymentStrategy() default;virtual std::string name() const 0;…...
数据结构:哈希表(unordered_map)
unordered_map 是 C 标准库中的一种哈希表实现,它提供了基于键值对(key-value)的存储,提供了常数时间复杂度的查找、插入和删除键值对的操作。 初始化代码示例: #include <unordered_map> using namespace std…...
鸿蒙-自定义布局-实现一个可限制行数的-Flex
文章目录 前提onMeasureSizeselfLayoutInfoconstraintchildren onPlaceChildren 实现思路属性准备测量组件布局小结 刷新 千呼万唤始出来的自定义布局功能终于可以用了,这就给了我们更多自由发挥创造的空间,不再局限于使用已有组件做组合。当然ÿ…...
安装可视化jar包部署平台JarManage
一、下载 下载地址:JarManage 发行版 - Gitee.com 🚒 下载 最新发行版 下载zip的里面linux和windows版本都有 二、运行 上传到服务器,解压进入目录 🚚 执行java -jar jarmanage-depoly.jar 命令运行 java -jar jarmanage-dep…...
1、Window Android 13模拟器 将编译的映像文件导入Android Studio
1、环境准备 编译环境:Ubuntu-18.04.5编译版本:android13-release下载地址:清华大学开源软件镜像站AOSP # 下载repo # 同步代码:repo init -u https://mirrors.tuna.tsinghua.edu.cn/git/AOSP/platform/manifest -b android13-r…...
力扣27. 移除元素(快慢指针)
Problem: 27. 移除元素 文章目录 题目描述思路Code 题目描述 思路 定义快慢指针均指向数组起始位置,当fast指针指向的元素不等于val时将fast指针指向的元素赋值给slow并让slow指针向前移动,fast指针一直向前移动 时间复杂度: O ( n ) O(n) O(n); 空间复杂…...
Unity学习part4
1、ui界面的基础使用 ui可以在2d和矩形工具界面下操作,更方便,画布与游戏窗口的比例一般默认相同 如图所示,图片在画布上显示的位置和在游戏窗口上显示的位置是相同的 渲染模式:屏幕空间--覆盖,指画布覆盖在游戏物体渲…...
前端面试之Flex布局:核心机制与高频考点全解析
目录 引言:弹性布局的降维打击 一、Flex布局的本质认知 1. 两大核心维度 2. 容器与项目的权力边界 二、容器属性深度剖析 1. 主轴控制三剑客 2. 交叉轴对齐黑科技 三、项目属性关键要点 1. flex复合属性解密 2. 项目排序魔法 四、六大高频面试场景 1. 经…...
关系数据理论
一、函数依赖 若t1(X)t2(X),必有t1(Y)t2(Y),那么我们称属性组X函数确定属性组Y,或者说Y函数依赖于X。记为X->Y,其中X叫决定因素,Y叫依赖因素。 平凡函数依赖与非平凡函数依赖: 二、1-BCNF 评价关系模式“好坏”的理论标准就…...
低代码与开发框架的一些整合[2]
1.分析的项目资源说明 经过近期的的不断分析与运行对比,最终把注意力集中在了以下几个框架: 01.dibootdiboot.diboot: 写的更少, 性能更好 -> 为开发人员打造的低代码开发平台。Mybatis-plus关联查询,关联无SQL,性能高10倍&a…...
网络空间安全(1)web应用程序的发展历程
前言 Web应用程序的发展历程是一部技术创新与社会变革交织的长卷,从简单的文档共享系统到如今复杂、交互式、数据驱动的平台,经历了多个重要阶段。 一、起源与初期发展(1989-1995年) Web的诞生: 1989年,欧洲…...
Android 之 AIDL for HAL
Android AIDL for HAL 的作用与实现 作用: Android AIDL for HAL(Android Interface Definition Language for Hardware Abstraction Layer)旨在统一 HAL 开发接口,替代 HIDL(Hardware Interface Definition Language…...
Python爬虫基础文件操作
文件操作 引言 爬虫爬取的一切内容都是在内存进行的,这样会有什么问题吗?如果一旦短电或着发生意外电脑关机了那么你的工作成果将瞬间消失。所以,我们还缺少数据在本地文件系统进行持久化的能力,简单的来说就是文件读写操作。文…...
OpenGauss MySQL兼容库迁移
OpenGauss 提供了从MySQL到OG的迁移工具,虽然安装在起来及其繁琐,也不怎么好用,不过我现在需要的是,从MySQL到OG的MySQL兼容库,可以理解成从MySQL到MySQL的迁移。 但是很不幸的是,OG的MySQL的兼容模式&…...
SOME/IP--协议英文原文讲解10
前言 SOME/IP协议越来越多的用于汽车电子行业中,关于协议详细完全的中文资料却没有,所以我将结合工作经验并对照英文原版协议做一系列的文章。基本分三大块: 1. SOME/IP协议讲解 2. SOME/IP-SD协议讲解 3. python/C举例调试讲解 4.2.2 Req…...
linux shell 当命令执行出现错误立即退出的方法
在 Linux 脚本中,如果你想在整个脚本执行完毕后检查是否有错误发生,可以通过以下几种方式实现: 1. 使用 $? 检查上一条命令的退出状态 每个命令执行后,Shell 会将其退出状态存储在特殊变量 $? 中。$? 的值为 0 表示成功&#…...
Moonshot AI 新突破:MoBA 为大语言模型长文本处理提效论文速读
前言 在自然语言处理领域,随着大语言模型(LLMs)不断拓展其阅读、理解和生成文本的能力,如何高效处理长文本成为一项关键挑战。近日,Moonshot AI Research 联合清华大学、浙江大学的研究人员提出了一种创新方法 —— 混…...
vue2 和 vue3 中 computer 计算属性的用法
Vue 2 中的 computed 在 Vue 2 中,计算属性是响应式的,并且基于 getter 进行缓存,只有依赖的响应式数据发生变化时才会重新计算。 基本用法 <template><div><p>原始消息:{{ message }}</p><p>反…...
Python爬虫入门到精通:从零开始的数据采集之旅
一、网络世界的"小蜘蛛":什么是爬虫? 想象一下,你是一只勤劳的小蜘蛛,每天在互联网这张巨大的网上爬来爬去。你不需要自己织网,只需要顺着别人织好的网络路径,把有价值的信息收集到自己的小篮子里。这就是爬虫最形象的比喻——一个自动化的信息采集程序。 Py…...
Python+Selenium+Pytest+POM自动化测试框架封装
🍅 点击文末小卡片 ,免费获取软件测试全套资料,资料在手,涨薪更快 1、测试框架简介 1)测试框架的优点 代码复用率高,如果不使用框架的话,代码会显得很冗余。可以组装日志、报告、邮件等一些高…...
【JMeter使用-2】JMeter中Java Request采样器的使用指南
Apache JMeter 是一款功能强大的性能测试工具,支持多种协议和测试场景。除了内置的采样器(如HTTP请求、FTP请求等),JMeter还允许通过 Java Request采样器 调用自定义的Java代码,从而实现更复杂的测试逻辑。本文将详细介…...
IntelliJ IDEA中Maven配置全指南
一、环境准备与基础配置 1.1 Windows 环境下载并配置 Maven 见此篇博文:环境配置 1.2 IDEA配置步骤 打开设置面板:File → Settings → Build → Build Tools → Maven 关键配置项: Maven home path E:\apache-maven-3.9.9 (…...