spring实战第四版01
spring自带了很多容器的实现,,总的分为两类:
- bean工厂 : 基本的DI支持
- ApplicationContext: 应用上下文,,应用框架级别的服务,,有更多的特性,比如
属性文本解析
,事件发布
- AnnotationConfigApplicationContext
- AnnotationConfigWebApplicationContext
- ClassPathXmlApplicationContext
- FileSystemXmlApplication
- XmlWebApplicationContext
spring生命周期
- 实例化
- 填充属性
- 调用BeanNameAware的setBeanName()方法,,如果bean实现了BeanNameAware,,那么spring就会将bean的id传递给
setBeanName()
方法作为参数 - 调用BeanFactoryAware的setBeanFactory()方法,,,可以感知BeanFactory
- 调用ApplicationContextAware的setApplicationContext()方法,,,可以感知ApplicationContext
- 调用BeanPostProcessor的预初始化方法,,如果实现了这个接口,,就会去调他的
postProcessBeforeInitialization()
方法 - 调用InitializingBean的afterPropertiesSet()方法:spring将调用他们的
afterPropertiesSet()
方法,如果bean使用init-method
声明了初始化方法,该方法也会被调用 - 调用自定义初始化方法
init-method
- 调用BeanPostProcessor的初始后方法,,spring将调用
postProcessAfterInitialization
方法,初始化后方法 - bean可以使用了
- 容器关闭
- 调用Disposable的
destroy()
方法,,如果bean使用destroy-method
声明了销毁方法,同样也会调用 - 调用自定义销毁方法
destroy-method
- 结束
自动化装配
spring中可以自己注册bean,但是一般都是自动化装配的,,spring中的自动化装配有两种方式:
- 组件扫描 component scanning:spring会自动发现应用上下文中所创建的bean
- 自动装配 autowiring: spring会自动满足bean之间的依赖
组件扫描默认不启用: 需要配置@ComponentScan
。。或者xml配置<context:component-scan>
spring中测试
<dependency><groupId>org.springframework</groupId><artifactId>spring-test</artifactId><version>6.1.12</version></dependency>
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = TestConfig.class)
public class DemoTest {@AutowiredUser user;@Testpublic void test(){System.out.println("user = " + user);}
}
@ContextConfiguration(classes=TestConfig.class)
: 这个可以自定义测试的配置文件,手动指定xml/Java config,,主要用于传统的spring项目测试,,非boot@SpringBootTest
: 自动加载@SpringBootApplication
spring中bean的装配
bean的装配分为两种:
- 隐式的装配
- 显示的装配
隐式的装配
@ComponentScan的属性
- basePackage
- baskPackageClasses : 设置的数组中包含了类,,这些类所在的包,,会作为组件扫描的
基础包
,可以创建一个用来扫描的空标记接口(marker interface)
@Autowired
@Autowired
这个注解不仅能用在构造器上,还能用在setter方法上,,并且能用在类的任意方法
上,spring都会尝试满足方法参数上所声明的依赖,,,如果没有bean会报错,,如果不能确定唯一的bean也会报错,,,,
@Autowired
的属性:
- required : 设置为false,找不到不会报错,,但是在启动的时候就发现不了错误了,,执行到那一块代码可能会因为没有注入对象而空指针
@Autowired
和 @Inject
,在大多数场景下,,可以互换
@Inject
:这个注解来源于 Java依赖注入规范,,该规范同时还定义了@Named
注解,,
@Named
注解 和 @Component
,在大多数场景下,可以互换
但是这种打注解注入的方式,,注入不了第三方组件,,,就需要显示的装配,,,
显示的装配
显示的装配可以用java或者 xml来装配
xml装配bean,,如果不给id,,系统默认会使用全限定类名#0
,,就是全限定类名+序号,,如果是第一个就是#0
,如果有多个,,序号往后累加
@Primary
和 限定符Qualifier(想要注入bean的id)
Environment
PropertySourcesPlaceholderConfigurer
: 这个类的对象,能够基于spring environment及其属性源来解析占位符
@PropertySource("classpath:db.properties")
: 这个注解会将这个配置加载到spring的Environment中
@Configuration
// 这个属性文件会加载到spring的Environment中,,,
@PropertySource("classpath:db.properties")
public class ExpressiveConfig {@AutowiredEnvironment env;//?????@Value("#{systemProperties['db.username']}")private String username;@Value("#{T(System).currentTimeMillis()}")private Long time;@Beanpublic DataSource dataSource(){System.out.println("username = " + username);System.out.println("time = " + time);String url = env.getProperty("db.url");System.out.println("env.getProperty(\"db.url\") = " +url);Integer age = env.getProperty("user.age", Integer.class, 30);System.out.println("age = " + age);Integer age02 = env.getProperty("user.age02", Integer.class, 30);System.out.println("age02 = " + age02);// 检测某个属性是否存在boolean b = env.containsProperty("user.age02");System.out.println("b = " + b);// 返回激活的profileString[] activeProfiles = env.getActiveProfiles();// 默认的profileString[] defaultProfiles = env.getDefaultProfiles();System.out.println("activeProfiles = " + Arrays.toString(activeProfiles));System.out.println("defaultProfiles = " + Arrays.toString(defaultProfiles));// 是否支持某个profileboolean b1 = env.acceptsProfiles("prod");System.out.println("b1 = " + b1);DataSource ds = new DataSource();ds.setUrl(url);return ds;}
}
env.acceptsProfiles('prod')
: 检测环境中是否有 prod
的profile
profile多环境切换
spring.profiles.active
和 spring.profiles.default
,如果没有设置spring.profiles.active
那么spring会查找spring.profiles.default
的值,,,如果这两个都没有设置,那么spring只会创建那些没有定义在profile中的bean,,,,这两个属性都是复数,,可以激活多个profile,,,列出多个profile,逗号分隔
多种方式来设置这两个属性:
- 作为DispatcherServlet的初始化参数
- 作为web应用的上下文参数
- 作为JNDI条目
- 作为环境变量
- 作为JVM的系统属性
- 在集成测试类上,使用
@ActiveProfiles
注解设置
@Conditional
profile底层是@Conditional
,
public class MyCondition implements Condition {@Overridepublic boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {return false;}
}
这个方法里面的参数是:
-
ConditionContext
这个是个容器,,可以获取一些对象。比如:
- BeanDefinitionRegistry : 可以用来检查bean定义
- ConfigurableListableBeanFactory : 可以用来检查bean是否存在,检查bean的属性
- Environment : 可以用来检查环境变量是否存在,,以及环境变量对应的值
- ResourceLoader : 返回ResourceLoader所加载的资源
- ClassLoader : 可以用来加载并检查类是否存在
-
AnnotatedTypeMetaddata
: 能够让我们检查到,,带有@Bean注解的方法上,,还有什么其他的注解
scope的作用域
spring默认的作用域是singleton
,同一个实例,,但是有些时候,,你所使用的类是易变的mutable
,,他们可能会保持一些状态,,,,因此,重用是不安全的
scope作用域:
- 单例singleton : 整个应用中,只创建bean的一个实例
- 原型prototype : 每次注入或者通过spring应用上下文获取的时候,都会创建一个新的bean实例
- 会话session: 在web应用中,为每个会话创建一个bean实例
- 请求request: 在web应用中,为每个请求创建一个bean实例
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
@Scope("prototype")
商城中,如果购物车对象是单例的话,那么会导致所有用户都会向同一个购物车添加商品,,,另一方面,如果购物车是原型作用域,,那么在应用中某一个地方往购物车中添加商品,在应用的另一个地方可能就不可用了,,prototype总是获取一个新的bean
所以购物车用会话作用域,是最为合适的:
// proxyMode = ScopedProxyMode.INTERFACES : 这个属性解决了 将会话或者请求作用域的bean注入到单例bean中所遇到的问题
@Scope(value= WebApplicationContext.SCOPE_SESSION,proxyMode = ScopedProxyMode.INTERFACES)
为什么要添加这个???proxyMode = ScopedProxyMode.INTERFACES
,,因为spring默认是单例的,,如果一个类中@Autowired注入了这个 session作用域的bean,,,而session作用域的bean,只会在用户访问应用之后才会生成,,容器初始化的时候,是没有这个bean的,,所以会报错,,,配置了
proxyMode=ScopedProxyMode.INTERFACES
之后,,spring不会直接创建 ShoppingCart实例,,而是会注入一个 ShoppingCart的代理,,这个代理会暴露跟 ShoppingCart一样的方法,,,当用户访问系统后,,,代理会对其进行懒解析
,,,并将调用委托
给真正的ShoppingCart的Bean
如果这个ShoppingCart是接口就使用proxyMode=ScopedProxyMode.INTERFACES
如果这个ShoppingCart是一个具体的类,,spring就会使用cglib生成基于类的代理,,就使用proxyMode=ScopedProxyMode.TARGET_CLASS
,来表明要以生成目标类扩展的方式创建代理
spring的运行时求值
spring提供了两种在运行时求值的方式:
- 属性占位符 Property placeholder
- spring表达式语言 SpEL : spring expression language
spring表达式特性
- 使用bean的id来引用bean
- 调用方法和访问对象的属性
- 对值进行算数,关系,和逻辑运算
- 正则表达式匹配
- 集合操作
SpEL需要放到#{...}
之中,,属性占位符需要放到${...}
之中
用法:
#{beanId.属性名字}
: 获取指定id的bean的属性值#{systemProperties['user.age']}
: 通过systemProperties
对象引用系统属性T(java.lang.Math)
: 在SpEL中访问类作用域的方法和常量的话,,使用T()
,比如T(System).currentTimemillis()
使用场景:
- spring security的安全限制规则
- thymeleaf模板中使用SpEL表达式引用模型数据