java代码常见问题及优化建议
1、不充分的SQL参数验证(代码注入)
详细描述:攻击者可以在输入中注入恶意代码,没有对用户输入进行充分的验证,导致恶意输入可以绕过安全机制,从而执行未经授权的操作
举例说明:
SELECT * FROM users WHERE username = '输入的用户名' AND password = '输入的密码';
1、使用OR关键字
用户输入' OR '1' = '1', 可将原有SQL转换为:
SELECT * FROM users WHERE username = '' OR '1' = '1' AND password = ''; 查询将返回所有用户,攻击者可以绕过身份验证。
2、使用UNION关键字
用户输入' UNION SELECT email FROM users --
可将原有SQL转换为: SELECT * FROM users WHERE username = '' UNION SELECT email FROM users -- AND password = '';
查询将返回users表中的所有email字段,攻击者可以窃取用户的电子邮件地址
修改建议:
使用参数化查询、对用户输入进行验证和转义、限制数据库权限等
2、执行由前端提供的完整SQL
服务端执行由接口参数传递的完整sql
举例说明:
public ResultData<List<LinkedHashMap<String, Object>>> executeSql(String sql, String limit) {
if (ObjectUtils.isEmpty(limit)) {
limit = "limit 20";
}
List<LinkedHashMap<String, Object>> linkedHashMaps = sqlScriptMapper.executeSql(sql + " " + limit);
return ResultData.data(linkedHashMaps);
}
修改意见:
不允许在服务端执行前端完整sql,应在服务端预定义sql,前端只传递sql标识及参数,再由服务端执行,同时需要对参数进行注入风险检查。
3、索引可能失效
在where中对字段进行函数操作、计算操作会导致字段索引失效。
举例说明:
SELECT MAX(CAST(SUBSTRING(sr.remark,13) AS INTEGER)) FROM flfc_supervision_record sr WHERE SUBSTRING(sr.remark,1,8) = #{date}
修改意见:
尽量在SQL中不对字段时间函数、计算操作,避免索引失效。
4、数据事务范围过大或缺失事务控制
在类上声明@Transaction,在查询方法上启用事务注解,对多表数据变更服务未启用事务。
举例说明:
在类上声明事务注解
@Service
@Transactional
public class OperateTicketServeImpl implements IOperateTicketServer {}
修改建议:
1.禁止在声明类级的事务注解
2.不在查询类接口上声明事务注解
3.建议在多表数据变更服务上使用事务注解
4.设置合理的rollbackFor条件
5、未加密存储,明文密码(用户密码、数据库密码、FTP密码、应用秘钥等)
密码明文存储,即将敏感数据(如密码)直接以明文存储,或在代码中设置密码明文为变量值或在@Value定义为默认值
举例说明:
例如在User对象中,Password被不加处理的明文存储
java
复制代码
public class User {
private String username;
private String password; // 密码以明文形式存储
// 构造函数
public User(String username, String password) {
this.username = username;
this.password = password; // 直接存储明文密码
}
// 获取用户名
public String getUsername() {
return username;
}
// 设置用户名
public void setUsername(String username) {
this.username = username;
}
// 获取密码(这是不安全的,因为返回的是明文密码)
public String getPassword() {
return password;
}
// 设置密码(直接设置明文密码)
public void setPassword(String password) {
this.password = password;
}
}
修改建议:
为了改进安全性,应该采取以下措施:
密码哈希:存储密码的哈希值而不是明文密码。当验证密码时,哈希用户提供的密码并与存储的哈希值进行比较。
使用盐:为每个用户生成一个唯一的盐值,并将其与密码结合,然后哈希这个组合。这可以防止使用预先计算的哈希值进行攻击。
选择强哈希算法:使用像Bcrypt或Argon2这样的密码哈希算法,它们专门设计用于密码存储,并且比SHA-256等通用哈希算法更安全。
保护存储的密码:确保只有授权的服务或人员可以访问存储密码的数据库或系统。
禁止在代码中存储密码、应用密码等信息。
6、HTTP接口参数缺少验证
在RestController或Service中未对接口参数进行必要的可空、有效值范围等验证
举例说明:
@PostMapping("/listConfig")
public R<P<SysConfig>> listConfig(@RequestBody SysConfig config) {
return configService.listConfig(config);
}
修改建议:
使用SpringValidation框架或自定义校验代码对入参进行必要的检查,并向调用者返回合理的提示
7、服务端向调用方返回不必要的服务端信息
服务端向调用方返回了数据库信息(表名、字段名、数据错误描述等)、异常堆栈、操作系统信息等。
举例说明:
WrappedResult.failedWrappedResult(e.getMessage())
修改建议:
对服务端异常统一拦截,统一编码及描述,不返回异常详细信息。
8、代码硬编码URL、IP等信息
在代码中以变量、常量或配置项默认值方式硬编码了URL、IP等信息,一方面容易泄露各类环境的IP,另一方面代码在各现场容易出错,适应性差。
举例说明:
private static String dcloudUrl="http://172.16.221.133:1080/dcloudmodelservice";
修改建议:
采用配置文件管理外部系统URL、IP等信息。
9、空指针异常
是java中常见的运行时异常,它发生在程序试图在需要对象的地方使用null时。主要有以下几种常见情形:
1.对象未初始化:在调用对象的方法或访问其属性之前,对象没有被正确初始化。
2.返回null的方法调用:如果一个方法返回null,而调用该方法的代码没有检查null值就直接使用,就会抛出空指针异常。
3.数组元素未初始化:尝试访问数组中未初始化的元素,即数组元素为null.
举例说明:
变量空指针异常,该变量赋值为空,之后遍历该变量
List<VersionRecored> exsitsVersionRecordList == null;
for(VersionRecord versionRecord : versionRecordList) {
for(VersionRecord exsitsVersionRecord : exsitsVersionRecordList)
修改建议:
开发者应该在使用对象之前进行非空判断,或者确保对象在使用之前已经被初始化。此外,还可以使用Optional类来避免空指针异常,Optional类是一种可以为null的容器对象,如果值存在则可以直接获取,否则返回一个默认值。
10、线程、线程池不合理使用
线程的不合理使用包括:
1.创建过多线程:在这个例子中,我们创建了10个线程,但实际上可能并不需要这么多线程。过多的线程会消耗系统资源,并可能导致性能下降。
2.缺乏同步控制:Task类中的run方法访问了共享资源(在这里是system.out),但没有进行任务同步控制。这可能导致多个线程同时访问和修改同一资源,从而产生不可预测的结果。
3.没有使用线程池:在这个例子中,我们直接创建了新的线程来执行任务。这可能导致线程创建和销毁的开销很大。在实际应用中,应该考虑使用线程池来管理线程,这样可以复用线程,减少创建和销毁的开销。
4.缺乏异常处理:在Task的run方法中,我们捕获了InterruptedException但只是简单的打印了堆栈跟踪。在实际应用中,应该更适当的处理这种异常,例如重新中断线程或进行其他恢复操作。
5.线程泄露:由于我们直接创建线程而没有适当地管理它们,如果应用程序长时间运行并持续创建新线程,最终可能会导致线程泄露,即系统资源耗尽。
举例说明:
// 创建过多的线程
for (int i = 0; i < NUM_THREADS; i++) {
new Thread(new Task()).start();
}
// 创建方法级局线程池
private void test(){
ThreadPoolTaskExecutor taskExecutor = Utils.createThreadPoolExecutor();
...
}
修改建议:
1.使用线程池来管理线程,应使用可共享的全局线程池,应按需创建不同用途的线程,禁止使用局部线程池。
2.对共享资源的访问进行同步控制,例如使用synchronized关键字或java.util.concurrent包的锁。
3.适当处理异常,避免线程意外终止。
4.确保线程在完成任务后正确终止,避免线程泄露。
11、内存泄露
内存泄露通常发生在持续性的创建对象而忘记释放它们所占用的内存时,可能导致应用程序崩溃或性能下降。
举例说明:
List<Object> leadedList = new ArrayList<>();
public static void main(String[] args){
//持续向列表中添加对象
while(判断条件){
Object obj = new Object();
leadedList.add(obj);
}
}
修改建议:
确保在不再需要对象时释放他们的引用。或者使用能够自动管理资源的框架和库。此外,使用内存分析工具,可以帮助你检测和定位内存泄露问题。
12、并发异常
controller、service等单例对象中使用可变类属性,会导致数据共享或状态管理等问题;未使用并发读写安全的类,如使用HashMap
举例说明:
public class MyService{
private List<String> sharedList = new ArrayList<>();
public void addToList(String item){
sharedList.add(item);
}
public List<String> getList(){
return sharedlist:
}
}
@RestController
@RequestMapping(“/api”)
public class MyController{
@Autowired
private MyService myservice;
@PostMapping("/items")
public ResponseEntity<String> addItem(@RequestBody String item){
myservice.addToList(item);
return ResponseEntity.ok("Item added");
}
@GetMapping("/items")
public ResponseEntity<List<String>> getItems(){
return ResponseEntity.ok(myService.getList());
}
}
在这个例子中,MyService 类有一个私有的 sharedList 属性,这是一个ArrayList,是一个可变对象。因为MyService是单例的,这意味着无论有多少请求同时到达,它们都会操作同一个sharedList实例。
这可能会导致以下问题:
1、数据共享:不同的请求可能会同时修改sharedList,导致数据混乱或不可预测的行为。
2、线程安全性:sharedList没有进行线程安全处理。如果在多个线程同时修改sharedList时,可能会导致ConcurrentModificationException异常或数据不一致。
3、状态管理:sharedList的状态会在整个应用的生命周期内持续存在,这可能会导致难以调试的问题,因为状态可能会在不期望的时候被改变。
修改建议:
在Spring单例bean中不应使用可变类属性,或类属性的变化对服务请求是无状态的。使用并发安全的类(java.util.concurrent),Map使用ConcurrentHashMap,List使用Concurrent.SynchronizdList(new ArrayList());
13、数据库连接未释放或连接未池化,或使用后未归还连接池,或还池连接未恢复原始状态
数据库连接如果不被适当地释放,可能会导致资源泄露,因为数据库连接通常是有限的,并且长时间占用连接可能会耗尽连接池。
数据库连接未使用连接池。从池中获取连接,改变了连接的状态(如关闭自动提交),归还连接池时未将连接状态重置(如未还原为自动提交)
举例说明:
1、未关闭连接、未池化连接
public static void main(String[] args) {
Connection connection = null;
try {
// 加载数据库驱动
Class.forName("com.mysql.jdbc.Driver");
// 建立数据库连接
connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/mydatabase", "username", "password");
// 执行数据库操作...
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (SQLException e) {
e.printStackTrace();
}
// 忘记释放数据库连接(导致资源泄露)
// 在实际应用中,可能由于异常、逻辑错误或其他原因忘记关闭连接
2、池化对象未归还连接池
private void connDemo(){
Connection conn=dataSource.getConnection();
//数据库的操作后,未执行conn.close()
}
3、池化连接还池前未重置状态
private void connDemo(){
Connection conn=dataSource.getConnection();
//数据库事务操作后,归还连接前未调用conn.setAutoCommit(true);
conn.setAutoCommit(false);
try{
//事务操作
conn.commit();
}catch(Exception e){
conn.rollback();
}
//连接池池
conn.close();
}
修改建议:
1.确保在finally块中正确关闭所有资源,即使发生异常。
2.使用连接池,他们可以自动管理连接的创建、使用和释放。
3.从连接池获取的连接对象,在使用后必须关闭,否则不能归还连接池。
4.归还连接池的连接,必须重置改变的状态属性。
14、不合适的比较
不合适的比较操作通常会导致逻辑错误、程序崩溃或不可预测的行为。
举例说明:
1.字符串比较用==导致与预期结果不一致;
2.Integer与long型equals比较永远为false;
3、Java中int类型的变量number和一个String类型的变量text比较是不可以的,会报错,应先进行类型转换。
int number = 10;
String text = "1000";
// 不合适的比较:整数和字符串之间的比较
if (number == text) {
System.out.println("Comparison succeeded!");
} else {
System.out.println("Comparison failed.");
}
修改建议:
我们使用Integer.parseInt(text)方法尝试将字符串text转换为一个整数。如果转换成功,我们就可以安全地进行比较。如果字符串不能转换为整数(例如,它包含非数字字符),Integer.parseInt会抛出一个NumberFormatException异常,我们需要捕获这个异常并适当处理
14、移除无用的System.out、printStackTrace等控制台输出
过多的System.out、e.printStackTrace输出可能会产生大量的日志噪音,甚至可能泄露敏感信息。因此,在发布生产版本时,通常需要将它们移除或禁用。
修改建议:
1.手动删除或注释:
最直接的方法是在源代码中手动找到并删除或注释掉所有不需要的System.out、e.printStackTrace语句。这种方法虽然简单,但容易出错,尤其是当项目规模较大时。
2.使用日志框架:
使用像Log4j, SLF4J, Logback等日志框架,它们允许你通过配置文件灵活地控制日志的输出级别和输出目标。例如,你可以将System.out输出替换为日志框架的输出,并在生产环境中将日志级别设置为ERROR或WARN,这样就可以忽略掉所有的INFO和DEBUG级别的日志。
3.使用条件编译:
你可以利用Java的条件编译特性来移除System.out、e.printStackTrace。例如,你可以定义一个常量DEBUG,并在需要输出日志的地方使用if (DEBUG)来包裹System.out、e.printStackTrace语句。在编译生产版本时,你可以通过定义-DDEBUG=false来禁用这些输出。
15、未使用的声明变量或未调用的方法,大段已注释的代码或类
移除未使用的声明变量或未调用的方法、类,有助于保持代码的清晰和整洁,减少不必要的混淆,提高代码的可读性和可维护性。
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· DeepSeek “源神”启动!「GitHub 热点速览」
· 我与微信审核的“相爱相杀”看个人小程序副业
· 微软正式发布.NET 10 Preview 1:开启下一代开发框架新篇章
· C# 集成 DeepSeek 模型实现 AI 私有化(本地部署与 API 调用教程)
· spring官宣接入deepseek,真的太香了~