|
|
本帖最后由 598949112 于 2024-10-19 10:43 编辑
最近no-ip的域名每月经常忘记免费续期,已经彻底报废,而且现在很多网站都封杀no-ip的域名,让人苦不堪言,国内域名厂商全都要备案,我家里的服务器,上哪备案去。
于是去歪果域名注册商买了个域名,准备在这台玩客云3 PRO上挂个DDNS服务,检测外网IP自动更新解析。
JAVA21如果使用springboot3,是很容易进行native arrch64编译的,但是spring对于ARM来说还是太臃肿了,就算是个hello world,编译最快也需要半个多小时,就算只添加启动类的依赖,编译后的体积也有90多MB,运行时内存消耗也有70多MB,对于内存一般都不大的ARM设备来说,还是太奢侈了些,更别说直接使用JVM运行了,那内存消耗分分钟破几百兆,还需要依赖JDK环境。
DDNS服务运作流程:
1. 从小米路由器获取WAN口地址
2. 调用域名服务商提供的接口更新IP
在手搓DDNS服务前,我们需要先自行把springboot3的所有常用功能都实现(起码把用到的功能实现)
1.先把springboot所有常用注解都定义一遍,下面全部自己实现
2.实现核心的bean工厂
- public class BeanFactory {
- private static Map<String, Object> beanMap = new HashMap<String, Object>();
- public synchronized static <T> T createBean(Class<T> clazz, Set<Class<?>> services, Dict props) {
- try {
- Object obj = beanMap.get(clazz.getName());
- if (obj == null && clazz.isInterface()) {
- for (Class<?> service : services) {
- if (clazz.isAssignableFrom(service)) {
- obj = beanMap.get(service.getName());
- }
- }
- } else if (obj == null && !clazz.isInterface()) {
- Class<?>[] infs = clazz.getInterfaces();
- for (Class<?> inf : infs) {
- if (inf.getSimpleName().startsWith("I")) {
- obj = beanMap.get(inf.getName());
- }
- }
- }
- if (obj == null) {
- Field[] fields = clazz.getDeclaredFields();
- if (clazz.isInterface()) {
- for (Class<?> service : services) {
- if (clazz.isAssignableFrom(service)) {
- fields = service.getDeclaredFields();
- }
- }
- for (Class<?> service : services) {
- if (clazz.isAssignableFrom(service)) {
- T bean = clazz.cast(service.getDeclaredConstructor().newInstance());
- for (Field field : fields) {
- Autowired auto = field.getAnnotation(Autowired.class);
- if (auto != null) {
- Class<?> fieldClass = field.getType();
- field.setAccessible(true);
- Object fieldObj = createBean(fieldClass, services, props);
- field.set(bean, fieldObj);
- }
- Value value = field.getAnnotation(Value.class);
- if (value != null) {
- String key = value.value().substring(2, value.value().length() - 1);
- Class<?> fieldClass = field.getType();
- field.setAccessible(true);
- field.set(bean, getProp(key, props, fieldClass));
- }
- }
- Method[] methods = clazz.getDeclaredMethods();
- for (Method method : methods) {
- Cacheable cache = method.getAnnotation(Cacheable.class);
- if (cache != null) {
- bean = ProxyUtil.proxy(bean, CacheAspect.class);
- }
- }
- beanMap.put(clazz.getName(), bean);
- beanMap.put(service.getName(), bean);
- return bean;
- }
- }
- return null;
- } else {
- T bean = clazz.getDeclaredConstructor().newInstance();
- for (Field field : fields) {
- Autowired auto = field.getAnnotation(Autowired.class);
- if (auto != null) {
- Class<?> fieldClass = field.getType();
- field.setAccessible(true);
- Object fieldObj = createBean(fieldClass, services, props);
- field.set(bean, fieldObj);
- }
- Value value = field.getAnnotation(Value.class);
- if (value != null) {
- String key = value.value().substring(2, value.value().length() - 1);
- Class<?> fieldClass = field.getType();
- field.setAccessible(true);
- field.set(bean, getProp(key, props, fieldClass));
- }
- }
- Class<?>[] infs = clazz.getInterfaces();
- for (Class<?> inf : infs) {
- if (inf.getSimpleName().startsWith("I")) {
- Method[] methods = inf.getDeclaredMethods();
- for (Method method : methods) {
- Cacheable cache = method.getAnnotation(Cacheable.class);
- if (cache != null) {
- bean = ProxyUtil.proxy(bean, CacheAspect.class);
- }
- }
- beanMap.put(inf.getName(), bean);
- }
- }
- beanMap.put(clazz.getName(), bean);
- return bean;
- }
- } else {
- return clazz.cast(obj);
- }
- } catch (Exception e) {
- e.printStackTrace();
- return null;
- }
- }
- @SuppressWarnings("unchecked")
- public synchronized static <T> T getBean(Class<T> clazz) {
- Object obj = beanMap.get(clazz.getName());
- if (obj == null) {
- return null;
- }
- return (T) obj;
- }
- public synchronized static void setBean(String key, Object val) {
- beanMap.put(key, val);
- }
- public synchronized static void printBean() {
- beanMap.forEach((k, v) -> {
- System.out.println(k + ":" + v);
- });
- }
- @SuppressWarnings("unchecked")
- public static <T> T getProp(String key, Dict dict, Class<T> clazz) {
- String[] params = key.split("\\.");
- LinkedHashMap<String, Object> last = (LinkedHashMap<String, Object>) dict.get(params[0]);
- for (int i = 1; i < params.length - 1; i++) {
- if (last != null)
- last = (LinkedHashMap<String, Object>) last.get(params[i]);
- }
- if (last == null)
- return null;
- key = params[params.length - 1];
- Dict props = new Dict(last);
- if (clazz == String.class)
- return clazz.cast(props.getStr(key));
- else if (clazz == Integer.class || clazz == int.class)
- return (T) props.getInt(key);
- else if (clazz == Long.class || clazz == long.class)
- return (T) props.getLong(key);
- else if (clazz == Double.class || clazz == double.class)
- return (T) props.getDouble(key);
- else if (clazz == Boolean.class || clazz == boolean.class)
- return (T) props.getBool(key);
- else if (clazz == Float.class || clazz == float.class)
- return (T) props.getFloat(key);
- else if (clazz == Short.class || clazz == short.class)
- return (T) props.getShort(key);
- else if (clazz == Byte.class || clazz == byte.class)
- return (T) props.getByte(key);
- else if (clazz == BigDecimal.class)
- return clazz.cast(props.getBigDecimal(key));
- else if (clazz == Date.class)
- return clazz.cast(props.getDate(key));
- else if (clazz == BigInteger.class)
- return clazz.cast(props.getBigInteger(key));
- else
- return null;
- }
- }
复制代码
3.实现springboot启动器,用于加载yaml配置、初始化配置bean、实例化service、controller、定时任务、@Autowired 和 @Value 的依赖和配置注入等
- public class SpringApplication {
- public static void launch(String[] args) {
- System.out.println("DDNS服务已启动...");
- }
- public static void run(Class<?> clazz, String[] args) {
- TimeZone.setDefault(TimeZone.getTimeZone("Asia/Shanghai"));
- Dict props = new Dict();
- InputStream input = ResourceUtil.getStream("application.yaml");
- if (input != null) {
- try {
- props = YamlUtil.load(new InputStreamReader(input));
- } catch (Exception e) {
- e.printStackTrace();
- }
- }
- if (args.length > 0) {
- try {
- props = YamlUtil.load(new FileReader(args[0]));
- } catch (Exception e) {
- e.printStackTrace();
- }
- }
- List<?> classList = (List<?>) props.get("scans");
- SpringBootApplication sba = clazz.getAnnotation(SpringBootApplication.class);
- Set<Object> controllers = new HashSet<Object>();
- String[] packages = sba.scanBasePackages();
- for (String pack : packages) {
- Set<Class<?>> confs = ClassNativeUtil.scanPackageByAnnotation(classList, pack, Configuration.class);
- Set<Class<?>> comps = ClassNativeUtil.scanPackageByAnnotation(classList, pack, Component.class);
- Set<Class<?>> subControllers = ClassNativeUtil.scanPackageByAnnotation(classList, pack,
- RestController.class);
- Set<Class<?>> services = ClassNativeUtil.scanPackageByAnnotation(classList, pack, Service.class);
- for (Class<?> conf : confs) {
- Object obj = createObj(conf, services, props);
- Method[] methods = conf.getDeclaredMethods();
- for (Method method : methods) {
- Bean bean = method.getAnnotation(Bean.class);
- if (bean != null) {
- try {
- Object res = method.invoke(obj);
- BeanFactory.setBean(method.getReturnType().getName(), res);
- } catch (Exception e) {
- e.printStackTrace();
- }
- }
- }
- }
- for (Class<?> comp : comps) {
- createObj(comp, services, props);
- }
- for (Class<?> service : services) {
- createObj(service, services, props);
- EnableScheduling sch = service.getAnnotation(EnableScheduling.class);
- if (sch != null) {
- Object task = BeanFactory.getBean(service);
- Method[] taskMethods = service.getDeclaredMethods();
- for (Method taskMethod : taskMethods) {
- Scheduled scheduled = taskMethod.getAnnotation(Scheduled.class);
- if (scheduled != null) {
- taskMethod.setAccessible(true);
- CronUtil.schedule(scheduled.cron(), new Task() {
- @Override
- public void execute() {
- try {
- taskMethod.invoke(task);
- } catch (Exception e) {
- e.printStackTrace();
- }
- }
- });
- }
- }
- }
- }
- for (Class<?> controller : subControllers) {
- Object controllerObject = createObj(controller, services, props);
- controllers.add(controllerObject);
- }
- }
- Integer port = BeanFactory.getProp("server.port", props, Integer.class);
- if (port != null) {
- SimpleServer server = HttpUtil.createServer(port);
- for (Object controller : controllers) {
- Method[] methods = controller.getClass().getDeclaredMethods();
- for (Method method : methods) {
- method.setAccessible(true);
- StringBuffer reqMethod = new StringBuffer();
- StringBuffer path = new StringBuffer();
- GetMapping getMapping = method.getAnnotation(GetMapping.class);
- PostMapping postMapping = method.getAnnotation(PostMapping.class);
- if (getMapping != null) {
- reqMethod.append("GET");
- path.append(getMapping.value());
- }
- if (postMapping != null) {
- reqMethod.append("POST");
- path.append(postMapping.value());
- }
- if (!reqMethod.toString().equals(StrUtil.EMPTY)) {
- server.addAction(path.toString(), (req, res) -> {
- try {
- if (!reqMethod.toString().equals(req.getMethod())) {
- res.write("{"code":2,"msg":"不支持此请求方法"}", ContentType.JSON.toString());
- return;
- }
- Parameter[] params = method.getParameters();
- Object[] paramObject = new Object[params.length];
- for (int i = 0; i < params.length; i++) {
- Parameter param = params[i];
- RequestParam qp = param.getAnnotation(RequestParam.class);
- RequestBody rb = param.getAnnotation(RequestBody.class);
- if (qp != null) {
- String name = param.getName();
- paramObject[i] = req.getParam(name);
- } else if (reqMethod.toString().equals("POST") && rb != null) {
- Object obj = JSONUtil.toBean(req.getBody(), param.getType());
- paramObject[i] = obj;
- }
- }
- Object data = method.invoke(controller, paramObject);
- res.write(JSONUtil.toJsonStr(data), ContentType.JSON.toString());
- } catch (Exception e) {
- e.printStackTrace();
- res.write("{"code":1,"msg":"系统出错"}", ContentType.JSON.toString());
- }
- });
- }
- }
- }
- server.start();
- }
- CronUtil.setMatchSecond(true);
- CronUtil.start();
- launch(args);
- }
- public static <T> T createObj(Class<T> clazz, Set<Class<?>> services, Dict props) {
- T obj = BeanFactory.getBean(clazz);
- try {
- if (obj != null) {
- return (T) obj;
- }
- obj = BeanFactory.createBean(clazz, services, props);
- } catch (Exception e) {
- e.printStackTrace();
- return null;
- }
- return obj;
- }
- }
复制代码
4.实现@Cacheable缓存注解
i)创建代理工具
- package com.elite.ddns.util;
- import com.elite.ddns.aop.JDKAspect;
- import com.elite.ddns.factory.JDKProxyFactory;
- import cn.hutool.core.util.ReflectUtil;
- public class ProxyUtil {
- public static <T> T proxy(T target, Class<? extends JDKAspect> aspectClass) {
- return new JDKProxyFactory().proxy(target, ReflectUtil.newInstance(aspectClass));
- }
- }
复制代码 ii)对切面接口进行增强,hutool工具默认不支持环绕切面,需要增加环绕方法
- public interface JDKAspect {
- /**
- * 目标方法执行前的操作
- *
- * @param target 目标对象
- * @param method 目标方法
- * @param args 参数
- * [url=home.php?mod=space&uid=439838]@return[/url] 是否继续执行接下来的操作
- */
- boolean before(Object target, Method method, Object[] args);
- /**
- * 目标方法执行后的操作<br>
- * 如果 target.method 抛出异常且 {[url=home.php?mod=space&uid=50640]@link[/url] Aspect#afterException} 返回true,则不会执行此操作<br>
- * 如果 {@link Aspect#afterException} 返回false,则无论target.method是否抛出异常,均会执行此操作<br>
- *
- * @param target 目标对象
- * @param method 目标方法
- * @param args 参数
- * @param returnVal 目标方法执行返回值
- * @return 是否允许返回值(接下来的操作)
- */
- boolean after(Object target, Method method, Object[] args, Object returnVal);
- /**
- * 目标方法抛出异常时的操作
- *
- * @param target 目标对象
- * @param method 目标方法
- * @param args 参数
- * @param e 异常
- * @return 是否允许抛出异常
- */
- boolean afterException(Object target, Method method, Object[] args, Throwable e);
- Object around(Object target, Method method, Object[] args);
复制代码 iii)实现JDK的动态代理
- public class JDKInterceptor implements InvocationHandler, Serializable {
- private static final long serialVersionUID = 1L;
- private final Object target;
- private final JDKAspect aspect;
- /**
- * 构造
- *
- * @param target 被代理对象
- * @param aspect 切面实现
- */
- public JDKInterceptor(Object target, JDKAspect aspect) {
- this.target = target;
- this.aspect = aspect;
- }
- public Object getTarget() {
- return this.target;
- }
- @Override
- public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
- final Object target = this.target;
- final JDKAspect aspect = this.aspect;
- Object result = null;
- // 开始前回调
- if (aspect.before(target, method, args)) {
- // 环绕切面
- result = aspect.around(target, method, args);
- if (result == null) {
- ReflectUtil.setAccessible(method);
- try {
- result = method.invoke(ClassUtil.isStatic(method) ? null : target, args);
- } catch (InvocationTargetException e) {
- // 异常回调(只捕获业务代码导致的异常,而非反射导致的异常)
- if (aspect.afterException(target, method, args, e.getTargetException())) {
- throw e;
- }
- }
- }
- // 结束执行回调
- if (aspect.after(target, method, args, result)) {
- return result;
- }
- }
- return null;
- }
- }
复制代码 iv)实现缓存切面
- public class CacheAspect implements JDKAspect {
- private static Cache<String, Object> fifoCache = CacheUtil.newLRUCache(10);
- @Override
- public boolean before(Object target, Method method, Object[] args) {
- CacheEvict cacheEvict = method.getAnnotation(CacheEvict.class);
- if (cacheEvict != null) {
- String key = target.getClass().getName() + "." + cacheEvict.value();
- synchronized (fifoCache) {
- fifoCache.remove(key);
- }
- }
- return true;
- }
- @Override
- public boolean after(Object target, Method method, Object[] args, Object returnVal) {
- Cacheable cache = method.getAnnotation(Cacheable.class);
- if (cache != null) {
- String key = target.getClass().getName() + "." + cache.value();
- synchronized (fifoCache) {
- if (fifoCache.get(key, false) == null)
- fifoCache.put(key, returnVal, DateUnit.SECOND.getMillis() * cache.ttl());
- }
- }
- return true;
- }
- @Override
- public boolean afterException(Object target, Method method, Object[] args, Throwable e) {
- return true;
- }
- @Override
- public Object around(Object target, Method method, Object[] args) {
- Cacheable cache = method.getAnnotation(Cacheable.class);
- if (cache != null) {
- String key = target.getClass().getName() + "." + cache.value();
- synchronized (fifoCache) {
- return fifoCache.get(key, false);
- }
- }
- return null;
- }
- }
复制代码
4.现在,就可以正常使用所有自定义的springboot注解进行开发,使用方法和原框架完全一致,只是自行对功能进行简单实现,可以成称为精简版springboot
5.将项目打包成jar,效果很好,只有2.3MB,因为移除了所有springboot的依赖,项目体积极小
6.使用agentlib工具进行反射分析
java -agentlib:native-image-agent=config-output-dir=./native -jar D:\\ddns.native.jar
7.将生成的native目录下所有文件复制到这个目录,就可以开始原生编译: mvn -Pnative native:compile -DskipNativeTests
8.编译需要5分钟,比springboot3速度快了30分钟,体积是36MB,虽然体积还是不小,但是对于臃肿的JAVA来说,已经很强了!
9.迫不及待添加系统脚本,让系统运行时自动启动
10.不错运行一切正常
11.查看内存占用,50MB,不错不错,虽然还是有点多,但比springboot编译少了一大半,相当于JVM启动的零头!
11.源码:借助hutool工具实现了springboot3的所有常用功能,依赖包只有2MB,适合ARM的云原生编译,完美!
激动的心,颤抖的手,我基于这套框架继续开发其他应用来压榨这台玩客云3 PRO去了!
PS:非保姆教程,需要对JAVA开发有一定经验者才行
|
本帖子中包含更多资源
您需要 登录 才可以下载或查看,没有账号?立即注册
×
|