Skip to content

Commit 892f372

Browse files
committed
发送邮件
1 parent 794ae36 commit 892f372

25 files changed

Lines changed: 938 additions & 3 deletions
278 KB
Binary file not shown.
45.6 KB
Binary file not shown.
644 KB
Binary file not shown.
214 KB
Binary file not shown.

src/com/yale/test/io/file/ClassPathInput.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
* 如果我们把默认的配置放到jar包中,再从外部文件系统读取一个可选的配置文件,就可以做到既有默认的配置文件,又可以让用户自己修改配置:
1313
* 这样读取配置文件,应用程序启动就更加灵活。
1414
* Class对象的getResourceAsStream()可以从classpath中读取指定资源;
15+
* JVM对一个类只会加载一次,存在多个版本的,classpath在前的先加载,在后的永远加载不了
1516
* https://www.liaoxuefeng.com/wiki/1252599548343744/1298366384308257
1617
* @author dell
1718
*/
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
package com.yale.test.java.demo.jicheng;
2+
3+
public class Father {
4+
public String name = "父类的name属性";
5+
public Father() {
6+
super(); // 构造方法的第一行代码总是调用super(),如果你自己没加,JVM编译时会自动帮你加上
7+
//接下来初始化成员变量,上面name的值实际上是在这里被初始化的.
8+
}
9+
public void setName(String val) {
10+
this.name = this.name + val;
11+
}
12+
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
package com.yale.test.java.demo.jicheng;
2+
3+
public class Son extends Father {
4+
public static void main(String[] args) {
5+
Father fa = new Father();
6+
Son son = new Son();
7+
System.out.println("子类继承父类的属性name的值:" + son.name);
8+
fa.setName("变了");
9+
10+
System.out.println("父类的属性name值变了之后,子类会跟着变吗?:" + son.name);
11+
System.out.println("&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&");
12+
System.out.println("父类的属性值变了没?:" + fa.name);
13+
14+
}
15+
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
package com.yale.test.java.fanshe.proxy.cglib;
2+
3+
/*
4+
* 无论是使用AspectJ语法,还是配合Annotation,使用AOP,实际上就是让Spring自动为我们创建一个Proxy,使得调用方能无感知地调用指定方法,
5+
* 但运行期却动态“织入”了其他逻辑,因此,AOP本质上就是一个代理模式。
6+
* 因为Spring使用了CGLIB来实现运行期动态创建Proxy,如果我们没能深入理解其运行原理和实现机制,就极有可能遇到各种诡异的问题。
7+
* https://zhuanlan.zhihu.com/p/131584403
8+
* https://www.liaoxuefeng.com/wiki/1252599548343744/1281319432618017
9+
*/
10+
public class SrpingCGLIB {
11+
12+
public static void main(String[] args) {
13+
14+
}
15+
16+
}
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
package com.yale.test.java.fanshe.proxy.cglib.spring;
2+
3+
import org.springframework.context.ApplicationContext;
4+
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
5+
import org.springframework.context.annotation.ComponentScan;
6+
import org.springframework.context.annotation.Configuration;
7+
import org.springframework.context.annotation.EnableAspectJAutoProxy;
8+
9+
/*
10+
* 添加AOP类LoggingAspect之后,在AppConfig上加上@EnableAspectJAutoProxy。
11+
* 再次运行,不出意外的话,会得到一个NullPointerException:
12+
* 仔细跟踪代码,会发现null值出现在MailService.sendMail()内部的这一行代码:
13+
*
14+
* springboot默认cglib
15+
* 你真的深入用过Spring?如果你用xml配置当然可以指定接口,如果你写@ Component +autoscan只能标注在class,所有的bean都只能当具体类型用cglib代理
16+
* 只要不是springboot2,java类有接口,属性使用接口,使用@Aotuwired注入实现类,默认就是使用jdk的实现类,如果属性不使用向上造型,直接使用实现类,
17+
* 启动会报错,所以springboot2把默认的动态代理改成cglib,这样无论属性是使用接口还是实现类,都没有问题了。
18+
* 但是Spring 5.x 中也还是默认jdk动态代理。
19+
* 你说的是实现了接口的情况,接口没有字段不存在文中访问字段的问题,用jdk和cglib都没有问题。
20+
* 项目中service不使用接口怕是要被打死,所以说你这个对不知道spring有jdk动态代理的小白有误导,特别是要面试的人。一般讲这个知识点的,
21+
* 都是上来就明确有两种代理实现,否则小白还以为项目中用的都是cglib的代理(非springboot2),然而实际项目service一定会用接口,
22+
* 这是最基本的解藕规范了,否则切换一个业务实现类,还要把所有上游代码改一通不得崩溃。
23+
* 最近在看aop动态代理,说的就是如果有接口,优先用的就是jdk的动态代理,当然如果想用cglib可以在spring配置,如果是单独一个类,
24+
* 那么就用cglib,最近在看这块东西,小白一个,不知道理解这样对不对
25+
* 对的,只要记住springboot2 默认选择了cglib,也可以通过配置修改,spring全版本默认接口走jdk的代理,而一般项目都要使用接口解藕。
26+
*/
27+
@Configuration
28+
@ComponentScan
29+
@EnableAspectJAutoProxy
30+
public class AppConfig {
31+
public static void main(String[] args) {
32+
ApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
33+
MailService mailService = context.getBean(MailService.class);
34+
System.out.println(mailService.sendMail());
35+
}
36+
}
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
package com.yale.test.java.fanshe.proxy.cglib.spring;
2+
3+
import java.time.ZoneId;
4+
import java.time.ZonedDateTime;
5+
6+
import org.springframework.beans.factory.annotation.Autowired;
7+
import org.springframework.context.annotation.EnableAspectJAutoProxy;
8+
import org.springframework.stereotype.Component;
9+
10+
@Component
11+
public class MailService {
12+
13+
/*
14+
* 因为我们启用了AOP,用使用LoggingAspect对UserService这个目标了增强,所以Spring在往MailService类了注入UserService时
15+
* 不能注入原始的UserService类,而是注入Spring的代理类UserService$$EnhancerBySpringCGLIB,所以MailService这里的userService
16+
* 是Spring的代理类不是原始的UserService类了。
17+
*/
18+
@Autowired
19+
UserService userService;
20+
21+
public String sendMail() {
22+
//ZoneId zoneId = userService.zoneId;这行代码在Spring启用AOP之后会报错,不要直接访问类的成员变量
23+
/*
24+
* 这行代码无论启不启用Spring的AOP都不会报错
25+
* 无论注入的UserService是原始实例还是代理实例,getZoneId()都能正常工作,因为代理类会覆写getZoneId()方法,并将其委托给原始实例:
26+
* 注意到我们还给UserService添加了一个public+final的方法:
27+
* 如果在MailService中,调用的不是getZoneId(),而是getFinalZoneId(),又会出现NullPointerException,
28+
* 这是因为,代理类无法覆写final方法(这一点绕不过JVM的ClassLoader检查),该方法返回的是代理类的zoneId字段,即null
29+
* 实际上,如果我们加上日志,Spring在启动时会打印一个警告:
30+
* 10:43:09.929 [main] DEBUG org.springframework.aop.framework.CglibAopProxy - Final method [public final java.time.ZoneId xxx.UserService.getFinalZoneId()] cannot get proxied via CGLIB: Calls to this method will NOT be routed to the target instance and might lead to NPEs against uninitialized fields in the proxy instance.
31+
* 上面的日志大意就是,因为被代理的UserService有一个final方法getFinalZoneId(),这会导致其他Bean如果调用此方法,无法将其代理到真正的原始实例,从而可能发生NPE异常。
32+
* 因此,正确使用AOP,我们需要一个避坑指南:
33+
* 访问被注入的Bean时,总是调用方法而非直接访问字段;
34+
* 编写Bean时,如果可能会被代理,就不要编写public final方法。
35+
* 这样才能保证有没有AOP,代码都能正常工作。
36+
* 思考
37+
* 问:为什么Spring刻意不初始化Proxy继承的字段?
38+
* 我自己的思考:1,规范问题,本来java的封装特性就不鼓励直接通过类名.属性 直接访问属性,而是要求把属性用private封装起来,提供get方法来访问
39+
* 2、因为你初始化的时候很可能会用到注入的其他类
40+
* @Component
41+
public class MailService {
42+
@Value("${smtp.from:xxx}")
43+
String mailFrom;
44+
45+
SmtpSender sender;
46+
47+
@PostConstruct
48+
public void init() {
49+
sender = new SmtpSender(mailFrom, ...);
50+
}
51+
52+
public void sentMail(String to) {
53+
...
54+
}
55+
}
56+
* 你看,MailService的字段sender初始化需要依赖其他注入,并且已经初始化了一次,proxy类没法正确初始化sender
57+
* 主要原因就是spring无法在逻辑上正常初始化proxy的字段,所以干脆不初始化,并通过NPE直接暴露出来
58+
* 问:如果一个Bean不允许任何AOP代理,应该怎么做来“保护”自己在运行期不会被代理?
59+
* 答:将类设置为 final 的防止 cglib 创建Proxy,并且不继承接口防止 JDK 自带的动态代理
60+
* 小结
61+
* 由于Spring通过CGLIB实现代理类,我们要避免直接访问Bean的字段,以及由final方法带来的“未代理”问题。
62+
* 遇到CglibAopProxy的相关日志,务必要仔细检查,防止因为AOP出现NPE异常。
63+
*/
64+
ZoneId zoneId = userService.getZoneId();
65+
System.out.println("UserService的成员变量zoneId是用final修饰的,竟然为变成null?" + zoneId);
66+
System.out.println("原因是Spring的AOP动态代理搞的鬼,我们来看看userService的真身长什么样?" + userService.getClass());
67+
System.out.println("你可以把AppConfig类上面的@EnableAspectJAutoProxy去掉和加上分别运行一次,看看userService的真身");
68+
String dt = ZonedDateTime.now(zoneId).toString();
69+
return " Hello, it is " + dt;
70+
}
71+
}

0 commit comments

Comments
 (0)