没有出现NPE异常
Topic source我测试了下,常量(final修饰的基本类型,包括特殊的String)应该是比较特殊的成员变量,看字节码文件应该是在声明的时候初始化。除此以外成员变量,应该是像老师所说在构造器中初始化。我在想是不是隐含了static,类似静态变量会跟类一起初始化。
所以,在AOP代理模式下,用bean类名.常量
,可以正常访问,相当于全局变量。
---
p.s. 常量要求在声明或构造器中初始化,不然编译器报错,不像普通成员变量可以有初始值。
看了下cglib的源码,生成的代理类的构造方法里面是会去调用super()方法的了,现在的版本好像不存在这个问题。即便不是String类型的,也可以访问得到。
private void emitConstructors(ClassEmitter ce, List constructors) { boolean seenNull = false; // 遍历持有的构造方法对应的MethodInfo的集合 for (Iterator it = constructors.iterator(); it.hasNext();) { MethodInfo constructor = (MethodInfo)it.next(); // 如果currentData不为null 并且 当前构造方法的描述符是()V,直接跳过,进行下一次循环 if (currentData != null && !"()V".equals(constructor.getSignature().getDescriptor())) { continue; } // 否则根据MethodInfo向类中声明一个public的构造方法 CodeEmitter e = EmitUtils.begin_method(ce, constructor, Constants.ACC_PUBLIC); // 向构造方法中添加aload_0字节码,加载this引用到操作数栈顶 e.load_this(); // 插入dup字节码,复制栈顶元素 e.dup(); // 加载方法参数到操作数栈顶 e.load_args(); // 获取构造方法的方法签名 Signature sig = constructor.getSignature(); // 判断方法签名的描述符是否是()V,如果是,给seenNull赋值为true,表示访问过参数为null的构造方法 seenNull = seenNull || sig.getDescriptor().equals("()V"); // 然后向code中插入invokespecial字节码,调用父类的构造器 e.super_invoke_constructor(sig); // 如果currentData为null if (currentData == null) { // 插入invokestatic字节码,调用CGLIB$BIND_CALLBACKS(Object)方法, // 即调用静态方法将this作为参数传入,进行callbacks的绑定 e.invoke_static_this(BIND_CALLBACKS); // 如果interceptDuringConstruction为false的话 if (!interceptDuringConstruction) { // 继续向code中插入aload_0字节码 e.load_this(); // 然后给自身的CGLIB$CONSTRUCTED属性赋值为true e.push(1); e.putfield(CONSTRUCTED_FIELD); } } // 向code中插入return字节码,表示方法返回 e.return_value(); e.end_method(); } // 如果classOnly为false 并且 没有发现有无参构造器 并且 参数为null,报错 if (!classOnly && !seenNull && arguments == null) throw new IllegalArgumentException("Superclass has no null constructors but no arguments were given");}
我从堆里面去查找UserService类型对象,结果发现两个,一个是UserService,一个是UserService$$EnhanceBySpringCglib$$xxx
**UserService{"zoneId":"Asia/Shanghai"}
**
UserService$$EnhanceBySpringCglib$$xxx{"zoneId":null, "target":{"zoneId":"Asia/Shanghai"}}
其中UserService对象的zoneId是有值的,而UserService$$EnhanceBySpringCglib$$xxx对象的zoneId是null
debug发现注入对象是UserService$$EnhanceBySpringCglib$$xxx,因此userService.zonId为null是正常的,而userService.getZoneId()能够正常返回,是因为他实际调用的是target.getZoneId()。
源码也可以证明:spring会先创建目标对象,再把目标对象注入到代理对象中,虽然代理对象继承了目标对象,但他们之间的关系本质还是组合。
综上,为什么Spring不会初始化代理对象父类?就是因为代理对象可以访问到两个zoneId变量,但只需要为其中一个进行赋值,而Spring选择给target赋值,也就不需要对代理类的父类进行初始化了。
- 1
净净一隅
MailService 装配了AOP,有一个public final变量test;UserSerivce注入了MailService,在UserSerivce里可以正常使用test变量
是版本问题?
1. MailService
Component
public class MailService {
@Autowired(required = false)
private ZoneId zoneId = ZoneId.systemDefault();
@Value("#{smtpConfig.smtpHost}")
private String smtpHost;
@Value("#{smtpConfig.smtpPort}")
private int smtpPort;
public final String test="aaa";
public void setZoneId(ZoneId zoneId) {
this.zoneId = zoneId;
}
public String getTime() {
return ZonedDateTime.now(this.zoneId).format(DateTimeFormatter.ISO_ZONED_DATE_TIME);
}
public void sendLoginMail(User user) {
System.out.println(String.format("Hi, %s! You are logged in at %s", user.getName(), getTime()));
}
public void sendRegistrationMail(User user) {
System.out.println(String.format("Welcome, %s!", user.getName()));
}
@PostConstruct
public void init() {
System.out.println("Init mail service " + this.zoneId);
System.out.println("host: "+this.smtpHost);
System.out.println("port: "+this.smtpPort);
}
@PreDestroy
public void shutdown() {
System.out.println("Shutdown mail service");
}
}
2. UserService
package com.service;
import com.annotation.MetricTime;
import com.entity.User;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
@Component
public class UserService {
@Autowired
private MailService mailService;
@Autowired
private DataSource dataSource;
public UserService(){
System.out.println("创建UserService bean");
}
public void setMailService(MailService mailService) {
this.mailService = mailService;
}
public User getUserFromDB(String email,String password) {
try(Connection conn=this.dataSource.getConnection()){
String sql="select id,name from user where email=? and password=? limit 1";
System.out.println(sql);
PreparedStatement ps = conn.prepareStatement(sql);
ps.setObject(1, email);
ps.setObject(2, password);
try(ResultSet rs=ps.executeQuery()){
while (rs.next()) {
long id = rs.getLong("id");
String name = rs.getString("name");
return new User(id,email,password,name);
}
}
}catch (SQLException e){
e.printStackTrace();
return null;
}catch (Exception e){
e.printStackTrace();
return null;
}
return null;
}
public User getUserFromDB(String email) throws SQLException {
try(Connection conn=this.dataSource.getConnection()){
String sql="select id,email,name from user where email=?";
System.out.println(sql);
PreparedStatement ps = conn.prepareStatement(sql);
ps.setObject(1, email);
try(ResultSet rs=ps.executeQuery()){
while (rs.next()) {
long id = rs.getLong("id");
String name = rs.getString("name");
User user=new User();
user.setName(name);
user.setId(id);
user.setEmail(email);
return user;
}
}
}
return null;
}
@MetricTime("login")
public User login(String email, String password) {
User user=getUserFromDB(email,password);
if(user!=null){
mailService.sendLoginMail(user);
}
return user;
}
public User registerUser2DB(User user) throws SQLException{
System.out.println(mailService.test);
try(Connection conn=this.dataSource.getConnection()) {
String sql="insert into user(name,email,password) values(?,?,?)";
PreparedStatement ps = conn.prepareStatement(sql);
ps.setObject(1, user.getName());
ps.setObject(2,user.getEmail());
ps.setObject(3,user.getPassword());
int rs=ps.executeUpdate();
if(rs>0){
return user;
}
}
return null;
}
public User register(String email, String password, String name) throws SQLException{
if(getUserFromDB(email)!=null){
throw new RuntimeException("email exist.");
}
User user=new User();
user.setEmail(email);
user.setName(name);
user.setPassword(password);
this.registerUser2DB(user);
mailService.sendRegistrationMail(user);
System.out.println("mailService.test: "+this.mailService.test);
return user;
}
public DataSource getDataSource() {
return dataSource;
}
public void setDataSource(DataSource dataSource) {
this.dataSource = dataSource;
}
}
3. LoggingAspect
@Aspect
@Component
public class LoggingAspect {
@Before("execution(public * com.service.MailService.*(..))")
public void doAcessCheck(){
System.err.println("[Before] do access check...");
}
}
4. AppConfig 测试
@Configuration
@ComponentScan
@EnableAspectJAutoProxy
public class AppConfig {
public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, IllegalAccessException, InstantiationException, SQLException {
ApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
//MySqlConfig config= AppConfig.context.getBean(MySqlConfig.class);
UserService userService= context.getBean(UserService.class);
userService.register("Jimmy@123.com","123","Jimmy");
}
5. 运行日志
select id,email,name from user where email=?
aaa
[Before] do access check...
Welcome, Jimmy!
mailService.test: aaa