持续总结更新中。。。
Spring IOC 容器
导入IOC依赖
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>4.3.12.RELEASE</version>
</dependency>
SpringFramework的手动装配(手动给容器注册组件的方式)
包扫描 + 组件标注注解(@Controller/@Service/@Repository/@Component)
在配置类(使用@Configuration标注的类)中加入@ComponentScan包扫描注解,这种方式只局限于自己实现的类,第三方类无法修改源码不能在类上加注解。
@Configuration //告诉Spring这是一个配置类
@ComponentScan(value="com.xxx")
public class MainConfig {
}
@Controller
public class BookController {
}
@Test
public void test01(){
AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(MainConfig.class);
String[] definitionNames = applicationContext.getBeanDefinitionNames();
for (String name : definitionNames) {
System.out.println(name);
}
}
使用@Configuration表明是一个配置类。 使用@ComponentScan扫描配置bean的包,@ComponentScan 有几个属性
- value:指定要扫描的包;
- excludeFilters = Filter[] :excludeFilters包含@Filter数组,指定扫描的时候按照什么规则排除哪些组件;
- includeFilters = Filter[] :includeFilters包含@Filter数组,指定扫描的时候只需要包含哪些组件;
- @Filter 注解
- FilterType.ANNOTATION:按照注解
- FilterType.ASSIGNABLE_TYPE:按照给定的类型;
- FilterType.ASPECTJ:使用ASPECTJ表达式,不常用
- FilterType.REGEX:使用正则指定,不常用
- FilterType.CUSTOM:使用自定义规则,需要写一个规则类实现TypeFilter接口,重写match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory)方法,方法返回true表示匹配。
- @Filter 注解
includeFilters属性需要使用属性useDefaultFilters = false,禁用默认过滤规则。可以使用 @ComponentScans指定多个@ComponentScan规则。
综合案例
@Configuration //告诉Spring这是一个配置类
@ComponentScans(
value = {
@ComponentScan(value="com.atguigu",includeFilters = {
//@Filter(type=FilterType.ANNOTATION,classes={Controller.class}),
//@Filter(type=FilterType.ASSIGNABLE_TYPE,classes={BookService.class}),
@Filter(type=FilterType.CUSTOM,classes={MyTypeFilter.class})
},useDefaultFilters = false)
})
public class MainConfig {
//给容器中注册一个Bean;类型为返回值的类型,id默认是用方法名作为id
@Bean("person")
public Person person01(){
return new Person("lisi", 20);
}
}
public class MyTypeFilter implements TypeFilter {
/**
* metadataReader:读取到的当前正在扫描的类的信息
* metadataReaderFactory:可以获取到其他任何类信息的
*/
@Override
public boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory)
throws IOException {
// TODO Auto-generated method stub
//获取当前类注解的信息
AnnotationMetadata annotationMetadata = metadataReader.getAnnotationMetadata();
//获取当前正在扫描的类的类信息
ClassMetadata classMetadata = metadataReader.getClassMetadata();
//获取当前类资源(类的路径)
Resource resource = metadataReader.getResource();
String className = classMetadata.getClassName();
System.out.println("--->"+className);
// 指定类名称包含 er 字符串的都匹配 加入容器
if(className.contains("er")){
return true;
}
return false;
}
}
使用 @Configuration + @Bean注解声明【导入的第三方包里面的组件】
//配置类==配置文件
@Configuration //告诉Spring这是一个配置类
public class MainConfig {
//给容器中注册一个Bean;类型为返回值的类型,id默认是用方法名作为id,可以使用
// @Bean注解value属性指定bean的名字
@Bean("person")
public Person person01(){
return new Person("lisi", 20);
}
}
@Test
public void test01(){
// 使用 AnnotationConfigApplicationContext 加载配置类
ApplicationContext applicationContext = new AnnotationConfigApplicationContext(MainConfig.class);
Person bean = applicationContext.getBean(Person.class);
System.out.println(bean);
}
使用 @EnableXXX + @Import 注解
@Import
可以传入四种类型:普通类、配置类、ImportSelector
的实现类,ImportBeanDefinitionRegistrar
的实现类。
-
@Import 导入普通类;容器中就会自动注册这个组件,id默认是全类名; 使用@Import注解可以把一个普通类加入Spring容器中,例如下面这个把Color加入到Spring容器中。
@Configuration @Import({Color.class}) public class MainConfig { } public class Color { }
-
导入配置类
@Configuration public class ColorRegistrarConfiguration { @Bean public Yellow yellow() { return new Yellow(); } } @Import({ColorRegistrarConfiguration.class}) public @interface EnableColor { }
-
ImportSelector:导入一个选择器,返回需要导入的组件的全类名数组;
@Configuration @Import({MyImportSelector.class}) public class MainConfig { } //自定义逻辑返回需要导入的组件 public class MyImportSelector implements ImportSelector { //返回值,就是到导入到容器中的组件全类名 //AnnotationMetadata:当前标注@Import注解的类的所有注解信息 @Override public String[] selectImports(AnnotationMetadata importingClassMetadata) { // TODO Auto-generated method stub //importingClassMetadata //方法不要返回null值 return new String[]{"com.xxx.bean.Blue","com.xxx.bean.Yellow"}; } } public class Blue {} public class Yellow {}
-
ImportBeanDefinitionRegistrar:手动注册bean到容器中
@Configuration @Import({MyImportBeanDefinitionRegistrar.class}) public class MainConfig { } public class MyImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar { /** * AnnotationMetadata:当前类的注解信息 * BeanDefinitionRegistry:BeanDefinition注册类; * 把所有需要添加到容器中的bean;调用 * BeanDefinitionRegistry.registerBeanDefinition手工注册进来 */ @Override public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) { // 自己的逻辑 boolean definition = registry.containsBeanDefinition("com.xxx.bean.Red"); boolean definition2 = registry.containsBeanDefinition("com.xxx.bean.Blue"); if(definition && definition2){ //指定Bean定义信息;(Bean的类型,Bean。。。) RootBeanDefinition beanDefinition = new RootBeanDefinition(RainBow.class); //注册一个Bean,指定bean名 registry.registerBeanDefinition("rainBow", beanDefinition); } } } public class Blue {} public class Yellow {}
使用Spring提供的 FactoryBean(工厂Bean)
FactoryBean是一个工厂,是一个接口,容器会调用 getObject() 方法返回的对象放入容器中。
// 声明一个 ColorFactoryBean 实际 bean 的类型是 Color。
@Bean
public ColorFactoryBean colorFactoryBean(){
return new ColorFactoryBean();
}
//创建一个Spring定义的FactoryBean
public class ColorFactoryBean implements FactoryBean<Color> {
//返回一个Color对象,这个对象会添加到容器中
@Override
public Color getObject() throws Exception {
// TODO Auto-generated method stub
System.out.println("ColorFactoryBean...getObject...");
return new Color();
}
// Bean 返回的类型
@Override
public Class<?> getObjectType() {
// TODO Auto-generated method stub
return Color.class;
}
//是单例?
//true:这个bean是单实例,在容器中保存一份
//false:多实例,每次获取都会创建一个新的bean;
@Override
public boolean isSingleton() {
// TODO Auto-generated method stub
return true;
}
}
@Test
public void testImport(){
//工厂Bean获取的是调用getObject创建的对象
Object bean2 = applicationContext.getBean("colorFactoryBean");
Object bean3 = applicationContext.getBean("colorFactoryBean");
System.out.println("bean的类型:"+bean2.getClass());// bean的类型:class com.xxx.bean.Color
System.out.println(bean2 == bean3);
// 返回 true,表示是单实例的。因为ColorFactoryBean的isSingleton方法返回的是true表示单实例,如果返回false,这里bean2就不等于bean3。
//通过& 获取工厂Bean本身
Object bean4 = applicationContext.getBean("&colorFactoryBean");
System.out.println(bean4.getClass()); //返回colorFactoryBean
}
综上,FactoryBean默认获取到的是工厂bean调用getObject创建的对象,如果要获取工厂Bean本身,我们需要给id前面加一个&,即上面代码&colorFactoryBean;
SpringBoot的自动装配
SpringBoot的自动配置完全由 @EnableAutoConfiguration
开启。
这个注解官方文档翻译: 启用Spring-ApplicationContext的自动配置,并且会尝试猜测和配置您可能需要的Bean。通常根据您的类路径和定义的Bean来应用自动配置类。例如,如果您的类路径上有
tomcat-embedded.jar
,则可能需要TomcatServletWebServerFactory
(除非自己已经定义了ServletWebServerFactory
的Bean)。使用
@SpringBootApplication
时,将自动启用上下文的自动配置,因此再添加该注解不会产生任何其他影响。自动配置会尝试尽可能地智能化,并且在您定义更多自定义配置时会自动退出(被覆盖)。您始终可以手动排除掉任何您不想应用的配置(如果您无法访问它们,请使用
excludeName()
方法),您也可以通过spring.autoconfigure.exclude
属性排除它们。自动配置始终在注册用户自定义的Bean之后应用。通常被
@EnableAutoConfiguration
标注的类(如@SpringBootApplication
)的包具有特定的意义,通常被用作“默认值”。例如,在扫描@Entity类时将使用它。通常建议您将@EnableAutoConfiguration
(如果您未使用@SpringBootApplication
)放在根包中,以便可以搜索所有包及子包下的类。自动配置类也是常规的Spring配置类。它们使用
SpringFactoriesLoader
机制定位(针对此类)。通常自动配置类也是@Conditional
Bean(最经常的情况下是使用@ConditionalOnClass
和@ConditionalOnMissingBean
标注)
@EnableAutoConfiguration
是一个组合注解:
@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration
而 @AutoConfigurationPackage
内容:
/*
* 表示包含该注解的类所在的包应该在 `AutoConfigurationPackages` 中注册。
*/
@Import(AutoConfigurationPackages.Registrar.class)
public @interface AutoConfigurationPackage
springboot中主启动类必须放在所有自定义组件的包的最外层,以保证Spring能扫描到它们,由此可知是它起的作用。 AutoConfigurationPackages.Registrar
的作用就是取主启动类所在包及子包下的组件注册Bean。
@EnableAutoConfiguration
还有一部分内容:
@Import(AutoConfigurationImportSelector.class)
public class AutoConfigurationImportSelector implements DeferredImportSelector, BeanClassLoaderAware, ResourceLoaderAware, BeanFactoryAware, EnvironmentAware, Ordered
AutoConfigurationImportSelector
实现了 DeferredImportSelector
DeferredImportSelector
是ImportSelector
的一种扩展,在处理完所有@Configuration
类型的Bean之后运行。当所选导入为@Conditional
时,这种类型的选择器特别有用。实现类还可以扩展
Ordered
接口,或使用@Order
注解来指示相对于其他DeferredImportSelector
的优>先级。实现类也可以提供导入组,该导入组可以提供跨不同选择器的其他排序和筛选逻辑。
所以,DeferredImportSelector
的执行时机,是在 @Configuration
注解中的其他逻辑被处理完毕之后(包括对 @ImportResource
、@Bean
这些注解的处理)再执行,换句话说,DeferredImportSelector
的执行时机比 ImportSelector
更晚
而 AutoConfigurationImportSelector
的核心就是,ImportSelector
的 selectImport
方法,
调用了 getAutoConfigurationEntry
(这个方法作用:根据导入的@Configuration
类的AnnotationMetadata
返回AutoConfigurationImportSelector.AutoConfigurationEntry
),
然后调用 getCandidateConfigurations
(【核心】加载候选的自动配置类),这个方法又调用了 SpringFactoriesLoader.loadFactoryNames
方法,传入的Class就是 @EnableAutoConfiguration
SpringFactoriesLoader.loadFactoryNames
使用 classLoader 去加载指定常量路径下的资源: FACTORIES_RESOURCE_LOCATION
,而这个常量指定的路径实际是:META-INF/spring.factories 。
一图胜千言:
使用@Conditional按照条件给容器注册Bean
@Conditional({Condition}) : 按照一定的条件进行判断,满足条件给容器中注册bean
@Conditional注解可以加在方法上,也可以加在类上。
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Conditional {
/**
* All {@link Condition}s that must {@linkplain Condition#matches match}
* in order for the component to be registered.
*/
Class<? extends Condition>[] value();
}
@Conditional注解value属性需要一个实现Condition注解的类,重写 matches方法,返回true则是匹配,false则不匹配。
public interface Condition {
/**
* Determine if the condition matches.
* @param context the condition context
* @param metadata metadata of the {@link org.springframework.core.type.AnnotationMetadata class}
* or {@link org.springframework.core.type.MethodMetadata method} being checked.
* @return {@code true} if the condition matches and the component can be registered
* or {@code false} to veto registration.
*/
boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata);
}
案例:如果系统是windows,给容器中注册("bill"),如果是linux系统,给容器中注册("linus")
@Configuration
public class MainConfig {
@Bean("bill")
public Person person01(){
return new Person("Bill Gates",62);
}
@Conditional(LinuxCondition.class)
@Bean("linus")
public Person person02(){
return new Person("linus", 48);
}
}
public class LinuxCondition implements Condition {
/**
* ConditionContext:判断条件能使用的上下文(环境)
* AnnotatedTypeMetadata:注释信息
*/
@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
// TODO是否linux系统
//1、能获取到ioc使用的beanfactory
ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
//2、获取类加载器
ClassLoader classLoader = context.getClassLoader();
//3、获取当前环境信息
Environment environment = context.getEnvironment();
//4、获取到bean定义的注册类
BeanDefinitionRegistry registry = context.getRegistry();
String property = environment.getProperty("os.name");
//可以判断容器中的bean注册情况,也可以给容器中注册bean
boolean definition = registry.containsBeanDefinition("person");
if(property.contains("linux")){
return true;
}
return false;
}
}
//判断是否windows系统
public class WindowsCondition implements Condition {
@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
Environment environment = context.getEnvironment();
String property = environment.getProperty("os.name");
if(property.contains("Windows")){
return true;
}
return false;
}
}
使用@Scope指定不同作用域Bean
Spring容器默认生成的Bean都是单实例的,可以使用@Scope指定不同作用域。 在@Scope源码中可以看见scopeName属性上有下面这段Javadoc。
/**
* Specifies the name of the scope to use for the annotated component/bean.
* <p>Defaults to an empty string ({@code ""}) which implies
* {@link ConfigurableBeanFactory#SCOPE_SINGLETON SCOPE_SINGLETON}.
* @since 4.2
* @see ConfigurableBeanFactory#SCOPE_PROTOTYPE
* @see ConfigurableBeanFactory#SCOPE_SINGLETON
* @see org.springframework.web.context.WebApplicationContext#SCOPE_REQUEST
* @see org.springframework.web.context.WebApplicationContext#SCOPE_SESSION
* @see #value
*/
由上面可知 使用ConfigurableBeanFactory可以指定Scope的作用域SCOPE_SINGLETON(单例)和SCOPE_PROTOTYPE(多例),使用WebApplicationContext可以指定SCOPE_REQUEST(web环境下同一个请求创建一个实例),SCOPE_SESSION(web环境下同一个Session创建一个实例)。
@Scope("prototype")
@Bean("person")
public Person person(){
System.out.println("给容器中添加Person....");
return new Person("张三", 25);
}
注意: prototype:多实例的:ioc容器启动并不会去调用方法创建对象放在容器中,每次获取的时候才会调用方法创建对象; singleton:单实例的(默认值):ioc容器启动会调用方法创建对象放到ioc容器中,以后每次获取就是直接从容器(map.get())中拿,
使用@Lazy懒加载Bean
Spring中的Bean默认是单实例的,默认在容器启动的时候创建对象;那么可以使用@Lazy实现懒加载Bean,即容器启动不创建对象。第一次使用(获取)Bean创建对象,并初始化;
@Lazy
@Bean("person")
public Person person(){
System.out.println("给容器中添加Person....");
return new Person("张三", 25);
}
Bean的生命周期
bean的生命周期就是bean创建到初始化再到销毁的过程,容器管理bean的生命周期;我们可以自定义初始化和销毁方法;容器在bean进行到当前生命周期的时候来调用我们自定义的初始化和销毁方法。
指定初始化和销毁方法:
- 1.通过@Bean指定init-method和destroy-method;
@Configuration
public class MainConfigOfLifeCycle {
@Bean(initMethod="init",destroyMethod="detory")
public Car car(){
return new Car();
}
}
public class Car {
public Car(){
System.out.println("car constructor...");
}
public void init(){
System.out.println("car ... init...");
}
public void detory(){
System.out.println("car ... detory...");
}
}
@Test
public void test01(){
//1、创建ioc容器,容器创建之后就会初始化单实例的Bean
AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(MainConfigOfLifeCycle.class);
System.out.println("容器创建完成...");
//applicationContext.getBean("car");
//关闭容器
applicationContext.close();
}
// 运行后 car constructor... car ... init... 容器创建完成... car ... detory... 所以,
- 构造(对象创建)时机, - 单实例:在容器启动的时候创建对象 - 多实例:在每次获取的时候创建对象
- 初始化时机:对象创建完成,并赋值好,调用初始化方法。。。
- 销毁时机: - 单实例:容器关闭的时候 - 多实例:容器不会管理这个bean;容器不会调用销毁方法;
- 2.通过让Bean实现InitializingBean(定义初始化逻辑),DisposableBean(定义销毁逻辑);
@ComponentScan("com.xxx.bean")
@Configuration
public class MainConfigOfLifeCycle {
}
@Component
public class Cat implements InitializingBean,DisposableBean {
public Cat(){
System.out.println("cat constructor...");
}
@Override
public void destroy() throws Exception {
// TODO Auto-generated method stub
System.out.println("cat...destroy...");
}
@Override
public void afterPropertiesSet() throws Exception {
// TODO Auto-generated method stub
System.out.println("cat...afterPropertiesSet...");
}
}
- 3.可以使用JSR250规范中定义的两个注解;
- @PostConstruct:在bean创建完成并且属性赋值完成;来执行初始化方法。
- @PreDestroy:在容器销毁bean之前通知我们进行清理工作。
@Component
public class Dog implements ApplicationContextAware {
//@Autowired
private ApplicationContext applicationContext;
public Dog(){
System.out.println("dog constructor...");
}
//对象创建并赋值之后调用
@PostConstruct
public void init(){
System.out.println("Dog....@PostConstruct...");
}
//容器移除对象之前
@PreDestroy
public void detory(){
System.out.println("Dog....@PreDestroy...");
}
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
// TODO Auto-generated method stub
this.applicationContext = applicationContext;
}
}
使用BeanPostProcessor对Bean初始化前后进行拦截
BeanPostProcessor接口:bean的后置处理器;在bean初始化前后进行一些处理工作; postProcessBeforeInitialization:在初始化之前工作(比如实现 InitializingBean.afterPropertiesSet()之前,或者自定义 init-method方法之前执行 ) postProcessAfterInitialization:在初始化之后工作(比如实现 InitializingBean.afterPropertiesSet()之后,或者自定义 init-method方法之后执行 )
- 使用方式:
/**
* 后置处理器:初始化前后进行处理工作
* 将后置处理器加入到容器中
*/
@Component
public class MyBeanPostProcessor implements BeanPostProcessor {
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
System.out.println("postProcessBeforeInitialization..."+beanName+"=>"+bean);
return bean;
}
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
System.out.println("postProcessAfterInitialization..."+beanName+"=>"+bean);
return bean;
}
}
- 原理: 跟踪源码到 AbstractAutowireCapableBeanFactory 类的initializeBean方法。
protected Object initializeBean(final String beanName, final Object bean, RootBeanDefinition mbd) {
if (System.getSecurityManager() != null) {
AccessController.doPrivileged(new PrivilegedAction<Object>() {
@Override
public Object run() {
invokeAwareMethods(beanName, bean);
return null;
}
}, getAccessControlContext());
}
else {
invokeAwareMethods(beanName, bean);
}
Object wrappedBean = bean;
if (mbd == null || !mbd.isSynthetic()) {
wrappedBean = applyBeanPostProcessorsBeforeInitialization(wrappedBean, beanName);
}
try {
invokeInitMethods(beanName, wrappedBean, mbd);
}
catch (Throwable ex) {
throw new BeanCreationException(
(mbd != null ? mbd.getResourceDescription() : null),
beanName, "Invocation of init method failed", ex);
}
if (mbd == null || !mbd.isSynthetic()) {
wrappedBean = applyBeanPostProcessorsAfterInitialization(wrappedBean, beanName);
}
return wrappedBean;
}
整理裁剪上面源码:
populateBean(beanName, mbd, instanceWrapper);给bean进行属性赋值
initializeBean
{
// 遍历得到容器中所有的BeanPostProcessor;挨个执行beforeInitialization,
// 一但返回null,跳出for循环,不会执行后面的BeanPostProcessor.postProcessorsBeforeInitialization
applyBeanPostProcessorsBeforeInitialization(wrappedBean, beanName);
invokeInitMethods(beanName, wrappedBean, mbd);执行自定义初始化
applyBeanPostProcessorsAfterInitialization(wrappedBean, beanName);
}
applyBeanPostProcessorsBeforeInitialization()方法就是执行初始化之前的操作;invokeInitMethods()方法就是执行初始化;applyBeanPostProcessorsAfterInitialization()方法执行初始化之后的操作。 整个initializeBean()方法是在populateBean()方法之后执行的,这个方法是给bean进行属性赋值。
Spring底层对BeanPostProcessor的使用
使用 @Value 给属性赋值时有三种方式:
使用 @Value 给属性赋值时有三种方式:
- 基本数值
- 可以写SpEL; #{}
- 可以写${};取出配置文件【properties】中的值(在运行环境变量里面的值)
//使用@PropertySource读取外部配置文件中的k/v保存到运行的环境变量中;加载完外部的配置文件以后使用${}取出配置文件的值
@PropertySource(value={"classpath:/person.properties"})
@Configuration
public class MainConfigOfPropertyValues {
@Bean
public Person person(){
return new Person();
}
}
public class Person {
//使用@Value赋值;
//1、基本数值
//2、可以写SpEL; #{}
//3、可以写${};取出配置文件【properties】中的值(在运行环境变量里面的值)
// 基本数值,不怎么使用
@Value("张三")
private String name;
// SpEL
@Value("#{20-2}")
private Integer age;
// 从配置文件中获取 常用
@Value("${person.nickName}")
private String nickName;
}
spring 自动装配
自动装配概念
Spring利用依赖注入(DI),完成对IOC容器中中各个组件的依赖关系赋值;
自动装配方式
使用@Autowired自动注入
常用的使用方式,Autowired标注在属性上自动注入
- 默认优先按照类型去容器中找对应的组件:applicationContext.getBean(xxx.class);找到就赋值;
使用案例:
```
@Service
public class BookService {
@Autowired
private BookDao bookDao;
public void print(){
System.out.println(bookDao);
}
@Override
public String toString() {
return "BookService [bookDao=" + bookDao + "]";
}
@Repository
public class BookDao {}
```
测试代码:
```
@Test
public void test01(){
AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(MainConifgOfAutowired.class);
BookService bookService = applicationContext.getBean(BookService.class);
System.out.println(bookService);
applicationContext.close();
}
```
- 如果找到多个相同类型的组件,再将属性的名称作为组件的id去容器中查找,类似applicationContext.getBean("xxx");
多个相同的类型Bean案例:
先声明一个BookDao类型的Bean
```
//名字默认是类名首字母小写
@Repository
public class BookDao {
private String lable = "1";
public String getLable() {
return lable;
}
public void setLable(String lable) {
this.lable = lable;
}
@Override
public String toString() {
return "BookDao [lable=" + lable + "]";
}
}
```
再声明另一个BookDao类型的Bean
```
@Bean("bookDao2")
public BookDao bookDao(){
BookDao bookDao = new BookDao();
bookDao.setLable("2");
return bookDao;
}
```
测试:
```
@Test
public void test01(){
AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(MainConifgOfAutowired.class);
BookService bookService = applicationContext.getBean(BookService.class);
System.out.println(bookService);
applicationContext.close();
}
```
输出:默认输出label为1的BookDao, 因为BookDao类型找到多个相同的组件,再将属性的名称作为组件的id去容器中查找,即使用bookDao去找,就找到label为1的BookDao。
- @Qualifier("xxx"):使用@Qualifier指定需要装配的组件的id,而不是使用属性名;
```
@Service
public class BookService {
@Qualifier("bookDao2")
@Autowired
private BookDao bookDao;
public void print(){
System.out.println(bookDao);
}
@Override
public String toString() {
return "BookService [bookDao=" + bookDao + "]";
}
}
// 第一种声明Bean
@Repository
public class BookDao {
private String lable = "1";
public String getLable() {
return lable;
}
public void setLable(String lable) {
this.lable = lable;
}
@Override
public String toString() {
return "BookDao [lable=" + lable + "]";
}
}
// 第二种声明Bean
@Bean("bookDao2")
public BookDao bookDao(){
BookDao bookDao = new BookDao();
bookDao.setLable("2");
return bookDao;
}
```
上面在BookService中注入BookDao时,使用@Qualifier指定声明的是bookDao2,这时候bookDao2会被注入进去。
- 自动装配默认一定要将属性赋值好,如果容器中没有组件就会报错;可以使用@Autowired(required=false)表示找到这个组件就注入,没有找到就不注入;
- @Primary:让Spring进行自动装配的时候,默认使用首选的bean(这个时候@Qualifier就不能使用了);也可以继续使用@Qualifier指定需要装配的bean的名字;
```
@Service
public class BookService {
//不能使用 @Qualifier("bookDao2")指定Bean了 因为@Primary来确定首选Bean,当然这时候还用 @Qualifier指定的话,那么会按照@Qualifier指定的来注入
@Autowired
private BookDao bookDao;
public void print(){
System.out.println(bookDao);
}
@Override
public String toString() {
return "BookService [bookDao=" + bookDao + "]";
}
}
// 第一种声明Bean
@Repository
public class BookDao {
private String lable = "1";
public String getLable() {
return lable;
}
public void setLable(String lable) {
this.lable = lable;
}
@Override
public String toString() {
return "BookDao [lable=" + lable + "]";
}
}
// 第二种声明Bean 使用@Primary表明即使有多个BookDao类型的Bean,首选使用bookDao2
@Primary
@Bean("bookDao2")
public BookDao bookDao(){
BookDao bookDao = new BookDao();
bookDao.setLable("2");
return bookDao;
}
```
Autowired 其他使用方式
@Autowired可以标注在构造器,参数,方法,属性上;都是从容器中获取参数组件的值
- 标注在方法位置:@Bean+方法参数;参数从容器中获取;默认不写@Autowired效果是一样的;都能自动装配。
@Component
public class Boss {
private Car car;
public Car getCar() {
return car;
}
//标注在方法,Spring容器创建当前对象,就会调用方法,完成赋值;
//方法使用的参数,自定义类型的值从ioc容器中获取
@Autowired
public void setCar(Car car) {
this.car = car;
}
@Override
public String toString() {
return "Boss [car=" + car + "]";
}
}
@Component
public class Car {
public Car(){
System.out.println("car constructor...");
}
public void init(){
System.out.println("car ... init...");
}
public void detory(){
System.out.println("car ... detory...");
}
}
- 标在构造器上:如果组件只有一个有参构造器,这个有参构造器的@Autowired可以省略,参数位置的组件还是可以自动从容器中获取
//默认加在ioc容器中的组件,容器启动会调用无参构造器创建对象,再进行初始化赋值等操作
@Component
public class Boss {
private Car car;
//构造器要用的组件,都是从容器中获取
@Autowired // 可以省略不写
public Boss(Car car){
this.car = car;
System.out.println("Boss...有参构造器");
}
public Car getCar() {
return car;
}
public void setCar(Car car) {
this.car = car;
}
@Override
public String toString() {
return "Boss [car=" + car + "]";
}
}
@Component
public class Car {
public Car(){
System.out.println("car constructor...");
}
public void init(){
System.out.println("car ... init...");
}
public void detory(){
System.out.println("car ... detory...");
}
}
- 放在参数位置:
//默认加在ioc容器中的组件,容器启动会调用无参构造器创建对象,再进行初始化赋值等操作
@Component
public class Boss {
private Car car;
// 可以放在参数上 也可以直接省略,spring容器自动会在容器取值注入参数上
public Boss(@Autowired Car car){
this.car = car;
System.out.println("Boss...有参构造器");
}
public Car getCar() {
return car;
}
// set方法也一样可以放在参数上
public void setCar(Car car) {
this.car = car;
}
@Override
public String toString() {
return "Boss [car=" + car + "]";
}
}
@Component
public class Car {
public Car(){
System.out.println("car constructor...");
}
public void init(){
System.out.println("car ... init...");
}
public void detory(){
System.out.println("car ... detory...");
}
}
Spring还支持使用@Resource(JSR250)和@Inject(JSR330)两种java规范的注解;
- @Resource:可以和@Autowired一样实现自动装配功能;默认是按照组件名称进行装配的;没有能支持@Primary功能没有支持@Autowired(reqiured=false);
```
@Service
public class BookService {
@Resource
private BookDao bookDao;
public void print(){
System.out.println(bookDao);
}
@Override
public String toString() {
return "BookService [bookDao=" + bookDao + "]";
}
}
// 第一种声明Bean
@Repository
public class BookDao {
private String lable = "1";
public String getLable() {
return lable;
}
public void setLable(String lable) {
this.lable = lable;
}
@Override
public String toString() {
return "BookDao [lable=" + lable + "]";
}
}
// 第二种声明Bean
@Primary
@Bean("bookDao2")
public BookDao bookDao(){
BookDao bookDao = new BookDao();
bookDao.setLable("2");
return bookDao;
}
```
上面代码最后注入BookService的是lable等于1的bookDao,即使在lable等于2的bookDao上声明@Primary也没用,它是按照组件名称进行装配的,不支持@Primary。
- @Inject:需要导入javax.inject的包,和Autowired的功能一样。没有required=false的功能;
```
@Service
public class BookService {
@Inject
private BookDao bookDao;
public void print(){
System.out.println(bookDao);
}
@Override
public String toString() {
return "BookService [bookDao=" + bookDao + "]";
}
}
// 第一种声明Bean
@Repository
public class BookDao {
private String lable = "1";
public String getLable() {
return lable;
}
public void setLable(String lable) {
this.lable = lable;
}
@Override
public String toString() {
return "BookDao [lable=" + lable + "]";
}
}
// 第二种声明Bean
@Primary
@Bean("bookDao2")
public BookDao bookDao(){
BookDao bookDao = new BookDao();
bookDao.setLable("2");
return bookDao;
}
```
使用@Inject注入,打印的是lable等于2的bookDao,也就是@Primary可以起作用,它和Autowired的功能一样,只是没有required=false的功能;
综上,@Autowired、@Inject、@Resource都可以自动装配,区别在于@Autowired:Spring定义的; @Resource、@Inject都是java规范,另外@Resource不能支持是按照组件名称进行装配的,不支持@Primary。而@Inject和@Autowired是一样的,支持@Primary,但是@Inject没有required=false的功能;推荐使用@Autowired来自动装配。 自动注入能生效主要依赖于AutowiredAnnotationBeanPostProcessor这个后置处理器来解析完成自动装配功能;
实现xxxAware把Spring底层一些组件注入到自定义的Bean中
自定义组件想要使用Spring容器底层的一些组件(ApplicationContext,BeanFactory,xxx); 自定义组件实现xxxAware;在创建对象的时候,会调用接口规定的方法注入相关组件;xxxAware都是实现了Aware接口的;
/**
* Marker superinterface indicating that a bean is eligible to be
* notified by the Spring container of a particular framework object
* through a callback-style method. Actual method signature is
* determined by individual subinterfaces, but should typically
* consist of just one void-returning method that accepts a single
* argument.
*
* <p>Note that merely implementing {@link Aware} provides no default
* functionality. Rather, processing must be done explicitly, for example
* in a {@link org.springframework.beans.factory.config.BeanPostProcessor BeanPostProcessor}.
* Refer to {@link org.springframework.context.support.ApplicationContextAwareProcessor}
* and {@link org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory}
* for examples of processing {@code *Aware} interface callbacks.
*
* @author Chris Beams
* @since 3.1
*/
public interface Aware {
}
案例:
@Component
public class Red implements ApplicationContextAware,BeanNameAware,EmbeddedValueResolverAware {
private ApplicationContext applicationContext;
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
// TODO Auto-generated method stub
System.out.println("传入的ioc:"+applicationContext);
this.applicationContext = applicationContext;
}
@Override
public void setBeanName(String name) {
// TODO Auto-generated method stub
System.out.println("当前bean的名字:"+name);
}
@Override
public void setEmbeddedValueResolver(StringValueResolver resolver) {
// TODO Auto-generated method stub
String resolveStringValue = resolver.resolveStringValue("你好 ${os.name} 我是 #{20*18}");
System.out.println("解析的字符串:"+resolveStringValue);
}
}
xxxAware:功能使用xxxProcessor; 以ApplicationContextAware为例,当Bean创建之后,如果Bean实现了ApplicationContextAware接口,那么会有一个ApplicationContextAwareProcessor后置处理器把ApplicationContext注入进来。
/**
* {@link org.springframework.beans.factory.config.BeanPostProcessor}
* implementation that passes the ApplicationContext to beans that
* implement the {@link EnvironmentAware}, {@link EmbeddedValueResolverAware},
* {@link ResourceLoaderAware}, {@link ApplicationEventPublisherAware},
* {@link MessageSourceAware} and/or {@link ApplicationContextAware} interfaces.
*
* <p>Implemented interfaces are satisfied in order of their mention above.
*
* <p>Application contexts will automatically register this with their
* underlying bean factory. Applications do not use this directly.
*
* @author Juergen Hoeller
* @author Costin Leau
* @author Chris Beams
* @since 10.10.2003
* @see org.springframework.context.EnvironmentAware
* @see org.springframework.context.EmbeddedValueResolverAware
* @see org.springframework.context.ResourceLoaderAware
* @see org.springframework.context.ApplicationEventPublisherAware
* @see org.springframework.context.MessageSourceAware
* @see org.springframework.context.ApplicationContextAware
* @see org.springframework.context.support.AbstractApplicationContext#refresh()
*/
class ApplicationContextAwareProcessor implements BeanPostProcessor {
@Override
public Object postProcessBeforeInitialization(final Object bean, String beanName) throws BeansException {
AccessControlContext acc = null;
if (System.getSecurityManager() != null &&
(bean instanceof EnvironmentAware || bean instanceof EmbeddedValueResolverAware || bean instanceof ResourceLoaderAware || bean instanceof ApplicationEventPublisherAware || bean instanceof MessageSourceAware || bean instanceof ApplicationContextAware)) {
acc = this.applicationContext.getBeanFactory().getAccessControlContext();
}
if (acc != null) {
AccessController.doPrivileged(new PrivilegedAction<Object>() {
@Override
public Object run() {
invokeAwareInterfaces(bean);
return null;
}
}, acc);
}
else {
// 通过这个方法 注入
invokeAwareInterfaces(bean);
}
return bean;
}
private void invokeAwareInterfaces(Object bean) {
if (bean instanceof Aware) {
if (bean instanceof EnvironmentAware) {
((EnvironmentAware) bean).setEnvironment(this.applicationContext.getEnvironment());
}
if (bean instanceof EmbeddedValueResolverAware) {
((EmbeddedValueResolverAware) bean).setEmbeddedValueResolver(this.embeddedValueResolver);
}
if (bean instanceof ResourceLoaderAware) {
((ResourceLoaderAware) bean).setResourceLoader(this.applicationContext);
}
if (bean instanceof ApplicationEventPublisherAware) {
((ApplicationEventPublisherAware) bean).setApplicationEventPublisher(this.applicationContext);
}
if (bean instanceof MessageSourceAware) {
((MessageSourceAware) bean).setMessageSource(this.applicationContext);
}
if (bean instanceof ApplicationContextAware) {
((ApplicationContextAware) bean).setApplicationContext(this.applicationContext);
}
}
}
}
上面的源码中可以看到,ApplicationContextAwareProcessor是一个后置处理器,会在Bean初始化之前执行postProcessBeforeInitialization方法,然后判断Bean是否实现各种Aware接口,把相关的Bean注入进去,可以看到ApplicationContextAwareProcessor可以处理
org.springframework.context.EnvironmentAware
org.springframework.context.EmbeddedValueResolverAware
org.springframework.context.ResourceLoaderAware
org.springframework.context.ApplicationEventPublisherAware
org.springframework.context.MessageSourceAware
org.springframework.context.ApplicationContextAware
这些Aware。
使用@Profile分环境创建Bean
Profile:Spring为我们提供的可以根据当前环境,动态的激活和切换一系列组件的功能; @Profile:指定组件在哪个环境的情况下才能被注册到容器中,不指定,任何环境下都能注册这个组件
- 1)、加了环境标识的bean,只有这个环境被激活的时候才能注册到容器中。默认是default环境
- 2)、写在配置类上,只有是指定的环境的时候,整个配置类里面的所有配置才能开始生效
- 3)、没有标注环境标识的bean在,任何环境下都是加载的;
以数据源为例,开发环境、测试环境、生产环境;分别使用数据源:(/A)(/B)(/C);
// 分别使用多种方式 解析dbconfig.properties文件KV值
@PropertySource("classpath:/dbconfig.properties")
@Configuration
public class MainConfigOfProfile implements EmbeddedValueResolverAware{
@Value("${db.user}")
private String user;
private StringValueResolver valueResolver;
private String driverClass;
@Bean
public Yellow yellow(){
return new Yellow();
}
@Profile("test")
@Bean("testDataSource")
public DataSource dataSourceTest(@Value("${db.password}")String pwd) throws Exception{
ComboPooledDataSource dataSource = new ComboPooledDataSource();
dataSource.setUser(user);
dataSource.setPassword(pwd);
dataSource.setJdbcUrl("jdbc:mysql://localhost:3306/test");
dataSource.setDriverClass(driverClass);
return dataSource;
}
@Profile("dev")
@Bean("devDataSource")
public DataSource dataSourceDev(@Value("${db.password}")String pwd) throws Exception{
ComboPooledDataSource dataSource = new ComboPooledDataSource();
dataSource.setUser(user);
dataSource.setPassword(pwd);
dataSource.setJdbcUrl("jdbc:mysql://localhost:3306/ssm_crud");
dataSource.setDriverClass(driverClass);
return dataSource;
}
@Profile("prod")
@Bean("prodDataSource")
public DataSource dataSourceProd(@Value("${db.password}")String pwd) throws Exception{
ComboPooledDataSource dataSource = new ComboPooledDataSource();
dataSource.setUser(user);
dataSource.setPassword(pwd);
dataSource.setJdbcUrl("jdbc:mysql://localhost:3306/scw_0515");
dataSource.setDriverClass(driverClass);
return dataSource;
}
@Override
public void setEmbeddedValueResolver(StringValueResolver resolver) {
// TODO Auto-generated method stub
this.valueResolver = resolver;
driverClass = valueResolver.resolveStringValue("${db.driverClass}");
}
}
测试
//1、使用命令行动态参数: 在虚拟机参数位置加载 -Dspring.profiles.active=test
//2、代码的方式激活某种环境;
@Test
public void test01(){
// 使用无参构造器创建 AnnotationConfigApplicationContext
AnnotationConfigApplicationContext applicationContext =
new AnnotationConfigApplicationContext();
//1、创建一个applicationContext
//2、设置需要激活的环境
applicationContext.getEnvironment().setActiveProfiles("dev");
//3、注册主配置类
applicationContext.register(MainConfigOfProfile.class);
//4、启动刷新容器
applicationContext.refresh();
String[] namesForType = applicationContext.getBeanNamesForType(DataSource.class);
for (String string : namesForType) {
System.out.println(string);
}
applicationContext.close();
}
Spring AOP 面向切面编程
AOP概念:
指在程序运行期间动态的将某段代码切入到指定方法指定位置进行运行的编程方式;底层原理使用动态代理方式。
基本使用
1.导入AOP模块(Spring AOP)
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>4.3.12.RELEASE</version>
</dependency>
2.定义一个业务逻辑类(MathCalculator);
在业务逻辑运行的时候将日志进行打印(方法之前、方法运行结束、方法出现异常,xxx)
public class MathCalculator {
public int div(int i,int j){
System.out.println("MathCalculator...div...");
return i/j;
}
}
3.定义一个日志切面类(LogAspects)
切面类里面的方法需要动态感知MathCalculator.div运行到哪里然后执行;
通知方法
- 前置通知(@Before):在目标方法(div)运行之前运行,对应于LogAspects.logStart()
- 后置通知(@After):在目标方法(div)运行结束之后运行(无论方法正常结束还是异常结束),对应于LogAspects.logEnd()
- 返回通知(@AfterReturning):在目标方法(div)正常返回之后运行,对应于LogAspects.logReturn();
- 异常通知(@AfterThrowing):在目标方法(div)出现异常以后运行,对应于LogAspects.logException();
- 环绕通知(@Around):动态代理,手动推进目标方法运行(joinPoint.procced())
给切面类的目标方法标注何时何地运行(各种通知注解,比如前置通知、后置通知等);
必须告诉Spring哪个类是切面类(给切面类上加一个注解:@Aspect);
综上几步,下面代码定义了一个切面类:
@Aspect // 告诉Spring当前类是一个切面类
public class LogAspects {
//抽取公共的切入点表达式
//1、本类引用
//2、其他的切面引用
@Pointcut("execution(public int com.atguigu.aop.MathCalculator.*(..))")
public void pointCut(){};
//@Before在目标方法之前切入;切入点表达式(指定在哪个方法切入)
@Before("pointCut()")
public void logStart(JoinPoint joinPoint){
Object[] args = joinPoint.getArgs();
System.out.println(""+joinPoint.getSignature().getName()+"运行。。。@Before:参数列表是:{"+Arrays.asList(args)+"}");
}
// com.atguigu.aop.LogAspects.pointCut() 这种方式可以使用外部类,LogAspects可以定义一个切面类,外部类可以这么引用,这里演示
@After("com.atguigu.aop.LogAspects.pointCut()")
public void logEnd(JoinPoint joinPoint){
System.out.println(""+joinPoint.getSignature().getName()+"结束。。。@After");
}
//JoinPoint一定要出现在参数表的第一位,通过returning属性告诉Spring使用result来接受正常的返回结果
@AfterReturning(value="pointCut()",returning="result")
public void logReturn(JoinPoint joinPoint,Object result){
System.out.println(""+joinPoint.getSignature().getName()+"正常返回。。。@AfterReturning:运行结果:{"+result+"}");
}
// 通过throwing属性告诉Spring使用exception来接受异常
@AfterThrowing(value="pointCut()",throwing="exception")
public void logException(JoinPoint joinPoint,Exception exception){
System.out.println(""+joinPoint.getSignature().getName()+"异常。。。异常信息:{"+exception+"}");
}
}
4.将切面类和业务逻辑类(目标方法所在类)都加入到容器中,给配置类中加 @EnableAspectJAutoProxy 【开启基于注解的aop模式】
@EnableAspectJAutoProxy // 开启基于注解的aop模式 最关键
@Configuration
public class MainConfigOfAOP {
//业务逻辑类加入容器中
@Bean
public MathCalculator calculator(){
return new MathCalculator();
}
//切面类加入到容器中
@Bean
public LogAspects logAspects(){
return new LogAspects();
}
}
5.编写测试类
@Test
public void test01(){
AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(MainConfigOfAOP.class);
//1、不要自己创建对象
// MathCalculator mathCalculator = new MathCalculator();
// mathCalculator.div(1, 1);
MathCalculator mathCalculator = applicationContext.getBean(MathCalculator.class);
mathCalculator.div(1, 0);
applicationContext.close();
}
所以,总结上面的步骤,实现一个AOP只需要三步:
- 1)、将业务逻辑组件和切面类都加入到容器中;告诉Spring哪个是切面类(@Aspect)
- 2)、在切面类上的每一个通知方法上标注通知注解,告诉Spring何时何地运行(切入点表达式)
- 3)、开启基于注解的aop模式;@EnableAspectJAutoProxy
AOP原理:【看给容器中注册了什么组件,这个组件什么时候工作,这个组件的功能是什么?】
声明式事务
环境搭建
导入数据源、数据库驱动、Spring-jdbc模块
<!--Spring-jdbc模块-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>4.3.12.RELEASE</version>
</dependency>
<!--c3p0数据源-->
<dependency>
<groupId>c3p0</groupId>
<artifactId>c3p0</artifactId>
<version>0.9.1.2</version>
</dependency>
<!--数据库驱动-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.44</version>
</dependency>
配置数据源、JdbcTemplate(Spring提供的简化数据库操作的工具)操作数据;使用@EnableTransactionManagement 开启基于注解的事务管理功能;配置事务管理器PlatformTransactionManager来控制事务
@EnableTransactionManagement
@ComponentScan("com.atguigu.tx")
@Configuration
public class TxConfig {
//数据源
@Bean
public DataSource dataSource() throws Exception{
ComboPooledDataSource dataSource = new ComboPooledDataSource();
dataSource.setUser("root");
dataSource.setPassword("123456");
dataSource.setDriverClass("com.mysql.jdbc.Driver");
dataSource.setJdbcUrl("jdbc:mysql://localhost:3306/test");
return dataSource;
}
@Bean
public JdbcTemplate jdbcTemplate() throws Exception{
//Spring对@Configuration类会特殊处理;给容器中加组件的方法,多次调用都只是从容器中找组件
JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource());
return jdbcTemplate;
}
//注册事务管理器在容器中
@Bean
public PlatformTransactionManager transactionManager() throws Exception{
return new DataSourceTransactionManager(dataSource());
}
}
给方法上标注 @Transactional 表示当前方法是一个事务方法;
@Service
public class UserService {
@Autowired
private UserDao userDao;
@Transactional
public void insertUser(){
userDao.insert();
//otherDao.other();xxx
System.out.println("插入完成...");
int i = 10/0;
}
}
@Repository
public class UserDao {
@Autowired
private JdbcTemplate jdbcTemplate;
public void insert(){
String sql = "INSERT INTO `tbl_user`(username,age) VALUES(?,?)";
String username = UUID.randomUUID().toString().substring(0, 5);
jdbcTemplate.update(sql, username,19);
}
}
声明式事务原理
扩展原理
BeanFactoryPostProcessor 的原理
BeanFactoryPostProcessorbeanFactory的后置处理器; 在BeanFactory标准初始化之后调用,来定制和修改BeanFactory的内容; 所有的bean定义已经保存加载到beanFactory,但是bean的实例还未创建