MyBatis?Generator生成的$?sql是否存在注入風(fēng)險詳解
代理商sql注入問題排查
經(jīng)全面排查,代理商中sql層使用'$'獲取對象的只有一種類型,代碼格式如下:
<sql id="Example_Where_Clause"> <!-- WARNING - @mbggenerated This element is automatically generated by MyBatis Generator, do not modify. --> <where> <foreach collection="oredCriteria" item="criteria" separator="or"> <if test="criteria.valid"> <trim prefix="(" suffix=")" prefixOverrides="and"> <foreach collection="criteria.criteria" item="criterion"> <choose> <when test="criterion.noValue"> and ${criterion.condition} </when> <when test="criterion.singleValue"> and ${criterion.condition} #{criterion.value} </when> <when test="criterion.betweenValue"> and ${criterion.condition} #{criterion.value} and #{criterion.secondValue} </when> <when test="criterion.listValue"> and ${criterion.condition} <foreach collection="criterion.value" item="listItem" open="(" close=")" separator=","> #{listItem} </foreach> </when> </choose> </foreach> </trim> </if> </foreach> </where> </sql>
接下來我們在測試demo中復(fù)現(xiàn)下情況:
準(zhǔn)備測試demo
entity
Product.java
普通實體類,對應(yīng)數(shù)據(jù)庫中product表,表結(jié)構(gòu)見附錄:
package com.zhrb.springcloud.entity; import lombok.Data; import lombok.ToString; /** * @ClassName Product * @Description TODO * @Author Administrator * @Date 2019/9/3 14:26 * @Version */ @Data @ToString public class Product { //主鍵 private Long pid; //產(chǎn)品名稱 private String productName; // 來自哪個數(shù)據(jù)庫,因為微服務(wù)架構(gòu)可以一個服務(wù)對應(yīng)一個數(shù)據(jù)庫,同一個信息被存儲到不同數(shù)據(jù)庫 private String dbSource; }
ProductExample.java
同代理商環(huán)境一樣的動態(tài)條件類:
package com.zhrb.springcloud.entity; import java.util.ArrayList; import java.util.List; /** * @ClassName ProductExample * @Description TODO * @Author Administrator * @Date 2019/9/20 9:07 * @Version */ public class ProductExample { /** * This field was generated by MyBatis Generator. * This field corresponds to the database table CPT_DLS_CONFIG * * @mbggenerated */ protected String orderByClause; /** * This field was generated by MyBatis Generator. * This field corresponds to the database table CPT_DLS_CONFIG * * @mbggenerated */ protected boolean distinct; /** * This field was generated by MyBatis Generator. * This field corresponds to the database table CPT_DLS_CONFIG * * @mbggenerated */ protected List<Criteria> oredCriteria; /** * This method was generated by MyBatis Generator. * This method corresponds to the database table CPT_DLS_CONFIG * * @mbggenerated */ public ProductExample() { oredCriteria = new ArrayList<Criteria>(); } /** * This method was generated by MyBatis Generator. * This method corresponds to the database table CPT_DLS_CONFIG * * @mbggenerated */ public void setOrderByClause(String orderByClause) { this.orderByClause = orderByClause; } /** * This method was generated by MyBatis Generator. * This method corresponds to the database table CPT_DLS_CONFIG * * @mbggenerated */ public String getOrderByClause() { return orderByClause; } /** * This method was generated by MyBatis Generator. * This method corresponds to the database table CPT_DLS_CONFIG * * @mbggenerated */ public void setDistinct(boolean distinct) { this.distinct = distinct; } /** * This method was generated by MyBatis Generator. * This method corresponds to the database table CPT_DLS_CONFIG * * @mbggenerated */ public boolean isDistinct() { return distinct; } /** * This method was generated by MyBatis Generator. * This method corresponds to the database table CPT_DLS_CONFIG * * @mbggenerated */ public List<Criteria> getOredCriteria() { return oredCriteria; } /** * This method was generated by MyBatis Generator. * This method corresponds to the database table CPT_DLS_CONFIG * * @mbggenerated */ public void or(Criteria criteria) { oredCriteria.add(criteria); } /** * This method was generated by MyBatis Generator. * This method corresponds to the database table CPT_DLS_CONFIG * * @mbggenerated */ public Criteria or() { Criteria criteria = createCriteriaInternal(); oredCriteria.add(criteria); return criteria; } /** * This method was generated by MyBatis Generator. * This method corresponds to the database table CPT_DLS_CONFIG * * @mbggenerated */ public Criteria createCriteria() { Criteria criteria = createCriteriaInternal(); if (oredCriteria.size() == 0) { oredCriteria.add(criteria); } return criteria; } /** * This method was generated by MyBatis Generator. * This method corresponds to the database table CPT_DLS_CONFIG * * @mbggenerated */ protected Criteria createCriteriaInternal() { Criteria criteria = new Criteria(); return criteria; } /** * This method was generated by MyBatis Generator. * This method corresponds to the database table CPT_DLS_CONFIG * * @mbggenerated */ public void clear() { oredCriteria.clear(); orderByClause = null; distinct = false; } /** * This class was generated by MyBatis Generator. * This class corresponds to the database table CPT_DLS_CONFIG * * @mbggenerated */ protected abstract static class GeneratedCriteria { protected List<Criterion> criteria; protected GeneratedCriteria() { super(); criteria = new ArrayList<Criterion>(); } public boolean isValid() { return criteria.size() > 0; } public List<Criterion> getAllCriteria() { return criteria; } public List<Criterion> getCriteria() { return criteria; } protected void addCriterion(String condition) { if (condition == null) { throw new RuntimeException("Value for condition cannot be null"); } criteria.add(new Criterion(condition)); } protected void addCriterion(String condition, Object value, String property) { if (value == null) { throw new RuntimeException("Value for " + property + " cannot be null"); } criteria.add(new Criterion(condition, value)); } protected void addCriterion(String condition, Object value1, Object value2, String property) { if (value1 == null || value2 == null) { throw new RuntimeException("Between values for " + property + " cannot be null"); } criteria.add(new Criterion(condition, value1, value2)); } public Criteria andIdIsNull() { addCriterion("PID is null"); return (Criteria) this; } public Criteria andIdIsNotNull() { addCriterion("PID is not null"); return (Criteria) this; } public Criteria andIdEqualTo(String value) { addCriterion("PID =", value, "pid"); return (Criteria) this; } public Criteria andIdNotEqualTo(String value) { addCriterion("PID <>", value, "pid"); return (Criteria) this; } public Criteria andIdGreaterThan(String value) { addCriterion("PID >", value, "pid"); return (Criteria) this; } public Criteria andIdGreaterThanOrEqualTo(String value) { addCriterion("PID >=", value, "pid"); return (Criteria) this; } public Criteria andIdLessThan(String value) { addCriterion("PID <", value, "pid"); return (Criteria) this; } public Criteria andIdLessThanOrEqualTo(String value) { addCriterion("PID <=", value, "pid"); return (Criteria) this; } public Criteria andIdLike(String value) { addCriterion("PID like", value, "pid"); return (Criteria) this; } public Criteria andIdNotLike(String value) { addCriterion("PID not like", value, "pid"); return (Criteria) this; } public Criteria andIdIn(List<String> values) { addCriterion("PID in", values, "pid"); return (Criteria) this; } public Criteria andIdNotIn(List<String> values) { addCriterion("PID not in", values, "pid"); return (Criteria) this; } public Criteria andIdBetween(String value1, String value2) { addCriterion("PID between", value1, value2, "pid"); return (Criteria) this; } public Criteria andIdNotBetween(String value1, String value2) { addCriterion("PID not between", value1, value2, "pid"); return (Criteria) this; } } /** * This class was generated by MyBatis Generator. * This class corresponds to the database table CPT_DLS_CONFIG * * @mbggenerated do_not_delete_during_merge */ public static class Criteria extends GeneratedCriteria { protected Criteria() { super(); } } /** * This class was generated by MyBatis Generator. * This class corresponds to the database table CPT_DLS_CONFIG * * @mbggenerated */ public static class Criterion { private String condition; private Object value; private Object secondValue; private boolean noValue; private boolean singleValue; private boolean betweenValue; private boolean listValue; private String typeHandler; public String getCondition() { return condition; } public Object getValue() { return value; } public Object getSecondValue() { return secondValue; } public boolean isNoValue() { return noValue; } public boolean isSingleValue() { return singleValue; } public boolean isBetweenValue() { return betweenValue; } public boolean isListValue() { return listValue; } public String getTypeHandler() { return typeHandler; } protected Criterion(String condition) { super(); this.condition = condition; this.typeHandler = null; this.noValue = true; } protected Criterion(String condition, Object value, String typeHandler) { super(); this.condition = condition; this.value = value; this.typeHandler = typeHandler; if (value instanceof List<?>) { this.listValue = true; } else { this.singleValue = true; } } protected Criterion(String condition, Object value) { this(condition, value, null); } protected Criterion(String condition, Object value, Object secondValue, String typeHandler) { super(); this.condition = condition; this.value = value; this.secondValue = secondValue; this.typeHandler = typeHandler; this.betweenValue = true; } protected Criterion(String condition, Object value, Object secondValue) { this(condition, value, secondValue, null); } } }
控制層ProductController.java
package com.zhrb.springcloud.controller; import com.zhrb.springcloud.entity.Product; import com.zhrb.springcloud.entity.ProductExample; import com.zhrb.springcloud.service.ProductService; import io.swagger.annotations.Api; import io.swagger.annotations.ApiOperation; import org.mybatis.spring.annotation.MapperScan; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.*; import java.util.Collection; import java.util.List; /** * @ClassName ProductController * @Description TODO * @Author zhrb * @Date 2019/9/3 15:18 * @Version */ @RestController @RequestMapping("/product") @MapperScan("com.zhrb.springcloud.mapper") @Api(value = "/product",description = "商品管理 程序員小圈圈",position = 1) public class ProductController { @Autowired private ProductService productService; @ApiOperation(value="測試是否預(yù)編譯", notes="測試是否預(yù)編譯") @GetMapping(value = "/testList") public List<Product> testList() { ProductExample example = new ProductExample(); example.createCriteria().andIdLike("1' or '1=1"); List<Product> productList = productService.list(example); for (Product p :productList){ p.setProductName(p.getProductName()+"本條數(shù)據(jù)來自8001"); } return productList; } }
service層
ProductService.java
package com.zhrb.springcloud.service; import com.zhrb.springcloud.entity.Product; import com.zhrb.springcloud.entity.ProductExample; import java.util.List; /** * @ClassName ProductService * @Description TODO * @Author Administrator * @Date 2019/9/3 15:15 * @Version */ public interface ProductService { List<Product> list(ProductExample example); }
ProductServiceImpl.java
package com.zhrb.springcloud.service.impl; import com.zhrb.springcloud.entity.Product; import com.zhrb.springcloud.entity.ProductExample; import com.zhrb.springcloud.mapper.ProductMapper; import com.zhrb.springcloud.service.ProductService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import java.util.List; /** * @ClassName ProductServiceImpl * @Description TODO * @Author Administrator * @Date 2019/9/3 15:16 * @Version */ @Service public class ProductServiceImpl implements ProductService{ @Autowired private ProductMapper productMapper; @Override public List<Product> list(ProductExample example) { return productMapper.testList(example); } }
mapper
ProductController.java
package com.zhrb.springcloud.mapper; import com.zhrb.springcloud.entity.Product; import com.zhrb.springcloud.entity.ProductExample; import org.apache.ibatis.annotations.Mapper; import java.util.List; /** * @ClassName ProductMapper * @Description TODO * @Author Administrator * @Date 2019/9/3 14:55 * @Version */ @Mapper public interface ProductMapper { List<Product> testList(ProductExample example); }
ProductController.xml
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> <mapper namespace="com.zhrb.springcloud.mapper.ProductMapper"> <select id="testList" parameterType="com.zhrb.springcloud.entity.ProductExample" resultType="com.zhrb.springcloud.entity.Product"> select pid, product_name, db_source from product <if test="_parameter != null" > <include refid="Example_Where_Clause" /> </if> <if test="orderByClause != null" > order by ${orderByClause} </if> </select> <sql id="Example_Where_Clause" > <!-- WARNING - @mbggenerated This element is automatically generated by MyBatis Generator, do not modify. --> <where > <foreach collection="oredCriteria" item="criteria" separator="or" > <if test="criteria.valid" > <trim prefix="(" suffix=")" prefixOverrides="and" > <foreach collection="criteria.criteria" item="criterion" > <choose > <when test="criterion.noValue" > and ${criterion.condition} </when> <when test="criterion.singleValue" > and ${criterion.condition} #{criterion.value} </when> <when test="criterion.betweenValue" > and ${criterion.condition} #{criterion.value} and #{criterion.secondValue} </when> <when test="criterion.listValue" > and ${criterion.condition} <foreach collection="criterion.value" item="listItem" open="(" close=")" separator="," > #{listItem} </foreach> </when> </choose> </foreach> </trim> </if> </foreach> </where> </sql> </mapper>
測試
測試1:正常邏輯測試
首先按照正常代碼邏輯測試,校驗代碼是否成功,測試結(jié)果截圖如下:
可以看到調(diào)用成功,證明代碼邏輯沒問題,接下來進(jìn)行異常測試:
測試2:測試不存在的表字段
修改ProductExample.java如下(數(shù)據(jù)庫中字段為pid,無id,故先將pid改為id測試不存在字段編譯過程):
package com.zhrb.springcloud.entity; import java.util.ArrayList; import java.util.List; /** * @ClassName ProductExample * @Description TODO * @Author Administrator * @Date 2019/9/20 9:07 * @Version */ public class ProductExample { /** * This field was generated by MyBatis Generator. * This field corresponds to the database table CPT_DLS_CONFIG * * @mbggenerated */ protected String orderByClause; /** * This field was generated by MyBatis Generator. * This field corresponds to the database table CPT_DLS_CONFIG * * @mbggenerated */ protected boolean distinct; /** * This field was generated by MyBatis Generator. * This field corresponds to the database table CPT_DLS_CONFIG * * @mbggenerated */ protected List<Criteria> oredCriteria; /** * This method was generated by MyBatis Generator. * This method corresponds to the database table CPT_DLS_CONFIG * * @mbggenerated */ public ProductExample() { oredCriteria = new ArrayList<Criteria>(); } /** * This method was generated by MyBatis Generator. * This method corresponds to the database table CPT_DLS_CONFIG * * @mbggenerated */ public void setOrderByClause(String orderByClause) { this.orderByClause = orderByClause; } /** * This method was generated by MyBatis Generator. * This method corresponds to the database table CPT_DLS_CONFIG * * @mbggenerated */ public String getOrderByClause() { return orderByClause; } /** * This method was generated by MyBatis Generator. * This method corresponds to the database table CPT_DLS_CONFIG * * @mbggenerated */ public void setDistinct(boolean distinct) { this.distinct = distinct; } /** * This method was generated by MyBatis Generator. * This method corresponds to the database table CPT_DLS_CONFIG * * @mbggenerated */ public boolean isDistinct() { return distinct; } /** * This method was generated by MyBatis Generator. * This method corresponds to the database table CPT_DLS_CONFIG * * @mbggenerated */ public List<Criteria> getOredCriteria() { return oredCriteria; } /** * This method was generated by MyBatis Generator. * This method corresponds to the database table CPT_DLS_CONFIG * * @mbggenerated */ public void or(Criteria criteria) { oredCriteria.add(criteria); } /** * This method was generated by MyBatis Generator. * This method corresponds to the database table CPT_DLS_CONFIG * * @mbggenerated */ public Criteria or() { Criteria criteria = createCriteriaInternal(); oredCriteria.add(criteria); return criteria; } /** * This method was generated by MyBatis Generator. * This method corresponds to the database table CPT_DLS_CONFIG * * @mbggenerated */ public Criteria createCriteria() { Criteria criteria = createCriteriaInternal(); if (oredCriteria.size() == 0) { oredCriteria.add(criteria); } return criteria; } /** * This method was generated by MyBatis Generator. * This method corresponds to the database table CPT_DLS_CONFIG * * @mbggenerated */ protected Criteria createCriteriaInternal() { Criteria criteria = new Criteria(); return criteria; } /** * This method was generated by MyBatis Generator. * This method corresponds to the database table CPT_DLS_CONFIG * * @mbggenerated */ public void clear() { oredCriteria.clear(); orderByClause = null; distinct = false; } /** * This class was generated by MyBatis Generator. * This class corresponds to the database table CPT_DLS_CONFIG * * @mbggenerated */ protected abstract static class GeneratedCriteria { protected List<Criterion> criteria; protected GeneratedCriteria() { super(); criteria = new ArrayList<Criterion>(); } public boolean isValid() { return criteria.size() > 0; } public List<Criterion> getAllCriteria() { return criteria; } public List<Criterion> getCriteria() { return criteria; } protected void addCriterion(String condition) { if (condition == null) { throw new RuntimeException("Value for condition cannot be null"); } criteria.add(new Criterion(condition)); } protected void addCriterion(String condition, Object value, String property) { if (value == null) { throw new RuntimeException("Value for " + property + " cannot be null"); } criteria.add(new Criterion(condition, value)); } protected void addCriterion(String condition, Object value1, Object value2, String property) { if (value1 == null || value2 == null) { throw new RuntimeException("Between values for " + property + " cannot be null"); } criteria.add(new Criterion(condition, value1, value2)); } public Criteria andIdIsNull() { addCriterion("id is null"); return (Criteria) this; } public Criteria andIdIsNotNull() { addCriterion("id is not null"); return (Criteria) this; } public Criteria andIdEqualTo(String value) { addCriterion("id =", value, "id"); return (Criteria) this; } public Criteria andIdNotEqualTo(String value) { addCriterion("id <>", value, "id"); return (Criteria) this; } public Criteria andIdGreaterThan(String value) { addCriterion("id >", value, "id"); return (Criteria) this; } public Criteria andIdGreaterThanOrEqualTo(String value) { addCriterion("id >=", value, "id"); return (Criteria) this; } public Criteria andIdLessThan(String value) { addCriterion("id <", value, "id"); return (Criteria) this; } public Criteria andIdLessThanOrEqualTo(String value) { addCriterion("id <=", value, "id"); return (Criteria) this; } public Criteria andIdLike(String value) { addCriterion("id like", value, "id"); return (Criteria) this; } public Criteria andIdNotLike(String value) { addCriterion("id not like", value, "id"); return (Criteria) this; } public Criteria andIdIn(List<String> values) { addCriterion("id in", values, "id"); return (Criteria) this; } public Criteria andIdNotIn(List<String> values) { addCriterion("id not in", values, "id"); return (Criteria) this; } public Criteria andIdBetween(String value1, String value2) { addCriterion("id between", value1, value2, "id"); return (Criteria) this; } public Criteria andIdNotBetween(String value1, String value2) { addCriterion("id not between", value1, value2, "id"); return (Criteria) this; } } /** * This class was generated by MyBatis Generator. * This class corresponds to the database table CPT_DLS_CONFIG * * @mbggenerated do_not_delete_during_merge */ public static class Criteria extends GeneratedCriteria { protected Criteria() { super(); } } /** * This class was generated by MyBatis Generator. * This class corresponds to the database table CPT_DLS_CONFIG * * @mbggenerated */ public static class Criterion { private String condition; private Object value; private Object secondValue; private boolean noValue; private boolean singleValue; private boolean betweenValue; private boolean listValue; private String typeHandler; public String getCondition() { return condition; } public Object getValue() { return value; } public Object getSecondValue() { return secondValue; } public boolean isNoValue() { return noValue; } public boolean isSingleValue() { return singleValue; } public boolean isBetweenValue() { return betweenValue; } public boolean isListValue() { return listValue; } public String getTypeHandler() { return typeHandler; } protected Criterion(String condition) { super(); this.condition = condition; this.typeHandler = null; this.noValue = true; } protected Criterion(String condition, Object value, String typeHandler) { super(); this.condition = condition; this.value = value; this.typeHandler = typeHandler; if (value instanceof List<?>) { this.listValue = true; } else { this.singleValue = true; } } protected Criterion(String condition, Object value) { this(condition, value, null); } protected Criterion(String condition, Object value, Object secondValue, String typeHandler) { super(); this.condition = condition; this.value = value; this.secondValue = secondValue; this.typeHandler = typeHandler; this.betweenValue = true; } protected Criterion(String condition, Object value, Object secondValue) { this(condition, value, secondValue, null); } } }
測試結(jié)果如下:
可以看到,編譯出錯,證明此時雖然用的是$取值,也經(jīng)過了預(yù)編譯,繼續(xù)看下面。
測試3:like注入測試1
代碼及結(jié)果截圖如下:
從上面的圖可以得知:
此種注入,在封裝Criteria時把傳入的參數(shù)整體當(dāng)做一個對象然后傳遞下去,本次測試如上圖1,打了兩個斷點,但是沒執(zhí)行到第二個斷點處即中斷執(zhí)行,后臺日志報錯,證明此種注入sql有誤無法正常執(zhí)行。
測試3:like注入測試2
代碼及結(jié)果截圖如下:
like注入測試1中我們debug可以看到參數(shù)似乎拼接方式有誤,那么本次注入即正常注入方式,debug看參數(shù),如果將
andIdLike 值設(shè)置為:‘1' or ‘1=1'
數(shù)據(jù)上執(zhí)行的sql理論上是:
SELECT * from product WHERE pid LIKE '1' or '1=1';
在數(shù)據(jù)庫中執(zhí)行此條sql結(jié)果如下:
但是demo執(zhí)行查詢結(jié)果為空,并且控制臺報錯,證明此種注入亦不能注入成功。
結(jié)論
經(jīng)以上demo測試,此種$獲取值不會受到sql注入的影響,常規(guī)sql注入失敗。
附錄
數(shù)據(jù)庫表結(jié)構(gòu):
/* Navicat MySQL Data Transfer Source Server : BWG-104.225.147.76 Source Server Version : 50644 Source Host : 104.225.147.76:3306 Source Database : springcloud_db01 Target Server Type : MYSQL Target Server Version : 50644 File Encoding : 65001 Date: 2019-09-20 10:23:41 */ SET FOREIGN_KEY_CHECKS=0; -- ---------------------------- -- Table structure for product -- ---------------------------- DROP TABLE IF EXISTS `product`; CREATE TABLE `product` ( `pid` bigint(20) NOT NULL AUTO_INCREMENT, `product_name` varchar(50) DEFAULT NULL, `db_source` varchar(50) DEFAULT NULL, PRIMARY KEY (`pid`) ) ENGINE=InnoDB AUTO_INCREMENT=7 DEFAULT CHARSET=utf8; -- ---------------------------- -- Records of product -- ---------------------------- INSERT INTO `product` VALUES ('1', '手機(jī)', 'springcloud_db01'); INSERT INTO `product` VALUES ('2', '冰箱', 'springcloud_db01'); INSERT INTO `product` VALUES ('3', '電腦', 'springcloud_db01'); INSERT INTO `product` VALUES ('4', '洗衣機(jī)', 'springcloud_db01'); INSERT INTO `product` VALUES ('5', '電視', 'springcloud_db01'); INSERT INTO `product` VALUES ('6', '音響', 'springcloud_db01');
以上為個人經(jīng)驗,希望能給大家一個參考,也希望大家多多支持腳本之家。
相關(guān)文章
Springmvc工程跳轉(zhuǎn)controller無效的解決
這篇文章主要介紹了Springmvc工程跳轉(zhuǎn)controller無效的解決方案,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2021-09-09vue+springboot讀取git的markdown文件并展示功能
Markdown-it 是一個用于解析和渲染 Markdown 標(biāo)記語言的 JavaScript 庫,使用 Markdown-it,你可以將 Markdown 文本解析為 HTML 輸出,并且可以根據(jù)需要添加功能、擴(kuò)展語法或修改解析行為,本文介紹vue+springboot讀取git的markdown文件并展示,感興趣的朋友一起看看吧2024-01-01使用自定義注解+springAop實現(xiàn)參數(shù)非空校驗方式
這篇文章主要介紹了使用自定義注解+springAop實現(xiàn)參數(shù)非空校驗方式,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2021-09-09Java循環(huán)對bean的屬性進(jìn)行賦值的實現(xiàn)
本文主要介紹了Java循環(huán)對bean的屬性進(jìn)行賦值,文中通過示例代碼介紹的非常詳細(xì),具有一定的參考價值,感興趣的小伙伴們可以參考一下2021-08-08詳解Java中的println輸入和toString方法的重寫問題
這篇文章主要介紹了Java中的println輸入和toString方法的重寫,一個對象數(shù)組在調(diào)用Arrays.toString打印時,相當(dāng)于遍歷數(shù)組,然后打印里邊每個對象,這再打印對象就調(diào)用對象自己的toString了,需要的朋友可以參考下2022-04-04解決Spring Boot 在localhost域奇怪的404問題(Mac book pro)
這篇文章主要介紹了解決Spring Boot 在localhost域奇怪的404問題(Mac book pro),需要的朋友可以參考下2017-09-09Spring+SpringMVC+Hibernate整合實例講解
在本篇文章里小編給大家整理的是關(guān)于Spring+SpringMVC+Hibernate整合實例講解,需要的朋友們可以學(xué)習(xí)下。2020-03-03