9. 由 Spring 管理的对象的生命周期
如果需要管理 Bean 的生命周期,可以在对应的类中自定义生命周期的初始化方法和销毁方法,关于这 2 个方法的声明:
应该使用public
权限;
使用void
表示返回值类型;
方法名称可以自定义;
参数列表为空。
例如:
package cn.haiyong.spring;
public class User {
public User() {
System.out.println("User.User()");
}
public void init() {
System.out.println("User.init()");
}
public void destroy() {
System.out.println("User.destroy()");
}
}
复制代码
在配置 Spring 管理对象的@Bean
注解中,配置注解参数,以指定以上 2 个方法分别是初始化方法和销毁方法:
package cn.haiyong.spring;
@Configuration
public class BeanFactory {
@Bean(initMethod = "init", destroyMethod = "destroy")
public User user() {
return new User();
}
}
复制代码
最终,可以看到:
10. 使用组件扫描使得 Spring 管理类的对象
首先,自定义某个类(类名、包名均没有要求),在类的声明之前添加@ComponentScan
注解,该注解用于配置组件扫描,注解的参数是String
类型的,表示“被扫描的根包”:
package cn.haiyong.spring;
import org.springframework.context.annotation.ComponentScan;
@ComponentScan("cn.haiyong.spring")
public class SpringConfig {
}
复制代码
在组件扫描的包下创建类,该类的声明之前需要添加@Component
注解,以表示这个类是一个“组件类”,后续,当 Spring 扫描时,会自动创建所有组件类的对象:
package cn.haiyong.spring;
import org.springframework.stereotype.Component;
@Component
public class User {
}
复制代码
当完成以后配置后,后续,程序执行时,只要加载了SpringConfig
类,由于类之前配置了组件扫描,Spring 框架就会扫描对应的包下所有的类,并逐一检查是否为“组件类”,如果是,则创建对象,如果不是,则不创建!
使用@ComponentScan
时,配置的是需要扫描的“根包”,假设需要扫描的是cn.haiyong.spring
,在配置时,配置为cn.haiyong
甚至配置为cn
都是可用的,但是,强烈不推荐使用过于简单的设置,避免出现扫描范围过多而导致的浪费资源!
另外,在@ComponentScan
注解的源代码中:
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Repeatable(ComponentScans.class)
public @interface ComponentScan {
@AliasFor("basePackages")
String[] value() default {};
@AliasFor("value")
String[] basePackages() default {};
}
复制代码
可以看出,配置的值可以是String[]
,也就是可以指定多个包名。
在使用这种做法时,必须保证被 Spring 管理的对象所归属的类存在无参数构造方法!
在使用这种做法时,Spring 创建对象后,默认会使用以下原则作为 Bean 的名称:
如果希望使用自定义的名称作为 Bean 的名称,可以在@Component
注解中配置参数,例如:
package cn.haiyong.spring;
import org.springframework.stereotype.Component;
@Component("uuu")
public class User {
}
复制代码
则后续调用getBean()
方法时,就必须使用"uuu"
作为参数来获取对象!
在 Spring 框架的作用范围内,除了@Component
以外,另外还有 3 个注解,可以起到完全等效的效果:
也就是说,这 4 种注解作用、用法完全相同,只是语义不同。
目前,已经介绍了 2 种使得 Spring 框架管理类的对象的做法:
以上的第 1 种做法是万能的,适用于任何条件,但是,在设计代码时相对麻烦,管理起来相对不便利;而第 2 种做法就更加简单、直观,却只适用于自定义的类。
所以,只要是自行编写的类,都应该采取第 2 种做法,如果需要 Spring 管理其它类(JDK 中的,或某框架中的)的对象,只能使用第 1 种做法!
11. 使用 Spring 读取.properties 文件
假设在项目的 src/main/resources 下存在 jdbc.properties 文件,其内容是:
url=jdbc:mysql://localhost:3306/db_name
driver=com.mysql.jdbc.Driver
复制代码
然后,在项目中,自定义某个类,在这个类中,声明对应数量的属性,这些属性的值将会是以上配置信息的值!
public class JdbcProperties {
private String url;
private String driver;
// 生成以上2个属性的Getters & Setters
}
复制代码
当需要读取以上 jdbc.properties 配置文件时,需要在以上类的声明之前添加@PropertySource
注解,并配置需要读取的文件的位置:
// 以下注解的参数是配置文件的名称
@PropertySource("jdbc.properties")
public class JdbcProperties {
private String url;
private String driver;
// 生成以上2个属性的Getters & Setters
}
复制代码
接下来,就可以把读取到的值赋值给类中的 2 个属性,可以通过@Value
注解来实现:
// 以下注解的参数是配置文件的名称
@PropertySource("jdbc.properties")
public class JdbcProperties {
@Value("${url}") // 在注解参数的大括号的值,是jdbc.properties配置中等于号左侧的名称
private String url;
@Value("${driver}")
private String driver;
// 生成以上2个属性的Getters & Setters
}
复制代码
最后,整个的读取过程是由 Spring 框架来完成的,所以,以上JdbcProperties
类还应该被 Spring 框架所管理,可以采取组件扫描的做法,则创建SpringConfig
类,用于指定组件扫描的包:
// 以下注解参数配置的就是组件扫描的包,同时,请保证JdbcProperties类是在这个包或其子孙包中的
@ComponentScan("cn.haiyong.spring")
public class SpringConfig {
}
复制代码
然后,在JdbcProperties
类的声明之前,补充添加@Component
注解,使得 Spring 框架扫描到这个类时,能明确的知道“这个类是组件类”,从而创建该类的对象:
@Component
// 以下注解的参数是配置文件的名称
@PropertySource("jdbc.properties")
public class JdbcProperties {
@Value("${url}") // 在注解参数的大括号的值,是jdbc.properties配置中等于号左侧的名称
private String url;
@Value("${driver}")
private String driver;
// 生成以上2个属性的Getters & Setters
}
复制代码
全部完成后,可以自定义某个类,用于测试运行:
package cn.haiyong.spring;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
public class SpringTests {
public static void main(String[] args) {
// 1. 加载配置类,得到Spring容器
AnnotationConfigApplicationContext ac
= new AnnotationConfigApplicationContext(SpringConfig.class);
// 2. 从Spring容器中获取对象
JdbcProperties jdbcProperties
= (JdbcProperties) ac.getBean("jdbcProperties");
// 3. 测试
System.out.println(jdbcProperties.getUrl());
System.out.println(jdbcProperties.getDriver());
// 4. 关闭
ac.close();
}
}
复制代码
注意:在类似于 jdbc.properties 这样的配置文件中,如果某个属性的名称是username
,且最终项目是在 Windows 操作系统的平台上运行时,读取到的值将是“当前登录 Windows 系统的系统用户名称”,而不是 jdbc.properties 文件中配置的属性值!所以,一般推荐在编写 jdbc.properties 这类配置文件时,各属性之前最好都添加一些特有的前缀,使得属性名一定不与某些关键名称发生冲突,例如:
project.jdbc.url=jdbc:mysql://localhost:3399/db_name
project.jdbc.driver=com.mysql.jdbc.Driver
project.jdbc.username=root
project.jdbc.password=1234
复制代码
并且,在使用@Value
注解时,也配置为以上各个等于号左侧的完整名称:
@Component
@PropertySource("jdbc.properties")
public class JdbcProperties {
@Value("${project.jdbc.url}")
private String url;
@Value("${project.jdbc.driver}")
private String driver;
@Value("${project.jdbc.username}")
private String username;
@Value("${project.jdbc.password}")
private String password;
// Getters & Setters
}
复制代码
最后,使用 Spring 框架时,如果属性的值是由 Spring 框架进行赋值的,Spring 框架会自动的处理数据类型的转换,所以,在声明属性时,声明为所期望的类型即可,例如,在配置文件中存在:
project.jdbc.initialSize=5
project.jdbc.maxTotal=20
复制代码
这 2 个属性分别表示“初始化连接数”和“最大连接数”,应该是数值类型的,在类中声明属性时,就可以使用int
或Integer
类型:
@Value("${project.jdbc.initialSize}")
private int initialSize;
@Value("${project.jdbc.maxTotal}")
private int maxTotal;
复制代码
当然,必须保证类型的转换是可以成功的,例如数字5
既可以转换为String
,又可以是int
或Integer
,所以,声明以上initialSize
时,这几个数据类型都是可用的,根据使用需求进行选取即可!
另外,还有另一种做法读取**.properties**类型的文件,就是使用@Autowired
注解为Environment
类型的属性自动赋值:
@Component
@PropertySource("jdbc.properties")
public class JdbcProperties {
@Autowired
private Environment environment;
public Environment getEnvironment() {
return environment;
}
public void setEnvironment(Environment environment) {
this.environment = environment;
}
}
复制代码
最终,测试运行:
package cn.haiyong.spring;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
public class SpringTests {
public static void main(String[] args) {
// 1. 加载配置类,得到Spring容器
AnnotationConfigApplicationContext ac
= new AnnotationConfigApplicationContext(SpringConfig.class);
// 2. 从Spring容器中获取对象
JdbcProperties jdbcProperties
= (JdbcProperties) ac.getBean("jdbcProperties");
// 3. 测试
System.out.println(jdbcProperties.getEnvironment().getProperty("project.jdbc.url"));
System.out.println(jdbcProperties.getEnvironment().getProperty("project.jdbc.driver"));
System.out.println(jdbcProperties.getEnvironment().getProperty("project.jdbc.username"));
System.out.println(jdbcProperties.getEnvironment().getProperty("project.jdbc.password"));
System.out.println(jdbcProperties.getEnvironment().getProperty("project.jdbc.initialSize"));
System.out.println(jdbcProperties.getEnvironment().getProperty("project.jdbc.maxTotal"));
// 4. 关闭
ac.close();
}
}
复制代码
可以看到,使用这种做法时,Spring 框架会把读取到的所有配置信息都封装到了Environment
类型的对象中,当需要获取某个配置值时,调用Environment
对象的getProperty()
方法再获取,同时,getProperty()
方法返回的是String
类型的数据,如果希望的数据类型不是String
,则需要开发人员自行转换类型!
一般,还是推荐使用@Value
注解逐一读取各配置值,使用起来更加灵活一些!
抽象类与接口的区别
1. 共同点
都可以包含抽象方法;
2. 区别
抽象类是一种“类”,是使用class
作为关键字来声明的;而接口是另一种数据,是使用interface
作为关键字来声明的;
抽象类中可以有各种权限不同、修饰符不同的属性,也可以包含普通方法、抽象方法,或者完全没有普通方法,或者完全没有抽象方法;而接口中的所有成员都是public
的,所有属性都是static
、final
的,在 JDK 1.8 之前,所有的方法都是抽象的;
普通的类与抽象类的关系是“继承”的关系,当普通的类继承了抽象类后,就有义务重写抽象类中的抽象方法,在 Java 语句中,类之间的继承是 1 对 1 的关系;普通的类与接口的关系是”实现“的关系,当普通的类实现了接口后,也有义务重写接口中的所有抽象方法,类与接口的实现关系是 1 对多的,即 1 个类可以同时实现若干个接口;接口与接口之间也可以存在继承关系,且是 1 对多的关系,即某 1 个接口可以同时继承若干个接口;
3. 使用心得 / 装
类,是描述”类别“的;接口,是描述形为模式、行为特征、规范、标准的!
类与类之间是is a
的关系;类与接口之间是has a
的关系。
public class Person { public String name; }
public class Student extends Person {}
public class Teacher extends Person {}
public class Animal { }
public class Cat extends Animal {}
public interface 学习 { void 学习(某参数); }
public interface 授课 {}
public interface 驾驶 { void 驾驶(某参数); }
public class Person implements 学习, 授课, 驾驶 {}
Person 张三 = new Person();
Person 李四 = new Person();
复制代码
附 1:Eclipse 常用快捷键
评论