Spring学习总结
内容:Bean的创建、初始化两点内容
创建一个SpringBoot项目来测试一下Spring中的信息内容
Bean的创建:
1、@Configuration中的proxyBeanMethods
利用@Configuration来代替xml文件,表示的是一个配置类。
@Component
public @interface Configuration {
@AliasFor(
annotation = Component.class
)
String value() default "";
boolean proxyBeanMethods() default true;
}
可以看到注解@Configuration也是一个@Component,代表的是容器中的一个组件。value代表的是组件的名称,@Configuration和@Component都表示的是容器中的组件,默认的组件名称是类名首字母小写,也可以通过value来指定组件名称。
在spring5之后,在@Configuration中引入了一个新的属性:proxyBeanMethods,代表bean方法。
表示的是从@Configuration标注的类中来获取得到对应的组件,那么是什么意思?首先举例子来说明一下:
1、从上下文中获取得到当前Spring的容器对象
@Component
public class SpringContextConfig implements ApplicationContextAware {
private static ApplicationContext applicationContext;
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
// 静态变量成员是属于类的,最好这么来进行设置,否则就是对象的属性了!以前没有注意的一点
SpringContextConfig.applicationContext=applicationContext;
}
/**
* 根据类型来获取得到容器中的组件
* example: SpringContextConfig.getBeanForClassType(User.class)
* @param clazz
* @param <T>
* @return
*/
public static <T> T getBeanForClassType(Class<T> clazz){
return SpringContextConfig.applicationContext.getBean(clazz);
}
/**
* 根据类型来获取得到容器中组件
* example: SpringContextConfig.getBeanForName("user")
* @param beanName
* @param <T>
* @return
*/
public static <T> T getBeanForName(String beanName){
return CastUtil.cast(SpringContextConfig.applicationContext.getBean(beanName));
}
}
2、先写两个类:
@Data
public class Cat {
private Integer catId;
private String catName;
}
@Data
public class User {
private Integer userId;
private String username;
private Cat cat;
}
3、定义配置类
@Configuration
public class MyConfig {
@Bean
public Cat cat(){
Cat cat = new Cat();
cat.setCatId(22);
cat.setCatName("cat");
return cat;
}
@Bean
public User user(){
User user = new User();
user.setCat(cat());
return user;
}
}
4、进行测试
@Test
void contextLoads() {
User user1 = SpringContextConfig.getBeanForClassType(User.class);
Cat user1Cat = user1.getCat();
Cat cat = SpringContextConfig.getBeanForName("cat");
System.out.println(user1Cat==cat);
}
可以看到控制台输出为:
true
为什么这里是true?首先看一下@Configuration对象中的proxyBeanMethods属性:
boolean proxyBeanMethods() default true;
也就是说,当想从容器上下文中获取得到组件的时候,Spring容器首先检查容器中是否有这个组件,如果有这个组件,然后再去获取得到组件依赖的组件,如果当前的容器中有组件依赖的组件,那么会从容器中来进行获取。那么也就是下面这个样子:
所以如果想让User组件不依赖容器中的Cat类型的组件的处理方式是在@Configuration添加proxyBeanMethods = false
@Configuration(proxyBeanMethods = false)
那么再来进行上面的测试:
false
也就是说proxyBeanMethods为true的时候,表示从容器中来进行获取;为FALSE的时候,表示自己建立一个新的对象,然后给对象来进行赋值。
当然,这样子来进行操作,可能会导致容器中的对象过多。
最佳实践:根据需求来进行自定义操作
2、@ComponentScan扫描包
作用:利用@ComponentScan来扫描包,使得包下面的哪些类成为组件。默认扫描的是
在SpringBoot中,默认的是启动类所在的包及其子包下面的类,当然我们这里可以来进行自定义操作。
@Repeatable(ComponentScans.class)
public @interface ComponentScan {
@AliasFor("basePackages")
String[] value() default {};
@AliasFor("value")
String[] basePackages() default {};
Class<?>[] basePackageClasses() default {};
Class<? extends BeanNameGenerator> nameGenerator() default BeanNameGenerator.class;
Class<? extends ScopeMetadataResolver> scopeResolver() default AnnotationScopeMetadataResolver.class;
ScopedProxyMode scopedProxy() default ScopedProxyMode.DEFAULT;
String resourcePattern() default "**/*.class";
boolean useDefaultFilters() default true;
ComponentScan.Filter[] includeFilters() default {};
ComponentScan.Filter[] excludeFilters() default {};
boolean lazyInit() default false;
@Retention(RetentionPolicy.RUNTIME)
@Target({})
public @interface Filter {
FilterType type() default FilterType.ANNOTATION;
@AliasFor("classes")
Class<?>[] value() default {};
@AliasFor("value")
Class<?>[] classes() default {};
String[] pattern() default {};
}
}
属性介绍:
value | String[] | 使用方式同basePackages | |
---|---|---|---|
basePackages | String[] | 使用方式同value | |
basePackageClasses | Class<?>[] | 基于类来今天组件 | |
includeFilters | ComponentScan.Filter[] | 使用过滤器将哪些组件添加到容器中来。默认会将在类上标注了@Component @Repository, @Service, @Controller注解的类添加到容器中来 | |
excludeFilters | ComponentScan.Filter[] | 使用过滤器将哪些排除 |
可以看到在类中的过滤器Filter注解。这里强调的是过滤规则,哪些可以到容器中来,哪些不可以添加到容器中来
value | 基于类型 | Class<?>[] |
---|---|---|
classes | 基于类型 | Class<?>[] |
pattern | 基于规则 | String[] |
FilterType | 默认按照注解 | 默认按照注解的方式将其扫描组件添加到容器中来 |
下面来通过例子说明:
新建三个类:
@Controller
public class HelloController {
}
public class Animal {
}
public class Person {
}
然后在主配置类上修改如下:
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(
excludeFilters = {@ComponentScan.Filter(
type = FilterType.CUSTOM,
classes = {TypeExcludeFilter.class}
), @ComponentScan.Filter(
type = FilterType.CUSTOM,
classes = {AutoConfigurationExcludeFilter.class}
),
@ComponentScan.Filter(
type = FilterType.ASSIGNABLE_TYPE,
classes = {Animal.class}
)
},
includeFilters = {
@ComponentScan.Filter(
type = FilterType.ASSIGNABLE_TYPE,
classes = {Person.class}
)
}
)
public class SpringOneApplication {
public static void main(String[] args) {
ApplicationContext applicationContext = SpringApplication.run(SpringOneApplication.class, args);
for (String beanDefinitionName : applicationContext.getBeanDefinitionNames()) {
System.out.println(beanDefinitionName);
}
}
}
因为@SpringbootApplication是一个组合注解:
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan
那么我们可以用这三个注解来代替@SpringbootApplication。然后在上面指定我们自己的扫描规则:
excludeFilters = {@ComponentScan.Filter(
type = FilterType.CUSTOM,
classes = {TypeExcludeFilter.class}
), @ComponentScan.Filter(
type = FilterType.CUSTOM,
classes = {AutoConfigurationExcludeFilter.class}
),
@ComponentScan.Filter(
type = FilterType.ASSIGNABLE_TYPE,
classes = {Animal.class}
)
},
要将哪些类型的组件进行排除掉。
FilterType.CUSTOM表示自定义的类型不要添加到容器中来;
FilterType.ASSIGNABLE_TYPE表示指定哪些类类型的组件不要添加到容器中来;
includeFilters = {
@ComponentScan.Filter(
type = FilterType.ASSIGNABLE_TYPE,
classes = {Person.class}
)
}
指定哪些将会扫描进容器当中来:
查看控制台:
myConfig
springContextConfig
helloController
person
cat
user
因为将Animal类型的组件排除掉了,所以容器中不会有这个类型的组件。
这里可以看到尽管是自定义,但是还是会将@Component @Repository, @Service, @Controller注解的类添加到容器中来。因为在注解上已经说明了
/**
* Indicates whether automatic detection of classes annotated with {@code @Component}
* {@code @Repository}, {@code @Service}, or {@code @Controller} should be enabled.
*/
boolean useDefaultFilters() default true;
因为这里为true,默认会将其添加进来。默认扫描的是启动类所在的路径。
那么当想修改整个扫描路径的时候,整个时候需要将这个设置为false。(但是一般不会这么来进行操作,因为开发规范)
下面来演示一下:
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(
useDefaultFilters = false,
basePackages = {"com.gaung"},
excludeFilters = {@ComponentScan.Filter(
type = FilterType.CUSTOM,
classes = {TypeExcludeFilter.class}
), @ComponentScan.Filter(
type = FilterType.CUSTOM,
classes = {AutoConfigurationExcludeFilter.class}
),
@ComponentScan.Filter(
type = FilterType.ASSIGNABLE_TYPE,
classes = {Animal.class}
)
},
includeFilters = {
@ComponentScan.Filter(
type = FilterType.ASSIGNABLE_TYPE,
classes = {Person.class}
)
}
)
public class SpringOneApplication {
public static void main(String[] args) {
ApplicationContext applicationContext = SpringApplication.run(SpringOneApplication.class, args);
for (String beanDefinitionName : applicationContext.getBeanDefinitionNames()) {
System.out.println(beanDefinitionName);
}
}
}
但是在控制台上查看的时候,发现已经没有了我们的@Service、@Controller和@Configuration注解标注的类的组件了。
因为将useDefaultFilters 修改成false之后,将默认不在会去扫描@Component @Repository, @Service, @Controller这些注解标注的类了。
注:为什么没有myConfig组件了,因为@Configuration是一个@Component,所以禁用掉之后不会扫描。
那么为了将这些注解标注的类给扫描进容器中来,还需要在includeFilters中来进行添加,如下所示:
includeFilters = {
@ComponentScan.Filter(
type = FilterType.ASSIGNABLE_TYPE,
classes = {Person.class}
),
@ComponentScan.Filter(
type = FilterType.ANNOTATION,
classes = {Controller.class, Service.class, Repository.class, Component.class}
)
}
添加之后,然后去控制台查看:
userServcie
myConfig
springContextConfig
helloController
person
cat
user
我们最常用的又可以看到了。但是我们一般开发都会在启动类所在包及其子包下面来进行建立包进行开发。
所以还是不要来违反这个在启动类所在包及其子包之外来建立包。
最后多来介绍一个这个过滤规则
includeFilters = {
@ComponentScan.Filter(
type = FilterType.ASSIGNABLE_TYPE,
classes = {Person.class}
)
}
这个注解将会把指定类型的子类或者是实现类也会添加到容器中来。测试一下:
首先建立接口、抽象类和其子类:
public interface MyServlet {
String HelloWorld();
}
public abstract class AbstractMyServlet implements MyServlet{
}
public class MySql extends AbstractMyServlet{
@Override
public String HelloWorld() {
System.out.println("mysql..............");
return "mysql";
}
}
public class Tom extends AbstractMyServlet{
@Override
public String HelloWorld() {
System.out.println("tom...........");
return "hello";
}
}
然后在启动类上添加配置:
includeFilters = {
@ComponentScan.Filter(
type = FilterType.ASSIGNABLE_TYPE,
classes = {Person.class}
),
@ComponentScan.Filter(
type = FilterType.ANNOTATION,
classes = {Controller.class, Service.class, Repository.class, Component.class}
),
@ComponentScan.Filter(
type = FilterType.ASSIGNABLE_TYPE,
classes = {MyServlet.class}
)
}
指定MyServlet.class类型的添加到容器中来。查看控制台:
mySql
tom
因为抽象类不可以进行实例化,所以没有这个组件。
在这里的实际开发中的作用是,我定义了一个接口,在接口和抽象类中已经定义好了对应的方法,那么子类只需要重写对应的方法,但是因为怕后来开发者不按照归来进行实现(如:不加上@Component注解,只是继承一下抽象类或者是去实现接口),那么我直接指定这个类的组件全部都添加进行即可。
那么怎么来获取得到这个接口或者是这个类型的组件呢?
可以通过容器对象BeanFactory提供的一个方法来进行获取:
public class SpringOneApplication {
public static void main(String[] args) {
ApplicationContext applicationContext = SpringApplication.run(SpringOneApplication.class, args);
Map<String, MyServlet> beansOfType = applicationContext.getBeansOfType(MyServlet.class);
beansOfType.forEach((k,v)-> System.out.println("bean的ID是"+k+",对应的组件是:"+v));
}
}
查看控制台:
bean的ID是mySql,对应的组件是:com.gaung.ziorimplement.MySql@4cfce6bd
bean的ID是tom,对应的组件是:com.gaung.ziorimplement.Tom@263fd558
3、@Autowired
直接去查看源信息:
@Target({ElementType.CONSTRUCTOR, ElementType.METHOD, ElementType.PARAMETER, ElementType.FIELD, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Autowired {
boolean required() default true;
}
可以作用在方法上、构造函数上、参数上、成员变量上和注解上。
看到required默认为true,那么表示这一定要有对应的组件,才会从容器上来进行注入到。
我们最常见的方式就是:
@Autowired
private UserServcie userService;
但是这种使用方式在Spring5之后,是不推荐使用的
最佳使用方式是:
public interface HelloService {
}
@Service
public class HelloServiceImpl implements HelloService {
}
@Controller
@AllArgsConstructor
public class HelloController {
private final HelloService helloService;
public void sayHelloServie(){
System.out.println(helloService);
}
}
这里需要注意的是,当当前类是容器中的组件的时候,如果类中只有一个有参构造函数,那么当前类的成员属性都将会从容器中获取得到。
这也是为什么加了@AllArgsConstructor的目的。现在开发几乎都是在使用这种方式来进行操作。
这里是@Autowired的隐式用法,省略了而已。
同样省略的还有:
@Bean(value = "userDao3")
public UserDao userDao3(Boss boss){
System.out.println("容器中的boss是---------------->>>>>>"+boss);
System.out.println("@Bean注解的方法参数上的参数会从容器中来进行获取---------------"+boss);
UserDao userDao = new UserDao();
userDao.setLabel(666);
return userDao;
}
在@Bean注解的方法上如果有组件,那么会从容器中来进行获取。所以使用起来非常方便。
首先经过类型判断,如果有多个类型的值,那么再来通过bean的ID来进行注入;
4、创建 Bean
创建bean的方式不止上面的注解方式@Controller、@Servcie、@Component和@Configuration或者是@Bean几种注解方式而已。
还可以通过@Import注解及其衍生注解(ImportSelector和ImportBeanDefinitionRegistrar接口)来进行创建bean。
下面来演示一下:
public class Blank {
}
然后在配置上进行配置@Import注解:
@Configuration
@Import(value = {Blank.class})
public class MyConfig {
查看控制台打印出来的bean:
com.gaung.service.Blank
对应的bean的ID默认是权限定类名
那么再看一下其他方式:
ImportSelector使用方式:https://blog.csdn.net/elim168/article/details/88131614
ImportBeanDefinitionRegistrar使用方式:https://elim168.blog.csdn.net/article/details/88131712
还有一个FactoryBean来进行实现的方式,需要注意的是根据&来获取得到原类型还是具体的类型。对应链接:https://blog.csdn.net/zknxx/article/details/79572387
看评论区有对应的实例:org.mybatis.spring.mapper.MapperFactoryBean#getObject
4、Bean的初始化
从上面的@Bean中,可以看到@Bean注解中的属性:
String initMethod() default "";
String destroyMethod() default AbstractBeanDefinition.INFER_METHOD;
所以我们可以在bean所在的类中直接指定对应的初始化方法和销毁方法。
下面来通过案例来进行演示一下:
public class Elepont {
public void init(){
System.out.println("开始对Elepont进行初始化操作...........");
}
public void destory(){
System.out.println("开始对Elepont进行销毁操作........");
}
}
然后在配置类中来进行配置:
@Configuration
public class MyConfig {
@Bean(initMethod = "init",destroyMethod = "destory")
public Elepont elepont(){
Elepont elepont = new Elepont();
return elepont;
}
}
那么启动查看控制台:
开始对Elepont进行初始化操作...........
可以看到初始化方法执行了,那么销毁方法什么时候执行?在容器销毁的时候:
applicationContext.close();
会调用bean的初始化方法。
但是这里需要只有单例bean才会被容器进行管理。也就是说单例bean的初始化和销毁方法都是由容器来进行管理的。
而对于多例bean来说,容器只帮忙来进行创建,至于什么时候销毁,容器是不会来进行管理的。这点需要注意。
至于懒加载也是相当于单例bean来说的,对于单例bean来说,在容器启动的时候不会加载到容器中去,而是实现需要使用到bean的时候才会来创建bean对象到容器中来。
当然bean的初始化并不止这一种方式。先写到这里,有时间再来进行更新。