springboot注解Aspect的实现方案是什么
发表于:2025-01-19 作者:千家信息网编辑
千家信息网最后更新 2025年01月19日,这篇文章跟大家分析一下"springboot注解Aspect的实现方案是什么"。内容详细易懂,对"springboot注解Aspect的实现方案是什么"感兴趣的朋友可以跟着小编的思路慢慢深入来阅读一下
千家信息网最后更新 2025年01月19日springboot注解Aspect的实现方案是什么
这篇文章跟大家分析一下"springboot注解Aspect的实现方案是什么"。内容详细易懂,对"springboot注解Aspect的实现方案是什么"感兴趣的朋友可以跟着小编的思路慢慢深入来阅读一下,希望阅读后能够对大家有所帮助。下面跟着小编一起深入学习"springboot注解Aspect的实现方案是什么"的知识吧。
目标
下面提供一种自定义注解,来实现业务审批操作的DEMO,不包含审批流程的配置功能。
具体方案是
自定义一个Aspect注解,拦截sevice方法,将拦截的信息持久化,待审批;审批时获取持久化数据,执行目标方法。
实现
POM
4.0.0 org.springframework.boot spring-boot-starter-parent 2.5.8 com.proc process-test 1.0.0-SNAPSHOT process-test Demo project for Spring Boot 1.8 org.springframework.boot spring-boot-starter-web org.springframework.boot spring-boot-starter-test test org.springframework.boot spring-boot-starter-aop org.springframework.boot spring-boot-configuration-processor true com.alibaba transmittable-thread-local 2.12.2 org.springframework.boot spring-boot-maven-plugin
一些实体类
CheckedParam
用于包装页面传进来的参数
package com.proc.model;import java.util.List;public class CheckedParam { //业务标记,由页面传入,用于审批时页面根据tagPageJs解析data,渲染到页面,审批管理员可看到审批的内容 private String tagPageJs; //页面传入的原始数据 private Listdata; public String getTagPageJs() { return tagPageJs; } public void setTagPageJs(String tagPageJs) { this.tagPageJs = tagPageJs; } public List getData() { return data; } public void setData(List data) { this.data = data; } }
ProcessDbModel
拦截的信息包装类,用于持久化数据
package com.proc.model;public class ProcessDbModel { //bean的目标类全限定名 private String targetClassName; //拦截到的service方法名 private String methodName; //页面传入的tagPageJs或Checked注解的tag private String tag; private String description; //拦截到的service入参类型,包含泛型信息 private String paramTypes; //拦截到的service入参值 private String paramArgs; //拦截到的service入参值或页面传入的原始数据 private String data; public String getTargetClassName() { return targetClassName; } public void setTargetClassName(String targetClassName) { this.targetClassName = targetClassName; } public String getMethodName() { return methodName; } public void setMethodName(String methodName) { this.methodName = methodName; } public String getTag() { return tag; } public String getDescription() { return description; } public void setDescription(String description) { this.description = description; } public void setTag(String tag) { this.tag = tag; } public String getParamTypes() { return paramTypes; } public void setParamTypes(String paramTypes) { this.paramTypes = paramTypes; } public String getParamArgs() { return paramArgs; } public void setParamArgs(String paramArgs) { this.paramArgs = paramArgs; } public String getData() { return data; } public void setData(String data) { this.data = data; } @Override public String toString() { return "ProcessDbModel [targetClassName=" + targetClassName + ", methodName=" + methodName + ", tag=" + tag + ", description=" + description + ", paramTypes=" + paramTypes + ", paramArgs=" + paramArgs + ", data=" + data + "]"; } }
测试用的入参对象
package com.proc.model;import java.math.BigDecimal;public class Score { private BigDecimal langue; private BigDecimal math; private BigDecimal english; public BigDecimal getLangue() { return langue; } public void setLangue(BigDecimal langue) { this.langue = langue; } public BigDecimal getMath() { return math; } public void setMath(BigDecimal math) { this.math = math; } public BigDecimal getEnglish() { return english; } public void setEnglish(BigDecimal english) { this.english = english; } @Override public String toString() { return "Score [langue=" + langue + ", math=" + math + ", english=" + english + "]"; }}
package com.proc.model;import java.util.List;public class Person{ private String name; private String age; private String sex; private String testName; private String salary; private String work; private List grades; public String getName() { return name; } public void setName(String name) { this.name = name; } public String getAge() { return age; } public void setAge(String age) { this.age = age; } public String getSex() { return sex; } public void setSex(String sex) { this.sex = sex; } public String getSalary() { return salary; } public void setSalary(String salary) { this.salary = salary; } public String getTestName() { return testName; } public void setTestName(String testName) { this.testName = testName; } public String getWork() { return work; } public void setWork(String work) { this.work = work; } public List getGrades() { return grades; } public void setGrades(List grades) { this.grades = grades; } @Override public String toString() { return "Person [name=" + name + ", age=" + age + ", sex=" + sex + ", testName=" + testName + ", salary=" + salary + ", work=" + work + ", grades=" + grades + "]"; } }
一些工具类
JacksonCanonicalUtil
package com.proc.util;import com.fasterxml.jackson.core.type.TypeReference;import com.fasterxml.jackson.databind.JavaType;import com.fasterxml.jackson.databind.json.JsonMapper;public class JacksonCanonicalUtil { private static final JsonMapper MAPPER = new JsonMapper(); private JacksonCanonicalUtil () {} public staticString toCanonical (Class clazz) { return MAPPER.getTypeFactory().constructType(clazz).toCanonical(); } public static String toCanonical (TypeReference tr) { return MAPPER.getTypeFactory().constructType(tr).toCanonical(); } //反序列化时从持久数据中获取JavaType public static JavaType constructFromCanonical (String canonical) { return MAPPER.getTypeFactory().constructFromCanonical(canonical); }}
StringZipUtil
用于压缩和解压字符串,减少持久数据占用空间
package com.proc.util;import java.io.ByteArrayOutputStream;import java.io.OutputStream;import java.nio.charset.StandardCharsets;import java.util.Base64;import java.util.zip.DeflaterOutputStream;import java.util.zip.InflaterOutputStream;public class StringZipUtil { private StringZipUtil () {} public static String zipBase64(String text) { try (ByteArrayOutputStream out = new ByteArrayOutputStream()) { try (OutputStream os = new DeflaterOutputStream(out)) { os.write(text.getBytes(StandardCharsets.UTF_8)); } return Base64.getEncoder().encodeToString(out.toByteArray()); } catch (Exception e) { throw new RuntimeException("压缩字符串出错", e); } } public static String unzipBase64(String text) { try (ByteArrayOutputStream out = new ByteArrayOutputStream()) { try (OutputStream os = new InflaterOutputStream(out)) { os.write(Base64.getDecoder().decode(text)); } return new String(out.toByteArray(), StandardCharsets.UTF_8); } catch (Exception e) { throw new RuntimeException("解压字符串出错", e); } }}
Base64Util
一些参数值转为Base64后持久化
package com.proc.util;import java.nio.charset.StandardCharsets;import java.util.ArrayList;import java.util.Base64;import java.util.List;import java.util.stream.Collectors;import com.fasterxml.jackson.databind.json.JsonMapper;public class Base64Util { private Base64Util () {} private static final JsonMapper MAPPER = new JsonMapper(); public static String[] toStrings (Object[] objs) { Listlist = new ArrayList<>(); try { for (Object obj : objs) { list.add(MAPPER.writeValueAsString(obj)); } } catch (Exception e) { throw new RuntimeException("序列化对象出错", e); } return list.toArray(new String[0]); } public static String encode (String[] strs) { List list = new ArrayList<>(); for (String str : strs) { list.add(Base64.getEncoder().encodeToString(str.getBytes(StandardCharsets.UTF_8))); } String join = list.stream().collect(Collectors.joining("|")); return join; } public static String[] decode (String text) { String[] strs = text.split("\\|", -1); List list = new ArrayList<>(); for (String base64 : strs) { list.add(new String(Base64.getDecoder().decode(base64), StandardCharsets.UTF_8)); } return list.toArray(new String[0]); } public static String encodeZip (Object[] objs) { return encodeZip(toStrings(objs)); } public static String encodeZip (String[] strs) { List list = new ArrayList<>(); for (String str : strs) { list.add(StringZipUtil.zipBase64(str)); } String join = list.stream().collect(Collectors.joining("|")); return StringZipUtil.zipBase64(join); } public static String[] decodeZip (String text) { String str = StringZipUtil.unzipBase64(text); String[] strs = str.split("\\|", -1); List list = new ArrayList<>(); for (String base64 : strs) { list.add(StringZipUtil.unzipBase64(base64)); } return list.toArray(new String[0]); }}
SpringBootBeanUtil
package com.proc.util;import java.util.Map;import org.springframework.beans.BeansException;import org.springframework.context.ApplicationContext;import org.springframework.context.ApplicationContextAware;import org.springframework.stereotype.Component;@Componentpublic class SpringBootBeanUtil implements ApplicationContextAware { private static ApplicationContext applicationContext; @Override public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { SpringBootBeanUtil.applicationContext = applicationContext; } public static ApplicationContext getApplicationContext() { return applicationContext; } public static Object getBean(String name) { return applicationContext.getBean(name); } public staticT getBean(Class clazz) { return (T) applicationContext.getBean(clazz); } public static T getBean(String name, Class clazz) { return applicationContext.getBean(name, clazz); } public static Map getBeansOfType(Class clazz) { return applicationContext.getBeansOfType(clazz); } }
ProcessBeanUtil
用于执行目标方法
package com.proc.util;import java.lang.reflect.Method;import org.springframework.util.ReflectionUtils;public class ProcessBeanUtil { private ProcessBeanUtil () {} public static Object excuteBeanMethod (String targetClassName, String methodName, Class>[] parameterTypes, Object[] args) { Class> targetClass; try { targetClass = Class.forName(targetClassName); } catch (ClassNotFoundException e) { throw new RuntimeException("未找到类", e); } return excuteBeanMethod(targetClass, methodName, parameterTypes, args); } public static Object excuteBeanMethod (Class> targetClass, String methodName, Class>[] parameterTypes, Object[] args) { Object bean = SpringBootBeanUtil.getBean(targetClass); Method method = ReflectionUtils.findMethod(targetClass, methodName, parameterTypes); return ReflectionUtils.invokeMethod(method, bean, args); }}
CheckedTransmitableUtil
用于传递业务参数
package com.proc.util;import com.alibaba.ttl.TransmittableThreadLocal;import com.proc.model.CheckedParam;public class CheckedTransmitableUtil { private static final TransmittableThreadLocalthreadLocal = new TransmittableThreadLocal<>(); private CheckedTransmitableUtil () {} public static void set (CheckedParam checkedParam) { threadLocal.set(checkedParam); } public static CheckedParam getAndRemove () { CheckedParam checkedParam = threadLocal.get(); threadLocal.remove(); return checkedParam; }}
PrivateTransmitableUtil
为Aspect判断是否拦截提供依据
package com.proc.util;import com.alibaba.ttl.TransmittableThreadLocal;public class PrivateTransmitableUtil { private static final String CHECKED = "__CHECKED__"; private static final TransmittableThreadLocalthreadLocal = new TransmittableThreadLocal<>(); private PrivateTransmitableUtil () {} public static void set () { threadLocal.set(CHECKED); } //是否执行的审批程序 public static boolean isCheck () { String checked = threadLocal.get(); threadLocal.remove(); return CHECKED.equals(checked); }}
一些Bean
PostProcess
用于拦截方法后做的个性处理
package com.proc.bean;public interface PostProcess{ //返回说明内容,审批时在页面显示 String description(String tag, Class>[] parameterTypes, Object[] args); //返回代替的返回值 T retObject(String tag, Class>[] parameterTypes, Object[] args);}
TestCheckPostProcess
测试用
package com.proc.bean;import org.springframework.stereotype.Component;@Componentpublic class TestCheckPostProcess implements PostProcess{ @Override public String description(String tag, Class>[] parameterTypes, Object[] args) { return tag + "测试testCheck"; } @Override public String retObject(String tag, Class>[] parameterTypes, Object[] args) { return tag + "返回拦截响应"; }}
Aspect注解
package com.proc.config;import static java.lang.annotation.ElementType.METHOD;import static java.lang.annotation.RetentionPolicy.RUNTIME;import java.lang.annotation.Retention;import java.lang.annotation.Target;import com.proc.bean.PostProcess;@Retention(RUNTIME)@Target(METHOD)public @interface Checked { String tag() default ""; /** * @see com.proc.util.JacksonCanonicalUtil * @return */ String[] paramCanonical(); Class extends PostProcess>> postProcess();}
切面类 CheckedAop
package com.proc.config;import org.aspectj.lang.ProceedingJoinPoint;import org.aspectj.lang.annotation.Around;import org.aspectj.lang.annotation.Aspect;import org.aspectj.lang.annotation.Pointcut;import org.aspectj.lang.reflect.MethodSignature;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.stereotype.Component;import com.proc.bean.PostProcess;import com.proc.model.CheckedParam;import com.proc.model.ProcessDbModel;import com.proc.service.ProcessDbService;import com.proc.util.Base64Util;import com.proc.util.CheckedTransmitableUtil;import com.proc.util.PrivateTransmitableUtil;import com.proc.util.SpringBootBeanUtil;@Component@Aspectpublic class CheckedAop { @Autowired private ProcessDbService processDbService; //拦截Checked注释的方法 @Pointcut("@annotation(com.proc.config.Checked)") public void check() { } @Around(value = "com.proc.config.CheckedAop.check() && @annotation(checked)") public Object around(ProceedingJoinPoint joinPoint, Checked checked) throws Throwable { MethodSignature signature = (MethodSignature) joinPoint.getSignature(); Class>[] parameterTypes = signature.getParameterTypes(); String methodName = signature.getMethod().getName(); Object[] args = joinPoint.getArgs(); if (PrivateTransmitableUtil.isCheck()) { //审批后,执行业务代码 Object returnVal = joinPoint.proceed(); return returnVal; } else { //不是审批操作,拦截 Class extends PostProcess>> postProcess = checked.postProcess(); PostProcess> bean = SpringBootBeanUtil.getBean(postProcess); //组装持久化数据 ProcessDbModel dbModel = new ProcessDbModel(); dbModel.setTargetClassName(joinPoint.getTarget().getClass().getName()); dbModel.setMethodName(methodName); String tag = checked.tag(); CheckedParam checkedParam = CheckedTransmitableUtil.getAndRemove(); if (checkedParam == null || checkedParam.getTagPageJs() == null || checkedParam.getTagPageJs().isEmpty()) { //不是页面调用的业务,使用注解的tag,data保存为service的参数,这时需要页面专门解析渲染 String[] argStrs = Base64Util.toStrings(args); dbModel.setParamArgs(Base64Util.encodeZip(argStrs)); dbModel.setData(Base64Util.encode(argStrs)); } else { tag = checkedParam.getTagPageJs(); dbModel.setParamArgs(Base64Util.encodeZip(args)); dbModel.setData(Base64Util.encode(checkedParam.getData().toArray(new String[0]))); } dbModel.setTag(tag); dbModel.setParamTypes(Base64Util.encodeZip(checked.paramCanonical())); dbModel.setDescription(bean.description(tag, parameterTypes, args)); //持久化数据 processDbService.save(dbModel); return bean.retObject(tag, parameterTypes, args); } }}
线程池配置
测试用
package com.proc.config;import java.util.concurrent.Executor;import java.util.concurrent.ThreadPoolExecutor;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;import com.alibaba.ttl.threadpool.TtlExecutors;@Configurationpublic class TaskExecutePoolConfig { @Bean public Executor processExecutor() { ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); //核心线程池大小 executor.setCorePoolSize(10); //最大线程数 executor.setMaxPoolSize(10); //队列容量 executor.setQueueCapacity(500); //活跃时间 executor.setKeepAliveSeconds(60); //线程名字前缀 executor.setThreadNamePrefix("ProcessExecutor-"); executor.setRejectedExecutionHandler(new ThreadPoolExecutor.AbortPolicy()); // 等待所有任务结束后再关闭线程池 executor.setWaitForTasksToCompleteOnShutdown(true); executor.initialize(); //用transmittable-thread-local包装,才可以正确给线程池中的线程传递数据 return TtlExecutors.getTtlExecutor(executor); }}
持久化service
为测试方便,未真正实现持久化
package com.proc.service;import com.proc.model.ProcessDbModel;public interface ProcessDbService { void save (ProcessDbModel model); ProcessDbModel get ();}
package com.proc.service.impl;import org.slf4j.Logger;import org.slf4j.LoggerFactory;import org.springframework.stereotype.Component;import com.proc.model.ProcessDbModel;import com.proc.service.ProcessDbService;@Componentpublic class ProcessDbServiceImpl implements ProcessDbService { private static final Logger log = LoggerFactory.getLogger(ProcessDbService.class); private volatile ProcessDbModel model; @Override public void save(ProcessDbModel model) { this.model = model; log.info(model.toString()); } @Override public ProcessDbModel get() { return this.model; }}
审批用的service
package com.proc.service;import com.proc.model.ProcessDbModel;public interface ProcessCheckService { void process (ProcessDbModel model);}
package com.proc.service.impl;import java.util.ArrayList;import java.util.List;import java.util.Objects;import org.slf4j.Logger;import org.slf4j.LoggerFactory;import org.springframework.stereotype.Service;import com.fasterxml.jackson.core.JsonProcessingException;import com.fasterxml.jackson.databind.JavaType;import com.fasterxml.jackson.databind.json.JsonMapper;import com.proc.model.ProcessDbModel;import com.proc.service.ProcessCheckService;import com.proc.util.Base64Util;import com.proc.util.JacksonCanonicalUtil;import com.proc.util.PrivateTransmitableUtil;import com.proc.util.ProcessBeanUtil;@Servicepublic class ProcessCheckServiceImpl implements ProcessCheckService { private static final Logger log = LoggerFactory.getLogger(ProcessCheckServiceImpl.class); private static final JsonMapper MAPPER = new JsonMapper(); @Override public void process(ProcessDbModel model) { PrivateTransmitableUtil.set(); String[] paramArgs = Base64Util.decodeZip(model.getParamArgs()); String[] paramTypes = Base64Util.decodeZip(model.getParamTypes()); List> parameterTypes = new ArrayList<>(); List
测试用的service
package com.proc.service;import com.proc.model.Person;import com.proc.model.Score;public interface TestService { String testCheck(Personperson, String team); String testCheck2(Person person, String team); String testCheckAsync(Person person, String team);}
package com.proc.service.impl;import org.slf4j.Logger;import org.slf4j.LoggerFactory;import org.springframework.scheduling.annotation.Async;import org.springframework.stereotype.Service;import com.proc.bean.TestCheckPostProcess;import com.proc.config.Checked;import com.proc.model.Person;import com.proc.model.Score;import com.proc.service.TestService;@Servicepublic class TestServiceImpl implements TestService { private static final Logger log = LoggerFactory.getLogger(TestServiceImpl.class); //paramCanonical对应testCheck的参数类型 @Checked( paramCanonical = {"com.proc.model.Person", "java.lang.String"}, postProcess = TestCheckPostProcess.class) @Override public String testCheck(Person person, String team) { log.info(team + ">>>>" + person); return "target方法"; } @Checked( tag = "A1", paramCanonical = {"com.proc.model.Person ", "java.lang.String"}, postProcess = TestCheckPostProcess.class) @Override public String testCheck2(Person person, String team) { log.info(team + ">>2>>" + person); return "target2方法"; } @Async("processExecutor") @Checked( paramCanonical = {"com.proc.model.Person ", "java.lang.String"}, postProcess = TestCheckPostProcess.class) @Override public String testCheckAsync(Person person, String team) { log.info(team + ">>>>" + person); return "target方法"; }}
审批用的controller
package com.proc.ctrl;import org.slf4j.Logger;import org.slf4j.LoggerFactory;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.web.bind.annotation.GetMapping;import org.springframework.web.bind.annotation.RestController;import com.proc.model.ProcessDbModel;import com.proc.service.ProcessCheckService;import com.proc.service.ProcessDbService;@RestControllerpublic class ProcessCheckController { private static final Logger log = LoggerFactory.getLogger(ProcessCheckController.class); @Autowired private ProcessDbService processDbService; @Autowired private ProcessCheckService processCheckService; @GetMapping(value = "process") public String process() { ProcessDbModel processDbModel = processDbService.get(); log.info(processDbModel.toString()); processCheckService.process(processDbModel); return "审批成功"; }}
测试用的controller
package com.proc.ctrl;import java.math.BigDecimal;import java.util.ArrayList;import java.util.List;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.web.bind.annotation.GetMapping;import org.springframework.web.bind.annotation.RestController;import com.proc.model.CheckedParam;import com.proc.model.Person;import com.proc.model.Score;import com.proc.service.TestService;import com.proc.util.CheckedTransmitableUtil;@RestControllerpublic class TestController { @Autowired private TestService testService; //模拟页面调用 @GetMapping(value = "index") public String testCheck() { CheckedParam checkedParam = new CheckedParam(); checkedParam.setTagPageJs("01"); Listdata = new ArrayList<>(); data.add("前端传进来的数据1"); data.add("前端传进来的数据2"); checkedParam.setData(data); CheckedTransmitableUtil.set(checkedParam); Person person = new Person<>(); person.setName("一个人"); person.setAge("18"); person.setSex("1"); person.setSalary("20000.00"); person.setTestName("测试人"); person.setWork("工作"); Score score1 = new Score(); score1.setEnglish(new BigDecimal("12.4")); score1.setLangue(new BigDecimal("764")); score1.setMath(new BigDecimal("87.4")); Score score2 = new Score(); score2.setEnglish(new BigDecimal("12.4")); score2.setLangue(new BigDecimal("764")); score2.setMath(new BigDecimal("87.4")); List list = new ArrayList<>(); list.add(score1); list.add(score2); person.setGrades(list); testService.testCheck(person, "team>>>>>>>>"); return "12345"; } //模拟其他渠道调用 @GetMapping(value = "index2") public String testCheck2() { Person person = new Person<>(); person.setName("一个人"); person.setAge("18"); person.setSex("1"); person.setSalary("20000.00"); person.setTestName("测试人"); person.setWork("工作"); Score score1 = new Score(); score1.setEnglish(new BigDecimal("12.4")); score1.setLangue(new BigDecimal("764")); score1.setMath(new BigDecimal("87.4")); Score score2 = new Score(); score2.setEnglish(new BigDecimal("12.4")); score2.setLangue(new BigDecimal("764")); score2.setMath(new BigDecimal("87.4")); List list = new ArrayList<>(); list.add(score1); list.add(score2); person.setGrades(list); testService.testCheck2(person, "team>>>2>>>>>"); return "12345"; } //模拟调用异步方法 @GetMapping(value = "index3") public String testCheckAsync() { CheckedParam checkedParam = new CheckedParam(); checkedParam.setTagPageJs("01"); List data = new ArrayList<>(); data.add("前端传进来的数据1"); data.add("前端传进来的数据2"); checkedParam.setData(data); CheckedTransmitableUtil.set(checkedParam); Person person = new Person<>(); person.setName("一个人"); person.setAge("18"); person.setSex("1"); person.setSalary("20000.00"); person.setTestName("测试人"); person.setWork("工作"); Score score1 = new Score(); score1.setEnglish(new BigDecimal("12.4")); score1.setLangue(new BigDecimal("764")); score1.setMath(new BigDecimal("87.4")); Score score2 = new Score(); score2.setEnglish(new BigDecimal("12.4")); score2.setLangue(new BigDecimal("764")); score2.setMath(new BigDecimal("87.4")); List list = new ArrayList<>(); list.add(score1); list.add(score2); person.setGrades(list); testService.testCheckAsync(person, "team>>>3>>>>>"); return "12345"; }}
开启异步功能
package com.proc;import org.springframework.boot.SpringApplication;import org.springframework.boot.autoconfigure.SpringBootApplication;import org.springframework.scheduling.annotation.EnableAsync;@SpringBootApplication@EnableAsyncpublic class ProcessTestApplication { public static void main(String[] args) { SpringApplication.run(ProcessTestApplication.class, args); }}
关于springboot注解Aspect的实现方案是什么就分享到这里啦,希望上述内容能够让大家有所提升。如果想要学习更多知识,请大家多多留意小编的更新。谢谢大家关注一下网站!
数据
页面
方法
测试
注解
线程
方案
业务
参数
内容
前端
目标
个人
信息
字符
字符串
对象
序列
包装
工作
数据库的安全要保护哪些东西
数据库安全各自的含义是什么
生产安全数据库录入
数据库的安全性及管理
数据库安全策略包含哪些
海淀数据库安全审计系统
建立农村房屋安全信息数据库
易用的数据库客户端支持安全管理
连接数据库失败ssl安全错误
数据库的锁怎样保障安全
网络安全小组组长是谁视频
网络安全即是国家安全作文
软件开发和网络安全的区别
广东应用软件开发价钱
邹平包装管理软件开发
深圳软件开发网站备案
数据库产品核心技术
超凡先锋手游如何登录服务器
网络技术内容
恩施住宿软件开发
weiphp 数据库
戴尔服务器关机重启报错
湖北各专业录取数据库
科技公司的服务器都在哪里
数据库查询方法
虚拟网络技术有哪些
网络安全攻防演练一般几天啊
十四五规划网络安全
软件开发常见模式有哪些
在r中链接数据库
网络安全与全球网络监听
网络技术部部门职责
杭州服务器特价
网络技术现在及以后应用
2017网络安全趋势
软件开发需要哪些能力
主流数据库连接池
哪些企业单位需要服务器
数据库中视图和函数的区别
pk10网络安全