当前位置: 首页 > news >正文

【Spring之手写一个依赖注入容器】

Spring之手写一个依赖注入容器

  • 1. 创建两个自定义注解
    • 1.1 Component注解
    • 1.2 DI注解
  • 2. ApplicationContext接口与实现类
    • 2.1 ApplicationContext 接口
    • 2.2 实现类:DefaultListableApplicationContext
  • 3. 定义DAO层和Service层及其实现
  • 4. 定义异常信息类
    • 4.1 InjectBeanException
    • 4.2 NotExistsBean
    • 4.3 NotSupportMoreSuperInterface
  • 5. 测试自定义bean容器(带依赖注入)
    • 5.1 新建测试类TestUser
    • 5.2 输出结果

1. 创建两个自定义注解

1.1 Component注解

@Target(value = ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Component {String value() default "";
}

1.2 DI注解

@Target(value = ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface DI {String value() default "";}

2. ApplicationContext接口与实现类

2.1 ApplicationContext 接口

public interface ApplicationContext {// 获取beanObject getBean(String name);// 获取bean<T> T getBean(String name, Class<T> clazz);// 判断bean是否存在boolean exists(String name);boolean exists(String name, Class<?> clazz);
}

2.2 实现类:DefaultListableApplicationContext

// 实现类
public class DefaultListableApplicationContext implements ApplicationContext {/*** 存放bean对象容器*/private Map<String, Object> objectsMap = new ConcurrentHashMap<>(128);private Map<Class, Object> objectsTypeMap = new ConcurrentHashMap<>(128);private Logger log = LoggerFactory.getLogger(DefaultListableApplicationContext.class);// 扫描的基础路径private String basePath;public DefaultListableApplicationContext(String basePackage) {//public static void pathDemo1(String basePackageName){String packagePath = basePackage.replaceAll("\\.", "\\\\");try {URL url = Thread.currentThread().getContextClassLoader().getResource(packagePath);if (StringUtils.hasText(url.getPath())) {String filePath = URLDecoder.decode(url.getFile(), "UTF8");// 扫描的基础路径basePath = filePath.substring(0, filePath.length() - packagePath.length());//包扫描loadBeans(new File(filePath));// 依赖注入loadDI();}} catch (IOException e) {throw new RuntimeException(e);}}@Override@Nullablepublic Object getBean(String name) {return objectsMap.get(name);}@Override@Nullablepublic <T> T getBean(String name, Class<T> clazz) {Object obj = objectsMap.get(name);if (obj == null) {Object bean;if ((bean = objectsTypeMap.get(clazz)) == null) {for (Class<?> interfaceClazz : clazz.getInterfaces()) {if ((bean = objectsTypeMap.get(interfaceClazz)) != null)return (T) bean;getBean(name, interfaceClazz);}return null;}return (T) bean;}return clazz.isInstance(obj) ? clazz.cast(obj) : null;}@Overridepublic boolean exists(String name) {return objectsMap.get(name) == null ? false : true;}@Overridepublic boolean exists(String name, Class<?> clazz) {if (objectsMap.get(name) == null) {if (objectsTypeMap.get(clazz) == null) {for (Class<?> interfaceClazz : clazz.getInterfaces()) {if (objectsTypeMap.get(interfaceClazz) != null)return true;exists(name, interfaceClazz);}return false;}return true;}return true;}/*** 包扫描** @param file*/private void loadBeans(File file) {// 1 判断当前文件是否文件夹if (file.isDirectory()) {File[] childrenFiles = file.listFiles();// 2. 获取文件夹里面的所有内容// 3. 判断文件夹为空,直接返回if (childrenFiles == null || childrenFiles.length == 0) {return;}// 4. 如果文件夹不为空,遍历文件夹内容for (File childFile : childrenFiles) {// 4.1  遍历得到每个file对象,继续判断。如果是文件夹,递归if (childFile.isDirectory()) {loadBeans(childFile);} else {// 4.2 遍历得到的file不是文件夹,是文件// 4.3 得到包路径 + 类名称部分// C:/desktop/IdeaProjects/spring-mytest/src/main/java/com/ypy/contextString pathWithClass = childFile.getAbsolutePath().substring(basePath.length() - 1);// 4.4 判断当前文件类型是否为.classif (pathWithClass.endsWith(".class")) {// 4.5 如果是.class类型,把路径\替换成., 把.class去掉// com.ypy.context.beans.UserServiceImplString allName = pathWithClass.replaceAll("\\\\", ".").replace(".class", "");// 4.6 使用反射实例化try {Class<?> clazz = Class.forName(allName);if (!clazz.isInterface()) {// 4.6.1 判断是否有@Component注解,有if (clazz.getAnnotation(Component.class) != null) {Object instance = clazz.getConstructor().newInstance();// 优先使用用户自定义的bean名称if (StringUtils.hasText(clazz.getAnnotation(Component.class).value())) {String beanName = clazz.getAnnotation(Component.class).value();objectsMap.put(beanName, instance);log.warn(">>> objectsMap store bean(name,obj) ===> (" + beanName + "," + instance + ")");if (clazz.getInterfaces().length == 1) {objectsTypeMap.put(clazz.getInterfaces()[0], instance);log.warn(">>> objectsTypeMap store bean(class,obj) ===> (" + clazz.getInterfaces()[0].getSimpleName() + "," + instance + ")");} else if (clazz.getInterfaces().length == 0) {objectsTypeMap.put(clazz, instance);log.warn(">>> objectsTypeMap store bean(class,obj) ===> (" + clazz.getInterfaces()[0].getSimpleName() + "," + instance + ")");} else {throw new NotSupportMoreSuperInterface("Not support the bean that has more than two super interfaces.");}}// 其次使用父接口的类名作为bean名称else if (clazz.getInterfaces().length > 0) {String interfaceName = clazz.getInterfaces()[0].getSimpleName();String beanName = lowercaseFirstLetter(interfaceName);// 放入容器中objectsMap.put(beanName, instance);log.warn(">>> objectsMap store bean(name,obj) ===> (" + beanName + "," + instance + ")");// 可能出现bugobjectsTypeMap.put(clazz.getInterfaces()[0], instance);log.warn(">>> objectsTypeMap store bean(class,obj) ===> (" + clazz.getInterfaces()[0].getSimpleName() + "," + instance + ")");}// 如果说没有父接口,就使用类名作为bean名称else {String beanName = lowercaseFirstLetter(clazz.getSimpleName());// 放入容器中objectsMap.put(beanName, instance);log.warn(">>> objectsMap store bean(name,obj) ===> (" + beanName + "," + instance + ")");objectsTypeMap.put(clazz, instance);log.warn(">>> objectsTypeMap store bean(class,obj) ===> (" + clazz.getInterfaces()[0].getSimpleName() + "," + instance + ")");}}}} catch (Exception e) {throw new RuntimeException(e);}}}}}}private void loadDI() {for (Map.Entry<String, Object> entry : objectsMap.entrySet()) {Object obj = entry.getValue();Class<?> clazz = obj.getClass();Field[] fields = clazz.getDeclaredFields();for (Field field : fields) {try {field.setAccessible(true);if (field.getAnnotation(DI.class) != null) {Class<?>[] interfaces = field.getType().getInterfaces();String needWiredBeanName;Object autoWiredBean;// 优先使用注解DI的value注入if (StringUtils.hasText(needWiredBeanName = field.getAnnotation(DI.class).value())) {autoWiredBean = objectsMap.get(needWiredBeanName);if (autoWiredBean != null) {field.set(obj, autoWiredBean);log.warn("<<< DI: Class " + clazz.getSimpleName() + " of field named " + field.getName() + " is injected with value of " + autoWiredBean + " from " + "objectsMap, by value of annotation @DI");continue;} /*else {throw new NotExistsBean("Not exists the bean named <" + needWiredBeanName + "> to inject as property");}*/}// 没有父接口needWiredBeanName = lowercaseFirstLetter(field.getType().getSimpleName());if ((autoWiredBean = objectsMap.get(needWiredBeanName)) != null || (autoWiredBean = objectsTypeMap.get(field.getType())) != null) {field.set(obj, autoWiredBean);log.warn("<<< DI: Class " + clazz.getSimpleName() + " of field named " + field.getName() + " is injected with value of " + autoWiredBean + " , by value or type of property itself ");continue;}// 使用父接口的名字从bean容器中查找,注入if (interfaces.length > 0) {for (Class<?> interfaceClazz : interfaces) {String interfaceClazzName = interfaceClazz.getSimpleName();if (interfaceClazz.isAssignableFrom(field.getType())) {needWiredBeanName = lowercaseFirstLetter(interfaceClazzName);if ((autoWiredBean = objectsMap.get(needWiredBeanName)) != null || (autoWiredBean = objectsTypeMap.get(interfaceClazz)) != null) {field.set(obj, autoWiredBean);log.warn("<<< DI: Class " + clazz.getSimpleName() + " of field named " + field.getName() + " is injected with value of " + autoWiredBean + ", by value or type of super interface");}}}continue;}throw new InjectBeanException("There occurs an Exception while injecting property filed [" + field.getName() + "] of Class <" + clazz.getSimpleName() + "> , because bean factory doesn't exist the bean named " + needWiredBeanName);}} catch (IllegalAccessException e) {throw new RuntimeException(e);}}}}private String lowercaseFirstLetter(String name) {return name.substring(0, 1).toLowerCase() + name.substring(1);}}

3. 定义DAO层和Service层及其实现

// 3.1 dao层
public interface UserDao {void addUser(User user);
}/** 
*  userDao实现
*/
@Component("userDao")
public class UserDaoImpl implements UserDao {@Overridepublic void addUser(User user) {System.out.println();System.out.println("------------------>>>执行UserDaoImpl中的addUser方法开始<<<<------------------");System.out.println("\t\t\t\t\t\t名字\t\t\t\t年龄");System.out.println("\t\t\t\t\t\t"+user.getName()+"\t\t\t\t"+user.getAge());System.out.println("------------------>>>执行UserDaoImpl中的addUser方法完毕<<<<------------------");}
}// 3.2 service层public interface UserService {void add();
}// service实现
@Component("userService")
public class UserServiceImpl implements UserService {@DI("userDao")private UserDaoImpl userDao;@Overridepublic void add() {System.out.println("》》》 user service impl execute add method...");User user = new User("张三", 25);userDao.addUser(user);}
}

4. 定义异常信息类

4.1 InjectBeanException

public class InjectBeanException extends RuntimeException{public InjectBeanException() {super();}public InjectBeanException(String message) {super(message);}
}

4.2 NotExistsBean

public class NotExistsBean extends RuntimeException{public NotExistsBean() {}public NotExistsBean(String message) {super(message);}
}

4.3 NotSupportMoreSuperInterface

public class NotSupportMoreSuperInterface extends RuntimeException{public NotSupportMoreSuperInterface() {super();}public NotSupportMoreSuperInterface(String message) {super(message);}
}

5. 测试自定义bean容器(带依赖注入)

5.1 新建测试类TestUser

public class TestUser {public static void main(String[] args) {DefaultListableApplicationContext context = new DefaultListableApplicationContext("com.ypy.context");UserService userService = context.getBean("aaa", UserServiceImpl.class);UserDao userDao = context.getBean("userDaoImpl", UserDao.class);boolean existFlag = context.exists("aaa"/*, UserService.class*/);System.out.println("UserService exists ? " + existFlag);System.out.println("从Bean容器中获取aaa对象地址: " + userService);System.out.println("从Bean容器中获取userDao对象地址: " + userDao);userService.add();}
}

5.2 输出结果

一月 18, 2024 4:01:20 上午 com.sun.org.slf4j.internal.Logger warn
警告: >>> objectsMap store bean(name,obj) ===> (userDao,com.ypy.context.dao.impl.UserDaoImpl@2b193f2d)
一月 18, 2024 4:01:20 上午 com.sun.org.slf4j.internal.Logger warn
警告: >>> objectsTypeMap store bean(class,obj) ===> (UserDao,com.ypy.context.dao.impl.UserDaoImpl@2b193f2d)
一月 18, 2024 4:01:20 上午 com.sun.org.slf4j.internal.Logger warn
警告: >>> objectsMap store bean(name,obj) ===> (userService,com.ypy.context.service.impl.UserServiceImpl@7a81197d)
一月 18, 2024 4:01:20 上午 com.sun.org.slf4j.internal.Logger warn
警告: >>> objectsTypeMap store bean(class,obj) ===> (UserService,com.ypy.context.service.impl.UserServiceImpl@7a81197d)
一月 18, 2024 4:01:20 上午 com.sun.org.slf4j.internal.Logger warn
警告: <<< DI: Class UserServiceImpl of field named userDao is injected with value of com.ypy.context.dao.impl.UserDaoImpl@2b193f2d from objectsMap, by value of annotation @DI
UserService exists ? false
从Bean容器中获取aaa对象地址: com.ypy.context.service.impl.UserServiceImpl@7a81197d
从Bean容器中获取userDao对象地址: com.ypy.context.dao.impl.UserDaoImpl@2b193f2d
》》》 user service impl execute add method...------------------>>>执行UserDaoImpl中的addUser方法开始<<<<------------------名字				年龄张三				25
------------------>>>执行UserDaoImpl中的addUser方法完毕<<<<------------------Process finished with exit code 0

相关文章:

【Spring之手写一个依赖注入容器】

Spring之手写一个依赖注入容器 1. 创建两个自定义注解1.1 Component注解1.2 DI注解 2. ApplicationContext接口与实现类2.1 ApplicationContext 接口2.2 实现类&#xff1a;DefaultListableApplicationContext 3. 定义DAO层和Service层及其实现4. 定义异常信息类4.1 InjectBean…...

kafka之java客户端实战

1. kafka的客户端 Kafka提供了两套客户端API&#xff0c;HighLevel API和LowLevel API。 HighLevel API封装了kafka的运行细节&#xff0c;使用起来比较简单&#xff0c;是企业开发过程中最常用的客户端API。 而LowLevel API则需要客户端自己管理Kafka的运行细节&#xff0c;Pa…...

图解渠道网关:不只是对接渠道的接口(一)

这是《百图解码支付系统设计与实现》专栏系列文章中的第&#xff08;20&#xff09;篇。点击上方关注&#xff0c;深入了解支付系统的方方面面。 主要讲清楚什么是渠道&#xff0c;有哪些类型的渠道&#xff0c;什么是渠道网关&#xff0c;渠道网关在支付系统中定位、核心功能…...

【js版数据结构学习之队列】

队列 一、简要认识队列二、队列的封装三、队列的应用1.栈和队列的转换2.全排列3.任务调度4.缓存管理 一、简要认识队列 结构&#xff1a;一种特殊的线性表 入队&#xff1a;在队尾插入一个元素 出队&#xff1a;在队头删除一个元素 特点&#xff1a;先入先出 空队列&#xff1…...

iOS Xcode 升级Xcode15报错: SDK does not contain ‘libarclite‘

iOS Xcode 升级Xcode15报错: SDK does not contain libarclite 一、仔细查看报错代码: SDK does not contain libarclite at the path /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/ lib/arc/libarclite_iphonesimulator.a; try in…...

python高级练习题库实验2(B)部分

文章目录 题目1代码实验结果题目2代码实验结果题目总结题目1 注册课程小游戏程序 研究下面的例子,并编写一个与这些例子完全相同的程序。使用for loop和break来解决问题。提示用户输入课程数量,是否选择,并且课程代码,最后还需显示已经完成的课程注册数量或者未完成的注册…...

vue项目编译非常慢,经常卡在某个百分点

1、注册插件 2、在项目根目录下的 babel.config.js 文件中加入下方配置 3、将import导入方式改为require导入方式&#xff0c;返回promise 4、如果动态加载组件import引入组件找不到组件&#xff08;Error: Cannot find module&#xff09; 使用 webpack 的 require.ensure() …...

开源知识库zyplayer-doc部署指南

1.前置条件 docker已经安装 mysql已经安装且数据库zyplayer-doc存在 服务器ip:192.168.168.99/ 数据库账户:root,密码:123456 2.拉取镜像 docker pull zyplayer/zyplayer-doc:latest 3.启动 docker run -d \--restart unless-stopped \--name zyplayer-doc \-p 8083:8083 …...

第90讲:MySQL数据库主从复制集群原理概念以及搭建流程

文章目录 1.MySQL主从复制集群的核心概念1.1.什么是主从复制集群1.2.主从复制集群中的专业术语1.3.主从复制集群工作原理1.4.主从复制中的小细节1.5.搭建主从复制集群的前提条件1.6.MySQL主从复制集群的架构信息 2.搭建MySQL多实例环境2.1.在mysql-1中搭建身为主库的MySQL实例2…...

PHP反序列化漏洞-魔术方法绕过

一、__wakeup()魔法函数绕过: 在PHP中,__wakeup()是一个魔术方法,用于在反序列化对象时自动调用。当反序列化字符串中的对象属性个数大于实际属性个数时,可以利用这个漏洞进行绕过。 触发条件: PHP版本为5.6.25或早期版本,或者PHP7版本小于7.0.10。反序列化字符串中的对…...

抖店和商品橱窗的区别?这两个千万别再搞混了!

我是电商珠珠 很多人都会将抖店和商品橱窗搞混&#xff0c;想开抖店的人开了商品橱窗&#xff0c;想开橱窗的人开通了抖店。 我做抖店三年了&#xff0c;这种情况屡见不鲜。 那么抖店和商品橱窗究竟有什么区别呢&#xff1f; 1、属性不同 商品橱窗是抖音所展现商品的一个功…...

个人总结钉钉7.5新品发布会

钉钉发布了 7.5 版本&#xff0c;最主要推出了围绕AI能力的各项升级&#xff0c;通过AI“超级助理”提升组织内部的沟通协作效率、管理决策智能化程度&#xff0c;以及相关的音视频、在线文档、Teambition功能的升级&#xff0c;以满足不同企业的多元化需求。截至发布会&#x…...

连接超时的问题

连接超时的问题 通用第三方工具连接超时 connect timeout 方案一&#xff1a; /etc/ssh/sshd_config node1上操作&#xff0c;图是错的 方案二&#xff1a; windows上Hosts文件域名解析有问题 比如&#xff1a; 192.168.xx.100 node1 192.168.xx.161 node1 两个都解析成node…...

python贪吃蛇游戏

为了实现这个游戏&#xff0c;需要用到Python的pygame模块&#xff0c;它是一个专门用于开发游戏的模块&#xff0c;提供了很多方便的功能&#xff0c;比如窗口、图形、音效、事件处理等。 用pygame来创建一个窗口&#xff0c;设置游戏的背景色&#xff0c;画出蛇和食物&#…...

【Spring】Spring AOP

文章目录 前言1. 什么是 AOP2. 什么是 Spring AOP3. Spring AOP 的使用引入 AOP 依赖编写 AOP 程序 4. Spring AOP 详解4.1 Spring AOP 的概念4.1.1 切点4.1.2 连接点4.1.3 通知4.1.4 切面 4.2 通知类型4.3 切点4.4 切面优先级 Order注解4.5 切点表达式4.5.1 execution 切点表达…...

软件开发架构

【 一 】软件开发架构图 【 1】ATM和选课系统 三层的开发架构 前段展示台 后端逻辑层 数据处理层 【二】软件开发架构的步骤流程 需求分析&#xff1a;在软件开发架构设计之前&#xff0c;需要对应用系统进行需求分析&#xff0c;明确用户需求、功能模块、业务流程等内容。…...

计图大模型推理库部署指南,CPU跑大模型,具有高性能、配置要求低、中文支持好、可移植等特点

Excerpt 计图大模型推理库,具有高性能、配置要求低、中文支持好、可移植等特点 计图大模型推理库,具有高性能、配置要求低、中文支持好、可移植等特点 计图大模型推理库 - 笔记本没有显卡也能跑大模型 本大模型推理库JittorLLMs有以下几个特点: 成本低:相比同类框架,本库…...

CSS||Emmet语法

1、简介 ​ Emmet语法的前身是Zen coding,它使用缩写,来提高html/css的编写速度, Vscode内部已经集成该语法。 ​ 快速生成HTML结构语法 ​ 快速生成CSS样式语法 2、快速生成HTML结构语法 生成标签 直接输入标签名 按tab键即可 比如 div 然后tab 键&#xff0c; 就可以生成 <…...

Android中的anr定位指导与建议

1.背景 8月份安卓出现了一次直播间卡死(ANR)问题&#xff0c;且由于排查难度较大&#xff0c;持续了较长时间。本文针对如何快速定位安卓端出现ANR问题进行总结和探讨. 这里大致补充一下当时的情况,当时看到情景的是从某一个特定的场景下进入直播间后整个直播间界面立刻就卡住…...

YOLOV7剪枝流程

YOLOV7剪枝流程 1、训练 1&#xff09;划分数据集进行训练前的准备&#xff0c;按正常的划分流程即可 2&#xff09;修改train.py文件 第一次处在参数列表里添加剪枝的参数&#xff0c;正常训练时设置为False&#xff0c;剪枝后微调时设置为True parser.add_argument(--pr…...

XML Group端口详解

在XML数据映射过程中&#xff0c;经常需要对数据进行分组聚合操作。例如&#xff0c;当处理包含多个物料明细的XML文件时&#xff0c;可能需要将相同物料号的明细归为一组&#xff0c;或对相同物料号的数量进行求和计算。传统实现方式通常需要编写脚本代码&#xff0c;增加了开…...

stm32G473的flash模式是单bank还是双bank?

今天突然有人stm32G473的flash模式是单bank还是双bank&#xff1f;由于时间太久&#xff0c;我真忘记了。搜搜发现&#xff0c;还真有人和我一样。见下面的链接&#xff1a;https://shequ.stmicroelectronics.cn/forum.php?modviewthread&tid644563 根据STM32G4系列参考手…...

C# SqlSugar:依赖注入与仓储模式实践

C# SqlSugar&#xff1a;依赖注入与仓储模式实践 在 C# 的应用开发中&#xff0c;数据库操作是必不可少的环节。为了让数据访问层更加简洁、高效且易于维护&#xff0c;许多开发者会选择成熟的 ORM&#xff08;对象关系映射&#xff09;框架&#xff0c;SqlSugar 就是其中备受…...

网络编程(UDP编程)

思维导图 UDP基础编程&#xff08;单播&#xff09; 1.流程图 服务器&#xff1a;短信的接收方 创建套接字 (socket)-----------------------------------------》有手机指定网络信息-----------------------------------------------》有号码绑定套接字 (bind)--------------…...

图表类系列各种样式PPT模版分享

图标图表系列PPT模版&#xff0c;柱状图PPT模版&#xff0c;线状图PPT模版&#xff0c;折线图PPT模版&#xff0c;饼状图PPT模版&#xff0c;雷达图PPT模版&#xff0c;树状图PPT模版 图表类系列各种样式PPT模版分享&#xff1a;图表系列PPT模板https://pan.quark.cn/s/20d40aa…...

OPenCV CUDA模块图像处理-----对图像执行 均值漂移滤波(Mean Shift Filtering)函数meanShiftFiltering()

操作系统&#xff1a;ubuntu22.04 OpenCV版本&#xff1a;OpenCV4.9 IDE:Visual Studio Code 编程语言&#xff1a;C11 算法描述 在 GPU 上对图像执行 均值漂移滤波&#xff08;Mean Shift Filtering&#xff09;&#xff0c;用于图像分割或平滑处理。 该函数将输入图像中的…...

虚拟电厂发展三大趋势:市场化、技术主导、车网互联

市场化&#xff1a;从政策驱动到多元盈利 政策全面赋能 2025年4月&#xff0c;国家发改委、能源局发布《关于加快推进虚拟电厂发展的指导意见》&#xff0c;首次明确虚拟电厂为“独立市场主体”&#xff0c;提出硬性目标&#xff1a;2027年全国调节能力≥2000万千瓦&#xff0…...

解读《网络安全法》最新修订,把握网络安全新趋势

《网络安全法》自2017年施行以来&#xff0c;在维护网络空间安全方面发挥了重要作用。但随着网络环境的日益复杂&#xff0c;网络攻击、数据泄露等事件频发&#xff0c;现行法律已难以完全适应新的风险挑战。 2025年3月28日&#xff0c;国家网信办会同相关部门起草了《网络安全…...

【p2p、分布式,区块链笔记 MESH】Bluetooth蓝牙通信 BLE Mesh协议的拓扑结构 定向转发机制

目录 节点的功能承载层&#xff08;GATT/Adv&#xff09;局限性&#xff1a; 拓扑关系定向转发机制定向转发意义 CG 节点的功能 节点的功能由节点支持的特性和功能决定。所有节点都能够发送和接收网格消息。节点还可以选择支持一个或多个附加功能&#xff0c;如 Configuration …...

安卓基础(Java 和 Gradle 版本)

1. 设置项目的 JDK 版本 方法1&#xff1a;通过 Project Structure File → Project Structure... (或按 CtrlAltShiftS) 左侧选择 SDK Location 在 Gradle Settings 部分&#xff0c;设置 Gradle JDK 方法2&#xff1a;通过 Settings File → Settings... (或 CtrlAltS)…...