您的位置:首页 > 教育 > 培训 > 什么叫软件外包公司_泉州网站建设托管_免费b2b网站推广有哪些_日本关键词热搜榜

什么叫软件外包公司_泉州网站建设托管_免费b2b网站推广有哪些_日本关键词热搜榜

2024/10/5 4:03:26 来源:https://blog.csdn.net/qq_25156781/article/details/142706382  浏览:    关键词:什么叫软件外包公司_泉州网站建设托管_免费b2b网站推广有哪些_日本关键词热搜榜
什么叫软件外包公司_泉州网站建设托管_免费b2b网站推广有哪些_日本关键词热搜榜

引言

        曾经有人问我,假如有一个业务或者数据处理逻辑,会根据甲方客户繁杂的业务需求,而动态变化,该怎么处理,具体怎么实现?

        将所有策略strategy都写出来,然后放入一个hashMap,根据不同的业务需求,调用不同的策略就行了。

        再问:那如果我的策略动态多变,再将strategy放入Map的代码,不想写死,不想频繁的修改代码怎么实现?比如我将策略代码写入项目下某个文件夹下面某个文件里,或者直接存入数据库某张表里,策略代码用text类型字段存起来,再调用构建接口,将策略逻辑用类加载器,加载出来,动态的创建为一个个新的strategy对象呢?

        读取文件或者text字段,并解析为java代码,编译后,用类加载的方式加载,并且注册进Map,这个稍微复杂,但是我可以实现,只用在代码中添加策略,但是不用修改注册逻辑代码的方式。

设计思路:

  1. 所有需要被自动添加的策略类 Strategy 都必须加注解

  2. 再做一个扫描注解,属性包含:标明需要被扫描的包路径,@Import 标注一个注册器(实现ImportBeanDefinitionRegistrar),

  3. 定义一个注册器,实现ImportBeanDefinitionRegistrar,该注册器可以根据上述注解找到所有被注解标注的类,然后获取到标注的名称,实例化该类,并注册入指定的缓存。

        将此注解加注在springboot启动类上面就可以在项目启动的时候,按照指定的包路径去扫描指定注解的类,然后利用反射机制,获取到扫描到的(被标注注解的)类的元空间,有了元空间,不管是获取该类上的注解,注解属性,还是该类的属性,方法都是没问题的。那么就可以获取到被扫描到的策略strategy的名字,再利用反射 clazz.newInstance()  创建出实例,放入缓存,就解决了动态注册策略的目的了。

    除了对反射的利用,还需要对spring 容器有一定的理解,才能利用spring容器的environment, resourceLoader ,做出扫描器,使用resourceLoader 扫描该环境下,所有带注解的类;再根据指定的注解,过滤出目标类。

代码实现:

标注注解:@StrategyClass

import io.swagger.v3.oas.annotations.tags.Tag;
import java.lang.annotation.*;/*** @Title: StrategyClass* @Description: 用来标记需要被注册的strategy* @Author: wenrong* @Date: 2024/4/2 10:02* @Version:1.0*/@Inherited
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface StrategyClass {/*** 策略名* 为空时,会尝试读取 {@link Tag#name()} 属性*/String strategyName() default "";}

扫描注解:trategyScan

@DynamicStrategyScan注解标记在springboot启动类上,在springboot启动的时候,就会扫描@StrategyClass 注解的策略类,并开始自动添加到Map 缓存。

import org.springframework.context.annotation.Import;
import java.lang.annotation.*;/*** @Title: DynamicStrategyScan* @Description: 用来扫描有动态策略注解的类,并初始化成相应的配置,注册到JVM 缓存* @Author: wenrong* @Date: 2024/4/2 15:27* @Version:1.0*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Import(DynamicStrategyRegister.class)
@Documented
public @interface DynamicStrategyScan {String[] value() default {};String[] basePackages() default {};
}

注册器抽象类:

因为可能不光策略需要被注册,其他的也可能需要被注册,所以这里这里做一下抽象:

import org.springframework.beans.factory.annotation.AnnotatedBeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.context.EnvironmentAware;
import org.springframework.context.ResourceLoaderAware;
import org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider;
import org.springframework.context.annotation.ImportBeanDefinitionRegistrar;
import org.springframework.core.env.Environment;
import org.springframework.core.io.ResourceLoader;
import org.springframework.core.type.AnnotationMetadata;
import org.springframework.util.ClassUtils;
import org.springframework.util.StringUtils;import java.lang.annotation.Annotation;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;/*** @Title: AnnotationScanAndRegister* @Description:* @Author: wenrong* @Date: 2024/4/2 15:35* @Version:1.0*/
public abstract class AnnotationScanAndRegister implements ImportBeanDefinitionRegistrar, ResourceLoaderAware, EnvironmentAware {/*** 资源加载器*/protected ResourceLoader resourceLoader;/*** 环境*/protected Environment environment;@Overridepublic void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {ImportBeanDefinitionRegistrar.super.registerBeanDefinitions(importingClassMetadata, registry);}/*** author: wenrong* date: 2024/4/2 15:36* 获取base packages*/protected static Set<String> getBasePackages(AnnotationMetadata importingClassMetadata, Class<? extends Annotation> clazz) {// 获取到扫描注解所有属性Map<String, Object> attributes = importingClassMetadata.getAnnotationAttributes(clazz.getCanonicalName());Set<String> basePackages = new HashSet<>();assert attributes != null;// value 属性是否有配置值,如果有则添加for (String pkg : (String[]) attributes.get("value")) {if (StringUtils.hasText(pkg)) {basePackages.add(pkg);}}// basePackages 属性是否有配置值,如果有则添加for (String pkg : (String[]) attributes.get("basePackages")) {if (StringUtils.hasText(pkg)) {basePackages.add(pkg);}}// 如果上面两步都没有获取到basePackages,那么这里就默认使用当前项目启动类所在的包为basePackagesif (basePackages.isEmpty()) {basePackages.add(ClassUtils.getPackageName(importingClassMetadata.getClassName()));}return basePackages;}/*** 获取name*/protected static String getAttribute(Map<String, Object> attributes, String field) {return (String) attributes.get(field);}/*** 创建扫描器* 扫描器是在environment下,遇到带注解的类都扫描,只是后面会根据指定注解,做过滤*/protected ClassPathScanningCandidateComponentProvider getScanner() {return new ClassPathScanningCandidateComponentProvider(false, environment) {@Overrideprotected boolean isCandidateComponent(AnnotatedBeanDefinition beanDefinition) {boolean isCandidate = false;if (beanDefinition.getMetadata().isIndependent()) {if (!beanDefinition.getMetadata().isAnnotation()) {isCandidate = true;}}return isCandidate;}};}@Overridepublic void setEnvironment(Environment environment) {this.environment = environment;}@Overridepublic void setResourceLoader(ResourceLoader resourceLoader) {this.resourceLoader = resourceLoader;}
}

动态策略注册类:

import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.AnnotatedBeanDefinition;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.core.type.AnnotationMetadata;
import org.springframework.core.type.filter.AnnotationTypeFilter;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.*;
import java.util.stream.Collectors;/*** @Title: DynamicStrategyRegister* @Description: 用于系统启动时候,动态策略的初始化注册* @Author: wenrong* @Date: 2024/4/2 15:25* @Version:1.0*/
@Slf4j
public class DynamicStrategyRegister extends AnnotationScanAndRegister {public static final Map<String, Strategy> XXXStrategies = new HashMap<>();@Overridepublic void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {// 创建scannerClassPathScanningCandidateComponentProvider scanner = getScanner();scanner.setResourceLoader(resourceLoader);// 设置扫描器scanner扫描的过滤条件AnnotationTypeFilter annotationTypeFilter = new AnnotationTypeFilter(StrategyClass.class);scanner.addIncludeFilter(annotationTypeFilter);// 获取指定要扫描的basePackagesSet<String> basePackages = getBasePackages(metadata, DynamicStrategyScan.class);// 遍历每一个basePackagesfor (String basePackage : basePackages) {// 通过scanner获取basePackage下的候选类(有标注解的类)Set<BeanDefinition> candidateComponents = scanner.findCandidateComponents(basePackage);// 遍历每一个候选类,如果符合条件就把他们注册到容器for (BeanDefinition candidateComponent : candidateComponents) {if (candidateComponent instanceof AnnotatedBeanDefinition) {// verify annotated class is an interfaceAnnotatedBeanDefinition beanDefinition = (AnnotatedBeanDefinition) candidateComponent;AnnotationMetadata annotationMetadata = beanDefinition.getMetadata();// 获取@StrategyClass注解的属性Map<String, Object> attributes = annotationMetadata.getAnnotationAttributes(StrategyClass.class.getCanonicalName());// 注册配置到缓存registerBean(annotationMetadata, attributes);}}}}/*** 利用factoryBean创建代理对象,并注册到容器*/private void registerBean(AnnotationMetadata annotationMetadata, Map<String, Object> attributes) {// 获取类Class<?> clazz;try {clazz = Class.forName(annotationMetadata.getClassName());} catch (ClassNotFoundException e) {log.error(e.getMessage());return;}// 解析出@StrategyClass注解的nameString strategyName = getAttribute(attributes, "strategyName");Strategy strategy = (Strategy) clazz.newInstance();XXXStrategies.put(strategyName, strategy);}}

        前面说了,除了动态注册策略,我还可以用来动态注册操作日志配置,注册配置后,可以操作配置开关,控制哪些接口开启操作日志:

import io.swagger.v3.oas.annotations.Operation;
import jakarta.annotation.PostConstruct;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.AnnotatedBeanDefinition;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.core.type.AnnotationMetadata;
import org.springframework.core.type.filter.AnnotationTypeFilter;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.*;
import java.util.stream.Collectors;/*** @Title: OperateOrderRegister* @Description: 用于系统启动时候,操作配置的初始化注册* @Author: wenrong* @Date: 2024/4/2 15:25* @Version:1.0*/
@Slf4j
public class OperateLogConfigRegister extends AnnotationScanAndRegister {public static final List<LogConfigDTO> CONFIGS = new ArrayList<>();static final OperateTypeEnum[] OPERATE_TYPES = {OperateTypeEnum.CREATE, OperateTypeEnum.UPDATE, OperateTypeEnum.DELETE, OperateTypeEnum.IMPORT};@Resourceprivate OperateLogConfigApi operateLogConfigApi;public static RequestMethod[] getRequestMethod(Method method) {// 使用 Spring 的工具类,可以处理 @RequestMapping 别名注解RequestMapping requestMapping = AnnotationUtils.getAnnotation(method, RequestMapping.class);if (requestMapping == null) {throw new RuntimeException("方法上没有加请求方式注解");}return requestMapping.method();}@PostConstructprivate void register() {List<LogConfigDTO> configDTOS = CONFIGS.stream().filter(logConfigDTO -> {if (logConfigDTO.getOperateType() != null) {return operateLogConfigApi.validLogConfig(logConfigDTO);}return false;}).collect(Collectors.toList());//注册配置到数据库operateLogConfigApi.createOperateLogConfigs(configDTOS);// 删除已经没有配置日志注解的配置List<LogConfigDTO> allOperateLogConfigs = operateLogConfigApi.getAllOperateLogConfigs();Set<String> orders = CONFIGS.stream().map(LogConfigDTO::getOrderName).collect(Collectors.toSet());List<Long> config2Remove = allOperateLogConfigs.stream().filter(operateConfig ->!orders.contains(operateConfig.getOrderName())).map(LogConfigDTO::getId).collect(Collectors.toList());if (!config2Remove.isEmpty() && !CONFIGS.isEmpty()) {operateLogConfigApi.remove(config2Remove);}}@Overridepublic void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {// 创建scannerClassPathScanningCandidateComponentProvider scanner = getScanner();scanner.setResourceLoader(resourceLoader);// 设置扫描器scanner扫描的过滤条件AnnotationTypeFilter annotationTypeFilter = new AnnotationTypeFilter(OperateLogForClass.class);scanner.addIncludeFilter(annotationTypeFilter);// 获取指定要扫描的basePackagesSet<String> basePackages = getBasePackages(metadata, OperateLogApiScan.class);// 遍历每一个basePackagesfor (String basePackage : basePackages) {// 通过scanner获取basePackage下的候选类(有标注解的类)Set<BeanDefinition> candidateComponents = scanner.findCandidateComponents(basePackage);// 遍历每一个候选类,如果符合条件就把他们注册到容器for (BeanDefinition candidateComponent : candidateComponents) {if (candidateComponent instanceof AnnotatedBeanDefinition) {// verify annotated class is an interfaceAnnotatedBeanDefinition beanDefinition = (AnnotatedBeanDefinition) candidateComponent;AnnotationMetadata annotationMetadata = beanDefinition.getMetadata();// 获取@OperateLogForClass注解的属性Map<String, Object> attributes = annotationMetadata.getAnnotationAttributes(OperateLogForClass.class.getCanonicalName());// 注册配置到缓存registerBean(annotationMetadata, attributes);}}}}/*** 利用factoryBean创建代理对象,并注册到容器*/private void registerBean(AnnotationMetadata annotationMetadata, Map<String, Object> attributes) {// 获取类Class<?> clazz;try {clazz = Class.forName(annotationMetadata.getClassName());} catch (ClassNotFoundException e) {log.error(e.getMessage());return;}// 解析出@OperateLogForClass注解的moduleString module = getAttribute(attributes, "module");// 解析出@OperateLogForClass注解的nameString orderName = getAttribute(attributes, "orderName");Method[] declaredMethods = clazz.getDeclaredMethods();this.mapMethod(declaredMethods, module, orderName);}private void mapMethod(Method[] methods, String module, String orderName) {for (Method method : methods) {try {if (!Modifier.isPublic(method.getModifiers())) {continue;}LogConfigDTO logConfigDTO = new LogConfigDTO();logConfigDTO.setModule(module);logConfigDTO.setOrderName(orderName);logConfigDTO.setConfig(true);String name = method.getAnnotation(Operation.class).summary();logConfigDTO.setName(name);// 设置操作类型// setOperateTypeByMethodName(method, logConfigDTO);setOperateType(method, logConfigDTO);if (logConfigDTO.getOperateType() != null) {OperateLogConfigRegister.CONFIGS.add(logConfigDTO);}} catch (Exception e) {log.error("【操作类型匹配异常:】", e);}}}/*** @description: 设置操作类型, 虽然可以通过方法名匹配,不依赖注解,* 但是要依赖代码编写时候的方法名称的严格格式,如果方法名跳出增删改,* 则需要通过 {@link OperateType#type()}自定义方法操作类型* 适配非controller层的方法* @params: @param* @return: null* @author: wenrong* @time: 2024/4/3 14:11*/private void setOperateTypeByMethodName(Method method, LogConfigDTO logConfigDTO) {List<OperateTypeEnum> operateTypes = Arrays.stream(OPERATE_TYPES).filter(operateType ->method.getName().startsWith(operateType.getName())).collect(Collectors.toList());if (!operateTypes.isEmpty()) {logConfigDTO.setOperateType(operateTypes.get(0).getDescription());} else if (method.getName().startsWith(OperateTypeEnum.GET.getName())|| method.getName().startsWith(OperateTypeEnum.EXPORT.getName())) {logConfigDTO.setOperateType(null);} else if (StringUtils.hasText(method.getAnnotation(OperateType.class).type())) {logConfigDTO.setOperateType(method.getAnnotation(OperateType.class).type());}}/*** @description: 使用 {@link RequestMapping#method()} 匹配操作类型,不容易出错* 但是仅限于controller的 RESTFul风格接口* @params: @param* @return: null* @author: wenrong* @time: 2024/4/3 14:28*/private void setOperateType(Method method, LogConfigDTO logConfigDTO) {cn.iocoder.yudao.framework.operatelog.core.annotations.OperateLog operateLog = method.getAnnotation(cn.iocoder.yudao.framework.operatelog.core.annotations.OperateLog.class);if (operateLog != null && !operateLog.enable()) {return;}RequestMethod requestMethod = RequestMethodFilter.filterRequestMethod(getRequestMethod(method));if (requestMethod == null) {logConfigDTO.setOperateType(null);return;}OperateTypeEnum operateTypeEnum = OperateTypeConvertor.convertOperateType(requestMethod);OperateType operateType = method.getAnnotation(OperateType.class);if (operateTypeEnum == null && operateType == null) {logConfigDTO.setOperateType(null);} else {if (operateTypeEnum == null) {if (StringUtils.hasText(method.getAnnotation(OperateType.class).type())) {logConfigDTO.setOperateType(method.getAnnotation(OperateType.class).type());} else {logConfigDTO.setOperateType(OperateTypeEnum.OTHER.getDescription());}} else {logConfigDTO.setOperateType(operateTypeEnum.getDescription());}}}}

AOP类:

这里需要自己去实现,如果需要源码,后给我发消息,留言。

注意:

如果只做专业人员运维日志,保存接口所属功能模块,菜单,接口名称,uri、客户端IP,传参就可以了,但是如果是给用户看的,则需要定义一个上下文类,里面利用ThreadLocal存入两个字符串变量,修改前,修改后,至于新增,修改前为空,删除,修改后为空。(虽然操作肯定不止修改,有新增和删除,看似修改前和修改后的方式并不适合所有操作类型,但这是将技术实现设计向业务功能实现设计妥协的体现,因为不得不对业务代码有侵入。)

需要注意的是,上述出现过两次AnnotationMetadata  元空间,一次是registerBeanDefinitions(AnnotationMetadata metadata, ...)一次是AnnotationMetadata annotationMetadata = beanDefinition.getMetadata();  这两次虽然都是元空间对象,但是前面的元空间是当前整个容器的元空间,类信息都在里面,包括接口和注解的,所以利用元空间、注解的类名,获取类的属性空间,importingClassMetadata.getAnnotationAttributes(clazz.getCanonicalName());  后者的元空间则是扫描的到类的元空间,可以获取该类上的注解,注解的属性等。

总结:

Java的反射机制允许程序在运行时检查和操作类、方法和字段。通过反射,你可以在运行时获取类的信息(如类名、字段、方法等),并且可以动态地创建对象、调用方法和访问/修改字段。

以下是Java反射机制的一些重要概念和用法:

  • class类: Java中的每个类都有一个与之关联的Class对象,它包含了该类的完整信息。你可以使用以下方式获取Class对象:

Class<?> clazz = MyClass.class; // 通过类名 
Class<?> clazz = obj.getClass(); // 通过对象实例 
Class<?> clazz = Class.forName("com.example.MyClass"); // 通过类的完全限定名
  • 获取类的信息: 一旦有了Class对象,你就可以获取关于类的信息,如类名、字段、方法等。

​​​​​​​String className = clazz.getName(); 
Field[] fields = clazz.getDeclaredFields(); 
Method[] methods = clazz.getDeclaredMethods();
  • 创建对象: 可以通过反射来动态创建对象实例。

MyClass obj = (MyClass) clazz.newInstance();
  • 访问和修改字段: 可以通过反射来获取和修改对象的字段值。

Field field = clazz.getDeclaredField("fieldName"); 
field.setAccessible(true); // 如果字段是私有的,需要设置可访问性 
Object value = field.get(obj); // 获取字段值 
field.set(obj, newValue); // 设置字段值
  • 调用方法: 可以通过反射来调用类的方法。

Method method = clazz.getDeclaredMethod("methodName", parameterTypes); 
method.setAccessible(true); // 如果方法是私有的,需要设置可访问性 
Object result = method.invoke(obj, args); // 调用方法
  • 动态代理: 可以使用反射来创建动态代理对象。

MyInterface proxy = (MyInterface) Proxy
.newProxyInstance( MyInterface.class.getClassLoader(), new Class[] { MyInterface.class }, new MyInvocationHandler());

    虽然反射提供了很大的灵活性,但也需要谨慎使用,因为它会降低代码的可读性和性能,而且可能会在编译时捕获不到的错误。

​​​​​​​

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com