|
| 1 | +--- |
| 2 | +tags: ["Dev","Java"] |
| 3 | +title: "Goper直接撸java代码,真是操碎了心,tips总结①" |
| 4 | +linkTitle: "Java tips的总结" |
| 5 | +weight: 32 |
| 6 | +description: > |
| 7 | + Java Tips总结 |
| 8 | +--- |
| 9 | + |
| 10 | +{{% pageinfo %}} |
| 11 | +Java发展的这些年,为了复杂大型项目的工程实践能傻瓜落地,真是封了一层又一层啊,C/C++型选手转过来,会感觉各种看不透,总结了一些tips,尽量追根问底,分享给大家。 |
| 12 | +{{% /pageinfo %}} |
| 13 | + |
| 14 | +## @SpringBootApplication 注解 |
| 15 | + |
| 16 | +java spring项目,第一个看到的基本就是这个注解,只能加到main方法所在的主类上,标识这是一个Spring Boot应用程序的main入口类。 |
| 17 | + |
| 18 | +> @(注解)的本质是给代码打标签,告诉需要处理注解的工具或框架: 这里的代码需要特殊处理。 |
| 19 | +
|
| 20 | +“@SpringBootApplication”注解,是个组合注解,是表示要在程序启动运行阶段被特殊处理的(其实现完全依赖于Java的反射机制),是spring框架的核心机制,依靠Spring容器的运行时处理能力,体现了「约定优于配置」的设计思想。 |
| 21 | + |
| 22 | +> 没点儿Java经验,「约定优于配置」的设计思想,会让学习成本增加,但是在大型项目中,会让开发效率大大提高。 |
| 23 | +> spring 容器的运行时处理能力,是实现「约定优于配置」的关键。 |
| 24 | +
|
| 25 | +### Spring 容器 |
| 26 | + |
| 27 | +spring 容器是一个IoC容器(简单理解为一个对象工厂),用于管理应用程序中的Bean对象。 |
| 28 | + |
| 29 | +> Bean的生命周期包括:实例化、依赖注入、初始化、使用、销毁。 |
| 30 | +> 依赖注入是指在运行时,通过容器自动将Bean的依赖对象注入到Bean中。 |
| 31 | +> 初始化是指在Bean实例化后,调用Bean的初始化方法。 |
| 32 | +> 使用是指在Bean初始化后,通过容器获取Bean实例,调用其方法。 |
| 33 | +> 销毁是指在应用程序关闭时,调用Bean的销毁方法。 |
| 34 | +
|
| 35 | +简单来说: |
| 36 | +- 你写好代码,贴上标签(注解) |
| 37 | +- 容器帮你管理这些代码(Bean) |
| 38 | +- 你需要什么,容器就给你什么(依赖注入) |
| 39 | + |
| 40 | +### 为什么要使用 Spring 容器? |
| 41 | + |
| 42 | +> 理由有一堆,还都是高大上的那种.... |
| 43 | +
|
| 44 | +拿用户注册功能对比,举例说明,对比「不用Spring容器」和「用Spring容器」的代码差异,直观展示Spring容器的价值: |
| 45 | + |
| 46 | +**场景:用户注册功能(需要3层结构)** |
| 47 | +1. Controller :接收注册请求 |
| 48 | +2. Service :处理注册业务逻辑(密码加密、保存用户) |
| 49 | +3. Repository :操作数据库(保存用户信息) |
| 50 | + |
| 51 | +**方式1:不用Spring容器 → 传统Java开发** |
| 52 | + |
| 53 | +```java |
| 54 | +// 1. Repository层:操作数据库 |
| 55 | +public class UserRepository { |
| 56 | + public void save(User user) { |
| 57 | + System.out.println("保存用户到数据库:" + user.getName()); |
| 58 | + } |
| 59 | +} |
| 60 | + |
| 61 | +// 2. Service层:业务逻辑 |
| 62 | +public class UserService { |
| 63 | + // 必须手动创建依赖对象 → 「自己new」,初始化省略.. |
| 64 | + private UserRepository userRepository = new UserRepository(); |
| 65 | + |
| 66 | + public void registerUser(User user) { |
| 67 | + // 密码加密(业务逻辑) |
| 68 | + user.setPassword(encryptPassword(user.getPassword())); |
| 69 | + // 调用Repository保存 |
| 70 | + userRepository.save(user); |
| 71 | + } |
| 72 | + |
| 73 | + private String encryptPassword(String password) { |
| 74 | + return "加密后的密码:" + password; |
| 75 | + } |
| 76 | +} |
| 77 | + |
| 78 | +// 3. Controller层:接收请求 |
| 79 | +public class UserController { |
| 80 | + // 必须手动创建依赖对象 → 「自己new」 |
| 81 | + private UserService userService = new UserService(); |
| 82 | + |
| 83 | + // 可以改进为接收参数 |
| 84 | + public void handleRegisterRequest(String name, String password) { |
| 85 | + User user = new User(); |
| 86 | + user.setName(name); |
| 87 | + user.setPassword(password); |
| 88 | + userService.registerUser(user); |
| 89 | + } |
| 90 | +} |
| 91 | + |
| 92 | +// 4. 启动类:手动创建所有对象 |
| 93 | +public class App { |
| 94 | + public static void main(String[] args) { |
| 95 | + // 必须手动创建所有层的对象 → 「自己管理依赖」 |
| 96 | + UserController controller = new UserController(); |
| 97 | + // 调用注册接口 |
| 98 | + controller.handleRegisterRequest("张三", "123456"); |
| 99 | + } |
| 100 | +} |
| 101 | +``` |
| 102 | + |
| 103 | +有点儿偏面向过程的开发思路,需要自己管理各层依赖对象的创建初始化 : |
| 104 | +- 必须手动 new 所有对象,代码冗余 |
| 105 | +- 依赖关系硬编码(UserService直接依赖UserRepository的具体实现) |
| 106 | +- 修改依赖时有可能需要修改所有相关代码(比如把UserRepository换成Redis实现) |
| 107 | +- 无法集中管理对象生命周期 |
| 108 | + |
| 109 | +**方式2:用Spring容器 → 依赖注入开发** |
| 110 | + |
| 111 | +```java |
| 112 | +// 1. Repository层:用@Repository标记,告诉容器「UserRepository类是要管理的Bean对象」 |
| 113 | +@Repository |
| 114 | +public class UserRepository { |
| 115 | + public void save(User user) { |
| 116 | + System.out.println("保存用户到数据库:" + user.getName()); |
| 117 | + } |
| 118 | +} |
| 119 | + |
| 120 | +// 2. Service层:用@Service标记,@Autowired让容器自动注入依赖 |
| 121 | +@Service |
| 122 | +public class UserService { |
| 123 | + // 容器自动注入UserRepository → 「不用自己new」,配置已初始化 |
| 124 | + @Autowired |
| 125 | + private UserRepository userRepository; |
| 126 | + |
| 127 | + public void registerUser(User user) { |
| 128 | + // 专注于业务逻辑(密码加密) |
| 129 | + user.setPassword(encryptPassword(user.getPassword())); |
| 130 | + userRepository.save(user); |
| 131 | + } |
| 132 | + |
| 133 | + private String encryptPassword(String password) { |
| 134 | + return "加密后的密码:" + password; |
| 135 | + } |
| 136 | +} |
| 137 | + |
| 138 | +// 3. Controller层:用@RestController标记,@Autowired自动注入Service |
| 139 | +@RestController |
| 140 | +public class UserController { |
| 141 | + // 容器自动注入UserService → 「自动解决依赖」 |
| 142 | + @Autowired |
| 143 | + private UserService userService; |
| 144 | + |
| 145 | + @PostMapping("/register") |
| 146 | + public String registerUser(@RequestBody User user) { |
| 147 | + // 专注于处理请求,不用关心Service怎么来的 |
| 148 | + userService.registerUser(user); |
| 149 | + return "注册成功"; |
| 150 | + } |
| 151 | +} |
| 152 | + |
| 153 | +// 4. 启动类:用@SpringBootApplication标记,容器自动创建所有对象 |
| 154 | +@SpringBootApplication |
| 155 | +public class App { |
| 156 | + public static void main(String[] args) { |
| 157 | + // 一行代码启动 → 容器自动处理所有对象创建和依赖 |
| 158 | + SpringApplication.run(App.class, args); |
| 159 | + } |
| 160 | +} |
| 161 | +``` |
| 162 | + |
| 163 | +**具体价值体现 1. 价值1:不用自己new对象** |
| 164 | +1. 价值1:不用自己new对象 |
| 165 | +- 传统方式 :每个类都要手动 new 依赖对象,并自己管理参数初始化 |
| 166 | +- Spring方式 :只需要用 @Service / @Repository 标记,容器自动创建,并配合其他注解可自动配置参数。 |
| 167 | +- 例子 : UserService 不用写 new UserRepository() ,容器会自动创建并注入 |
| 168 | + |
| 169 | +2. 价值2:自动解决依赖 |
| 170 | +- 传统方式 :依赖关系硬编码,修改时要改所有相关代码 |
| 171 | +- Spring方式 : @Autowired 自动找到并注入(嵌入)合适的对象 |
| 172 | +- 例子 :如果把 UserRepository 换成Redis实现,只需要给Redis实现类加 @Repository ,不需要修改 UserService 代码 |
| 173 | + |
| 174 | +3. 价值3:专注业务逻辑 |
| 175 | +- 传统方式 :花大量时间管理对象创建和依赖 |
| 176 | +- Spring方式 :只需要关心业务逻辑(如密码加密、用户验证) |
| 177 | +- 例子 : UserService 只需要写 registerUser 的业务代码,不用关心 UserRepository 怎么来的 |
| 178 | + |
| 179 | +### spring 启动过程 |
| 180 | + |
| 181 | +回到开头提到的spring应用中常遇到的第一个注解,会出现在主类上,“@SpringBootApplication”注解,标识这是一个Spring Boot应用程序的main入口类。 |
| 182 | + |
| 183 | +“@SpringBootApplication”注解,包含了以下3个注解: |
| 184 | + |
| 185 | +1. @SpringBootConfiguration: 标识这是应用的主配置类(入口),是@Configuration的派生注解,提供了Spring Boot特定的配置语义,确保配置类能被正确识别和处理,优先级说明:默认配置 → 自动配置 → 外部配置(application.yml) → 手动配置(@Configuration)。 |
| 186 | + |
| 187 | +2. @EnableAutoConfiguration: 启用Spring Boot的自动配置机制。 |
| 188 | +- 自动扫描 classpath 下所有 `META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports` 文件(Spring Boot 2.7+) |
| 189 | +- 根据pom中的配置的依赖包、条件配置类等信息,自动配置相应的Bean(比如数据源、Web MVC配置等)。 |
| 190 | +- 另外会看application.yml配置文件中的属性,覆盖默认配置。举例:修改Tomcat默认端口为9090。 |
| 191 | + |
| 192 | +```yml |
| 193 | +# application.yml |
| 194 | +server: |
| 195 | + port: 9090 # 覆盖默认的 8080 端口 |
| 196 | + |
| 197 | +spring: |
| 198 | + datasource: |
| 199 | + url: jdbc:mysql://localhost:3306/yudao |
| 200 | + username: root |
| 201 | + password: 123456 |
| 202 | +``` |
| 203 | +
|
| 204 | +- Profile多环境支持 |
| 205 | + - 可以根据不同的环境(如开发、测试、生产),加载不同的配置文件(如application-dev.yml、application-test.yml、application-prod.yml)。 |
| 206 | + - 可以通过spring.profiles.active属性指定当前激活的环境,默认是default环境。 |
| 207 | +
|
| 208 | +3. @ComponentScan: 开启组件扫描,Spring Boot会自动扫描主类所在的包及其子包,重点找到使用了@Controller、@Service、@Repository等注解的类,并将这些Bean对象注册到Spring容器中。 |
| 209 | + - 支持通过basePackages参数设置,指定要扫描的包名,控制扫描范围,灵活组织应用的功能。 |
| 210 | +
|
| 211 | +经过以上3个隐含注解的协同处理,程序启动完成,会进入运行状态,等待接收外部请求。 |
| 212 | +
|
| 213 | +示例: |
| 214 | +```java |
| 215 | +/** |
| 216 | + * Spring Boot应用程序的主启动类 |
| 217 | + * |
| 218 | + * @SpringBootApplication 是一个组合注解,包含以下三个核心功能: |
| 219 | + * 1. @SpringBootConfiguration: 标识这是一个Spring Boot应用的主配置类 |
| 220 | + * 2. @EnableAutoConfiguration: 启用Spring Boot的自动配置机制 |
| 221 | + * 3. @ComponentScan: 自动扫描当前包及其子包下的组件(如@Controller、@Service、@Repository等) |
| 222 | + * |
| 223 | + * 这个注解告诉Spring Boot框架:这是一个Spring Boot应用程序的入口点 |
| 224 | + */ |
| 225 | +@SpringBootApplication |
| 226 | +public class ServerApplication { |
| 227 | + |
| 228 | + /** |
| 229 | + * 应用程序的主入口方法 |
| 230 | + * |
| 231 | + * 当JVM启动时,会首先执行这个main方法 |
| 232 | + * 该方法负责启动整个Spring Boot应用程序 |
| 233 | + * |
| 234 | + * @param args 命令行参数,可以在启动时传入配置参数 |
| 235 | + * 例如:--server.port=8081 或 --spring.profiles.active=dev |
| 236 | + */ |
| 237 | + public static void main(String[] args) { |
| 238 | + /** |
| 239 | + * SpringApplication.run() 方法执行以下关键操作: |
| 240 | + * 1. 创建Spring应用上下文(ApplicationContext) |
| 241 | + * 2. 启用自动配置(根据classpath中的依赖自动配置Bean) |
| 242 | + * 3. 启动内嵌的Web服务器(如Tomcat、Jetty等) |
| 243 | + * 4. 扫描并注册所有使用Spring注解的组件 |
| 244 | + * 5. 启动完成后,应用程序进入运行状态,等待HTTP请求 |
| 245 | + * |
| 246 | + * 参数说明: |
| 247 | + * - ServerApplication.class: 主配置类,Spring Boot会从这个类所在的包开始扫描组件 |
| 248 | + * - args: 命令行参数,用于覆盖默认配置 |
| 249 | + * |
| 250 | + * 返回值:ConfigurableApplicationContext对象,代表整个Spring应用上下文 |
| 251 | + * 可以用于获取Bean、关闭应用等操作 |
| 252 | + */ |
| 253 | + SpringApplication.run(ServerApplication.class, args); |
| 254 | + } |
| 255 | +} |
| 256 | +``` |
| 257 | + |
| 258 | +这个过程体现了spring boot生态的核心价值 - 配置自动化,开发人员可以通过pom引入下依赖包(内部包也一样),然后直接改改yml配置,就可以轻松写出生产级的代码了。 |
| 259 | + |
| 260 | +> SpringBootApplication注解工作的机制中,还有不少细节,属于用到了可以现查资料的知识,此处先不展开。 |
| 261 | +> spring框架还有很多AOP、事务管理等其他优势,此处先不展开。 |
| 262 | +
|
| 263 | +## lombok |
| 264 | +解决Java样板代码的利器,通过**@注解**的方式,在编译期自动生成对应的代码,减少了样板代码的编写,提高了开发效率。 |
| 265 | + |
| 266 | +解决了哪些样板代码的编写? 比如:getter/setter、toString、equals、hashCode等。这些方法在大多数Java类中都需要,但是编写逻辑高度重复啰嗦,通过lombok注解,就可以自动生成这些代码,减少了开发人员的机械工作量。举例: |
| 267 | + |
| 268 | +```java |
| 269 | +@Data // @Data组合注解,自动为该类生成getter/setter/toString/equals/hashCode等方法,节省了约50行左右的代码量。 |
| 270 | +// 相当于以下注解组合 |
| 271 | +// @Getter、@Setter、@ToString、@EqualsAndHashCode等 |
| 272 | +public class User { |
| 273 | + private Long id; |
| 274 | + private String name; |
| 275 | + private Integer age; |
| 276 | +} |
| 277 | +``` |
| 278 | + |
| 279 | +**为什么每个Java类都需要这些方法 ?** |
| 280 | + |
| 281 | +因为这些方法是Java类的基础(所有类都直接或间接的继承了Object类),没有它们,大多数Java类就不能正常工作。 |
| 282 | + |
| 283 | +```java |
| 284 | +// 所有类都继承 Object |
| 285 | +public class User { // 隐式继承 Object |
| 286 | + // 自动拥有 Object 的方法:toString(), equals(), hashCode() 等 |
| 287 | +} |
| 288 | +``` |
| 289 | + |
| 290 | +比如: |
| 291 | +- 没有getter/setter方法,就不能通过对象的属性来访问和修改属性值,Java封装思想的具体实现。 |
| 292 | +- 没有toString方法,就不能通过System.out.println()方法来打印对象的信息。 |
| 293 | +- 没有 equals 方法,虽然仍然可以使用equals方法(继承自Object),只是比较的是引用相等性,不能用于比较对象内容是否相等。 |
| 294 | +- 没有 hashCode 方法,就不能将对象存储在HashSet、HashMap等集合中。 |
| 295 | + |
| 296 | +```java |
| 297 | +Map<User, String> map = new HashMap<>(); |
| 298 | +User user = new User(); |
| 299 | +map.put(user, "value"); // 需要拥有正确的 hashCode() 和 equals() |
| 300 | + |
| 301 | +Set<User> set = new HashSet<>(); |
| 302 | +set.add(user); // 同样需要正确的 hashCode() 和 equals() |
| 303 | +``` |
| 304 | + |
| 305 | +确保了 Java 中所有对象都具有统一的基本行为,也是哈希集合(如 HashMap、HashSet)等功能的基础。 |
0 commit comments