这篇文章主要用于说明在日常开发中经常使用到的注解。
1. @Configuration
该注解用于类上,表明这是一个配置类。在Spring MVC中,项目的配置通常放在 xml文件中, 为此需要先写一个配置文件,例如在xml中使用bean标签来注册一些组件。如下:
<bean id="person" class="com.zhao.springboot.bean.Person">
<property name="age" value="22"></property>
<property name="name" value="zhangsan"></property>
</bean>
在注解式开发中 就用该注解 来替代 xml配置文件,在配置文件中注册组件,就被替换成在配置类中添加组件。如下所示:
// 配置类==配置文件
@Configuration //告诉SpringBoot 这是一个配置类
public class MainConfig {
@Bean // 给容器中注入一个Bean ,类型为返回值的类型,id默认是用方法名作为id
public Person person() {
return new Person("lisi", 25);
}
}
测试代码如下:
ApplicationContext annoContext = new AnnotationConfigApplicationContext(MainConfig.class);
Person person = annoContext.getBean(Person.class);
System.out.println(person);
输出结果如下:
注意: @Bean 向容器中注入Bean ,id默认为方法的名称,可以通过name改变 ,@Bean(name="person1")
2. @ComponentScan
该注解用于指定包扫描,用于类上。 Spring MVC中通常用 <context:component-scan /> 来指定扫描范围,只要标注了@Controller、@Service、@Repository、@Component就会被扫描到。注解式开发中使用 @ComponentScan 来替代。
在刚才的配置类上使用该注解进行标注: value指定扫描的位置
测试如下:
分别建立 controller、service、dao类。并用相关注解进行标识。
@Repository
public class BookDao {
}
.....
@Service
public class BookService {
}
.....
@Controller
public class BookController {
}
编写测试代码:
@Test
public void test01() {
ApplicationContext context = new AnnotationConfigApplicationContext(MainConfig.class);
String[] names = context.getBeanDefinitionNames();
for (String name : names) {
System.out.println(name);
}
}
输出如下:
说明: 配置类(MainConfig)自身也会被注册到bean容器中(@Configuration中包含了一个@Component注解) ,此外@ComponentScan注解中还包含有其他可以用的功能,如扫描时只包含哪些(includeFilters)、只排除哪些(excludeFilters)。
2.1 excludeFilters
eg : 排除 所有Controller和Service
@ComponentScan(value = "com.zhao.springboot",excludeFilters = {
@Filter(type = FilterType.ANNOTATION,classes = {Controller.class, Service.class})
})
测试结果:
2.2 includeFilters
eg: 只包含 Controller和Service
@ComponentScan(value = "com.zhao.springboot",includeFilters = {
//type 指定根据什么规则排除
@Filter(type = FilterType.ANNOTATION,classes = {Controller.class, Service.class})
},useDefaultFilters = false)
测试结果:
注意: 使用 includeFilters 时需要禁用掉默认的过滤规则,即需要设置useDefaultFilters =false(默认为true)
2.3 @ComponentScans
@ComponentScan 还是一个重复注解(@Repeatable(ComponentScans.class)),在java8中,可以多次使用该注解来指定不同的扫描策略:
假如不是java8 ,也可以通过 @ComponentScans来达到想要的效果。
2.4 FilterType :
@Filter中的type指定 根据某种规则过滤,
有以下几种规则:
public enum FilterType {
ANNOTATION, // 按照注解
ASSIGNABLE_TYPE, //按照给定的类型
ASPECTJ, // 使用ASPECTJ表达式,不是很常用
REGEX, // 使用正则表达式
CUSTOM; // 自定义规则
private FilterType() {
}
}
上面已经使用了 按照注解 FilterType.ANNOTATION 类型, 这里重点说明 FilterType.ASSIGNABLE_TYPE 和FilterType.CUSTOM。
2.4.1 给定类型 FilterType.ASSIGNABLE_TYPE:使用如下
@ComponentScan(value = "com.zhao.springboot",includeFilters = {
//type 指定根据什么规则排除
@Filter(type = FilterType.ANNOTATION,classes = {Controller.class}),
@Filter(type = FilterType.ASSIGNABLE_TYPE,classes = {BookService.class})
},useDefaultFilters = false)
测试结果:
2.4.2 自定义 类型
自定义过滤类型,需要实现TypeFilter (org.springframework.core.type.filter.TypeFilter)
用法如下:
public class MyTypeFilter implements TypeFilter {
/**
* @param metadataReader 读取到的当前正在扫描的类的信息
* @param metadataReaderFactory 可以获取到其他任何类的信息
* @return
* @throws IOException
*/
public boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory) throws IOException {
// 获取当前类的注解信息
AnnotationMetadata annotationMetadata = metadataReader.getAnnotationMetadata();
// 获取当前正在扫描的类的信息 ,类型,接口是什么......
ClassMetadata classMetadata = metadataReader.getClassMetadata();
// 获取当前类资源(类的路径)
Resource resource = metadataReader.getResource();
String className = classMetadata.getClassName();
String superClassName = classMetadata.getSuperClassName();
System.out.println("当前扫描类的类名---> " +className);
// System.out.println("当前扫描类的父类---> " +superClassName);
if (className.contains("er")) { // 类名中包含er,就包含进去
return true;
}
return false;
}
}
测试结果:
3. @Scope
在IOC容器中,我们在IOC容器中添加的实例默认都是单实例的 。也可以通过 @Scope 来指定它的作用范围。
@Scope 取值有 以下几种:
prototype 多实例
singleton 单实例 (默认)
request :同一次请求创建一个实例
session: 同一个session创建一个请求
后两种并不常用,所以这里仅仅说明前两种。
3.1. singleton
默认为单实例的(@Scope("singleton")),测试如下:
@Bean("person")
@Scope
public Person person() {
System.out.println("给容器中添加Person");
return new Person("张三", 25);
}
@Test
public void test02() {
ApplicationContext context = new AnnotationConfigApplicationContext(MainConfig2.class);
System.out.println("IOC容器创建完成......");
Object bean1 =context.getBean("person");
Object bean2 =context.getBean("person");
System.out.println(bean1 ==bean2);
}
运行结果如下:
另外,从测试结果还能看出 在单实例情况下,当IOC容器创建时,同时也会调用方法创建对象并放到IOC容器中,以后每次获取,就是直接从容器中拿。
3.1.1 懒加载 @Lazy
由于单实例bean默认在容器启动时创建组件实例,可以设置懒加载,使容器启动时不创建实例,而在第一次使用(获取)实例时创建,并初始化,注意:懒加载仅仅适用于单实例。
测试如下:
@Configuration
public class MainConfig3 {
@Bean("person")
@Lazy
public Person person() {
System.out.println("给容器中添加Person");
return new Person("张三", 25);
}
}
@Test
public void test03() {
ApplicationContext context = new AnnotationConfigApplicationContext(MainConfig3.class);
System.out.println("IOC容器创建完成......");
// Object bean1 =context.getBean("person");
// Object bean2 =context.getBean("person");
// System.out.println(bean1 ==bean2);
}
测试结果:
在测试如下:
@Test
public void test03() {
ApplicationContext context = new AnnotationConfigApplicationContext(MainConfig3.class);
System.out.println("IOC容器创建完成......");
Object bean1 =context.getBean("person");
Object bean2 =context.getBean("person");
System.out.println(bean1 ==bean2);
}
测试结果如下:
3.2 prototype
当@Scope的值设置为 prototype 时,则添加的组件在容器中是多实例的,IOC容器创建时,并不会调用方法创建该组件的实例,而是每当使用到该组件时,就去创建一个实例。
测试如下:
@Bean("person")
@Scope("prototype")
public Person person() {
System.out.println("给容器中添加Person");
return new Person("张三", 25);
}
@Test
public void test02() {
ApplicationContext context = new AnnotationConfigApplicationContext(MainConfig2.class);
// System.out.println("IOC容器创建完成......");
//
// Object bean1 =context.getBean("person");
// Object bean2 =context.getBean("person");
// System.out.println(bean1 ==bean2);
}
执行测试方法,会发现控制台并没有输出 创建Person的实例的语句(给容器中添加Person)。
在进行测试如下:
@Test
public void test02() {
ApplicationContext context = new AnnotationConfigApplicationContext(MainConfig2.class);
System.out.println("IOC容器创建完成......");
Object bean1 =context.getBean("person");
Object bean2 =context.getBean("person");
System.out.println(bean1 ==bean2);
}
输出如下:
4. @Conditional
该条件用于 按照一定的条件进行判断,满足条件进行判断,满足条件给容器中注册bean
其值是一个 Condition数组, Condition是一个包含一个方法的接口:
boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata);
context : 判断条件能使用到的上下文(环境) , metadata 注释信息。
使用如下:
@Configuration
public class MainConfig4 {
@Bean("Windows")
public Person person01() {
return new Person("Windows", 50);
}
@Bean("Linux")
public Person person02() {
return new Person("Linux", 40);
}
}
假如在配置类中向容器中注入两个Person实例,分别为 Windows和Linux ,想要实现根据操作系统(Windows或Linux)不同而分别注入不同的实例。那么,可以这样做:
首先编写两个类实现Condition类,并实现 matches 方法。WindowsCondition和 LinuxCondition(与WindowsCondition代码基本一致,不再重复贴)
public class WindowsCondition implements Condition {
@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
// 1. 能获取ioc 使用的beanFactory ,装配的工厂。
ConfigurableListableBeanFactory factory = context.getBeanFactory();
// 2. 获取类加载器
ClassLoader loader = context.getClassLoader();
//3. 获取当前环境信息,包括环境变量、虚拟机变量等
Environment environment = context.getEnvironment();
//4. 获取到bean定义的注册类
BeanDefinitionRegistry registry = context.getRegistry();
String operationName = environment.getProperty("os.name");
// System.out.println(operationName);
if (operationName.contains("Windows")) {
return true;
}
return false;
}
然后在配置类中使用 @Conditional 注解引入。
@Conditional({WindowsCondition.class})
@Bean("Windows")
public Person person01() {
return new Person("Windows", 50);
}
@Conditional({LinuxCondition.class})
@Bean("Linux")
public Person person02() {
return new Person("Linux", 40);
}
编写测试类如下:
@Test
public void test04() {
ApplicationContext context = new AnnotationConfigApplicationContext(MainConfig4.class);
System.out.println("IOC容器创建完成......");
Environment environment = context.getEnvironment();
String operationName =environment.getProperty("os.name");
System.out.println("操作系统名称:"+operationName);
Map<String, Person> beans = context.getBeansOfType(Person.class);
System.out.println(beans);
}
输出结果如下:
另外 @Conditional 也可以标注在类上 ,那么此时的含义为满足当前条件,该类中配置的所有bean注册才会生效。
@Configuration
@Conditional({LinuxCondition.class})
public class MainConfig2 {
@Bean("person")
public Person person() {
System.out.println("给容器中添加Person");
return new Person("张三", 25);
}
}
编写测试方法:
@Test
public void test04() {
ApplicationContext context = new AnnotationConfigApplicationContext(MainConfig2.class);
System.out.println("IOC容器创建完成......");
Environment environment = context.getEnvironment();
String operationName =environment.getProperty("os.name");
System.out.println("操作系统名称:"+operationName);
Map<String, Person> beans = context.getBeansOfType(Person.class);
System.out.println(beans);
}
测试结果:
5. 向容器中注册组件相关注解
给容器注册组件除了 上面的
包扫描+组件标注注解(@Controller /@Service...) ,
@Bean[导入的第三方包里面的组件]
也可以通过 @Import 快速给容器中导入一个组件 ,下面说明@Import用法。
5.1 @Import (要导入到容器中的组件)
该方式会自动向容器中注册组件,id默认为全类名
@Configuration
public class MainConfig5 {
}
....
public class Color {
}
.....
public class Red {
}
编写测试代码:
private void printBeans(ApplicationContext context) {
String[] beanDefinitionNames = context.getBeanDefinitionNames();
for (String name:beanDefinitionNames) {
System.out.println(name);
}
}
@Test
public void test05(){
ApplicationContext context = new AnnotationConfigApplicationContext(MainConfig5.class);
printBeans(context);
}
输出结果如下:
由于此时 配置类中并没有注册 Red 和Color ,所以并没有打印出其名称。修改配置类MainConfig5 ,加上@Import注解:
@Configuration
@Import({Color.class, Red.class})
public class MainConfig5 {
}
测试结果:
5.2 ImportSelector
ImportSelector 导入选择器, 是一个接口,里面包含一个 selectImports 方法, 通过实现该方法能实现返回 需要导入类的全类名,进而向容器注册组件。SpringBoot中该方式用的也比较多。
用法: 编写类实现 ImportSelector接口,实现里面的 selectImports:
public class MyImportSelector implements ImportSelector {
// 返回值,就是导入到容器中的组件全类名
// AnnotationMetadata : 当前标注 @Import注解的类的所有注解信息
@Override
public String[] selectImports(AnnotationMetadata importingClassMetadata) {
return new String[]{"com.zhao.springboot.bean.Blue","com.zhao.springboot.bean.Yellow"};
//方法不要返回null
}
}
测试:
@Configuration
@Import({Color.class, Red.class, MyImportSelector.class})
public class MainConfig5 {
}
测试结果如下:
注意:MyImportSelector 并不会被注册到容器中,该类实现了 ImportSelector,仅仅是把方法返回值中的全类名注册到容器中。
5.3 ImportBeanDefinitionRegistrar
也可以手动注册组件,通过实现 ImportBeanDefinitionRegistrar中的 registerBeanDefinitions 方法来注册 ,用法如下:
public class MyImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {
/**
*
* @param importingClassMetadata :当前类的注解信息
* @param registry : BeanDefinition注册类,把所有需要添加到容器中的bean
*/
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
boolean redDefinition = registry.containsBeanDefinition("com.zhao.springboot.bean.Blue");
boolean blueDefinition = registry.containsBeanDefinition("com.zhao.springboot.bean.Yellow");
if (redDefinition && blueDefinition){
// 指定bean定义信息,(Bean 类型..等)
BeanDefinition definition = new RootBeanDefinition(RainBow.class);
// definition.setScope();
// 指定bean名称
// String beanName, BeanDefinition beanDefinition
// 注册一个bean
registry.registerBeanDefinition("rainBow",definition);
}
}
}
测试如下:
5.4 使用Spring 提供的FactoryBean (工厂bean)注册
使用方式如下:
public class ColorFactoryBean implements FactoryBean<Color> {
// 返回一个Color对象,这个对象将会被添加到容器中
@Override
public Color getObject() throws Exception {
System.out.println("ColorFactoryBean........getObject....");
return new Color();
}
@Override
public Class<?> getObjectType() {
return Color.class;
}
/*
是否是单例? true:单例,在容器中保存一份,
false: 多实例, 每次获取都会创建一个新的bean
*/
@Override
public boolean isSingleton() {
return true;
}
}
将 ColorFactoryBean 注册到容器中:
@Bean
public FactoryBean colorFactoryBean() {
return new ColorFactoryBean();
}
测试:
@Test
public void test06(){
ApplicationContext context = new AnnotationConfigApplicationContext(MainConfig5.class);
// 工厂bean 获取的是 调用 getObject创建的对象
Object colorFactoryBean = context.getBean("colorFactoryBean");
Object colorFactoryBean1 = context.getBean("colorFactoryBean");
System.out.println(colorFactoryBean.getClass().getName());
System.out.println(colorFactoryBean == colorFactoryBean1);
// 如果想获取 工厂bean本身 ,可以使用 &
Object colorFactoryBean3 = context.getBean("&colorFactoryBean");
System.out.println(colorFactoryBean3.getClass().getName());
}
测试结果:
6. Bean生命周期相关注解
容器管理bean的生命周期,我们可以自定义初始化和销毁方法,容器在bean进行到当前生命周期的时候来调用我们自定义的初始化和销毁方法。
1) 指定初始化和销毁方法;
通过@Bean指定init-method和destory-method 。具体用法如下:
public class Car {
public Car() {
System.out.println("Car...constructor.....");
}
public void init(){
System.out.println("Car.....init.....");
}
public void destroy() {
System.out.println("Car......destroy....");
}
}
.....
@Configuration
public class MainConfigOfLifeCycle {
@Bean(initMethod ="init",destroyMethod ="destroy")
public Car car() {
return new Car();
}
}
编写测试方法:
注意: 单实例的销毁发生在容器关闭的时候,而当设置为多实例时,容器不会管理这个bean,容器不会调用销毁方法。
@Bean(initMethod ="init",destroyMethod ="destroy")
@Scope("prototype")
public Car car() {
return new Car();
}
......
@Test
public void test01() {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(MainConfigOfLifeCycle.class);
System.out.println("容器创建完成.......");
context.getBean("car");
context.close();
}
测试结果:
2) 通过让Bean 实现 InitializingBean(定义初始化逻辑), DisposableBean(定义销毁逻辑)
public class Cat implements InitializingBean, DisposableBean {
public Cat() {System.out.println("Cat...constructor.....");}
@Override
public void destroy() {
System.out.println("Cat......destroy....");
}
@Override
public void afterPropertiesSet() {
System.out.println("Cat.....afterPropertiesSet.....");
}
}
......
@Bean
public Cat cat(){
return new Cat();
}
测试结果:
3) 可以使用 JSR250
@PostConstruct :在bean创建完成并属性赋值完成,来执行初始化方法。
@PreDestroy:在容器销毁bean之前,通知我们进行销毁工作。
使用如下:
public class Dog {
public Dog() {
System.out.println("Dog...constructor");
}
@PreDestroy
public void destroy() {
System.out.println("Dog......@PreDestroy....");
}
@PostConstruct
public void init() {
System.out.println("Dog.....@PostConstruct.....");
}
}
.....
@Bean
public Dog dog() {
return new Dog();
}
测试结果如下:
4) 使用 BeanPostProcessor【Interface】
bean的后置处理器。在bean初始化前后进行一些处理工作。即在afterPropertiesSet 或init-method 前后进行一系列的操作。
有以下两个api :
// 在初始化之前工作
Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException;
//在初始化之后工作
Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException;
使用如下:
@Component
public class MyBeanPostProcessor implements BeanPostProcessor {
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
// bean : the new bean instance
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;
}
}
注意:要将实现类加入到bean容器。测试结果如下:
debug查看该执行流程,发现 执行初始化方法的时候(invokeInitMethods) ,先调用 applyBeanPostProcessorsBeforeInitialization,执行完初始化方法后又执行了 applyBeanPostProcessorsAfterInitialization:
执行流程:
doCreateBean --> createBeanInstance--> initializeBean--> applyBeanPostProcessorsBeforeInitialization -->invokeInitMethods--> applyBeanPostProcessorsAfterInitialization ...(AbstractAutowireCapableBeanFactory)
7. @Value
@Value 为属性赋值 , 支持 : 1) 基本数值 2) 可以写 SpEL ;#{ } 3) 可以写${} ;取出配置文件的值(在环境变量里面的值)
使用如下:
public class Student {
@Value("李四")
private String name;
@Value("#{25-5}")
private Integer age;
// ... 省略 getter 、setter、constructor 、toString
}
打印出该容器中所有bean ,结果如下:
若需要使用 ${}取出配置文件中的值,需要使用到另一个注解: @PropertySource ,该注解用于指定配置文件的位置,从而可以使用${} 取出配置文件中的值。下面说明用法:
给Student类中添加一个nickName的字段, 此时由于没有赋值,重新测试,打印出容器中的bean ,显示该字段为null:
写一个配置文件(Student.properties):
student.nickName=小灰灰
在配置类上进行修改:
@Configuration
@PropertySource(value={"classpath:/student.properties"}) // 指定配置文件的位置
public class MainConfigOfPropertyValues {
@Bean
public Student student() {
return new Student();
}
}
测试:
ConfigurableEnvironment environment = context.getEnvironment();
String property = environment.getProperty("student.nickName");
8. 自动装配的相关注解
8.1 @Autowired :自动注入
用法如下:
@Service
public class BookService {
@Autowired
private BookDao bookDao;
// toString 省略
}
.....
@Repository
public class BookDao {
private String label = "1";
// getter、setter、toString省略
}
.....
@Configuration
@ComponentScan({"com.zhao.springboot.controller", "com.zhao.springboot.service", "com.zhao.springboot.dao"})
public class MainConfigOfAutowired {
}
测试代码:
@Test
public void test01() {
BookService bookService = context.getBean(BookService.class);
System.out.println(bookService);
}
测试结果:
注意:
(1) 默认优先按照类型去容器中找对应的组件:applicationContext.getBean(BookDao.class);找到就赋值
(2)如果找到了多个相同类型的组件,在将属性的名称作为组件的id去容器中查找
applicationContext.getBean("bookDao");
(3) 使用 @Qualifier("bookDao"):使用@Qualifier指定需要装配的组件的id,而不是使用属性名
(4) 自动装配默认一定要将属性赋值好,没有就会报错,也可以使用 @Autowired(required =false),设置不必须装配
(5) @Primary:让spring 进行自动装配的时候,默认使用首选的bean,也可以继续使用@Qualifier指定需要装配的bean的名字
测试如下:
1)容器中存在两个bean
@Configuration
@ComponentScan({"com.zhao.springboot.controller", "com.zhao.springboot.service", "com.zhao.springboot.dao"})
public class MainConfigOfAutowired {
@Bean("bookDao2")
public BookDao bookDao() {
BookDao dao = new BookDao();
dao.setLabel("2");
return dao;
}
}
运行测试方法进行测试:
2) 使用@Primary指定默认装配的bean
@Primary
@Bean("bookDao2")
public BookDao bookDao() {
BookDao dao = new BookDao();
dao.setLabel("2");
return dao;
}
测试结果:
3)@Qualifier指定 要装配的组件的id
在 2)的基础上进入修改:
@Service
public class BookService {
@Autowired
@Qualifier("bookDao")
private BookDao bookDao;
// toString 省略
}
测试结果:
8.2 Spring 还支持使用 @Resource (JSR250) 和@Inject(JSR330)[JAVA规范的注解]
@Resource可以和 @Autowired 一样实现自动装配功能,默认是安装组件名称进行装配的。但是没有能支持@Primary,也没有支持@Autowired(required=false)的功能。其使用如下:
@Service
public class BookService {
// @Autowired
// @Qualifier("bookDao")
@Resource(name ="bookDao2")
private BookDao bookDao;
// toString 省略
}
测试结果:
如果想要使用@Inject, 还需要引入一个依赖:
<dependency>
<groupId>javax.inject</groupId>
<artifactId>javax.inject</artifactId>
<version>1</version>
</dependency>
使用如下:
@Service
public class BookService {
// @Autowired
// @Qualifier("bookDao")
// @Resource(name ="bookDao2")
@Inject
private BookDao bookDao;
// toString 省略
}
测试如下:
引入了 配置类中使用@Bean注入的实例,是因为 该实例使用了@Primary注解,也即 @Inject 和@Autowired功能一样,但是@Inject没有 @Autowired的required=false的功能。
8.3 @Autowired 标注的位置
@Autowired 可以标注在: 构造器、参数、方法、 属性 ,这些都是从容器中获取组件参数的值。
1) 标注在方法位置:用的最多的就是 @Bean +方法参数,参数从容器中自动获取,默认不写@Autowired
2) 标注在构造器上: 如果组件只有一个有参构造器,那么这个有参构造器的@AutoWired可以省略,参数位置的组件还是可以从容器中自动获取
3) 标注在方法中的参数上
@Component
public class Boss {
// @Autowired
private Car car;
// @Autowired // 可以直接标注在方法上
public void setCar(Car car) {
this.car = car;
}
// 省略 getter 和 toString方法
// @Autowired // 可以标注在构造器上
//public Boss(@Autowired Car car) { // 如果组件只有一个有参构造器,那么这个有参构造器的@Autowired可以省略,参数位置的组件还是可以从容器中自动获取
public Boss( Car car){
this.car = car;
}
}
@Bean
// public Boss boss(@Autowired Car car) { //Car只有一个有参构造器,所以可以省略 @Autowired
public Boss boss( Car car) {
return new Boss(car);
}
8.4 自定义组件想要使用Spring 容器底层的一些组件(ApplicationContext,BeanFactory,xxxx)
如果想要使用Spring容器底层组件,只需要让自定义组件 实现xxxAware即可。在创建对象的时候,会调用接口规定的方法注入相关组件。顶层 接口为 Aware(org.springframework.beans.factory)
使用如下:
public class Cup implements ApplicationContextAware, BeanNameAware, EmbeddedValueResolverAware {
private ApplicationContext applicationContext;
@Override
public void setBeanName(String name) {
System.out.println("当前bean的名称: " + name);
}
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
System.out.println("传入的IOC 容器: " + applicationContext);
this.applicationContext = applicationContext;
}
@Override
public void setEmbeddedValueResolver(StringValueResolver resolver) { //解析字符串
String resolveStringValue = resolver.resolveStringValue("你好,${os.name} 我是 #{20*18}");
System.out.println("解析的字符串:" + resolveStringValue);
}
}
.......
@Bean
public Cup cup() { // 向容器中注入 bean
return new Cup();
}
编写测试代码:
@Test
public void test02(){
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(MainConfigOfAutowired2.class);
System.out.println(context);
}
测试结果如下:
9. @Profile
@Profile 是Spring为我们提供的,具有可以根据当前环境,动态的激活和切换一系列组件的功能。该注解指定组件在哪个环境的情况下才能被注册到容器中,若不指定,任何环境下都能注册这个组件。
1) 加了环境标识的bean,只有这个环境被激活的时候才能注册到容器中,默认是default环境
2) 标注在 配置类上,只有在指定的环境的时候,整个配置类里面的所有配置才能开始生效
使用如下:(以多数据源为例。引入c3p0依赖)
@Configuration
@PropertySource("classpath:/dbConfig.properties")
public class MainConfigOfProfile implements EmbeddedValueResolverAware {
@Value("${db.user}")
private String user;
private String driverClass;
private StringValueResolver resolver;
@Profile("default")
// @Profile("test")
@Bean("testDataSource")
public DataSource dataSourceTest(@Value("db.password") String password) throws Exception {
return getDataSource(password,"jdbc:mysql://localhost:3306/test");
}
//@Profile("dev")
@Bean("devDataSource")
public DataSource dataSourceDev(@Value("db.password") String password) throws Exception {
return getDataSource(password,"jdbc:mysql://localhost:3306/dev");
}
@Profile("prod")
@Bean("prodDataSource")
public DataSource dataSourceProd(@Value("db.password") String password) throws Exception {
return getDataSource( password ,"jdbc:mysql://localhost:3306/prod");
}
private DataSource getDataSource(String password, String url) throws Exception {
ComboPooledDataSource dataSource = new ComboPooledDataSource();
dataSource.setJdbcUrl(url);
dataSource.setUser(user);
dataSource.setPassword(password);
dataSource.setDriverClass(driverClass);
return dataSource;
}
@Override
public void setEmbeddedValueResolver(StringValueResolver resolver) {
this.resolver = resolver;
this.driverClass = resolver.resolveStringValue("${db.driverClass}");
}
}
该配置类写的有点复杂,也是为了练习前面的注解 ,测试代码如下:
@Test
public void test01() {
AnnotationConfigApplicationContext context =
new AnnotationConfigApplicationContext(MainConfigOfProfile.class);
String[] names = context.getBeanNamesForType(DataSource.class);
for (String name : names) {
System.out.println(name);
}
}
测试结果如下:
激活prod环境测试如下:
AnnotationConfigApplicationContext context =
new AnnotationConfigApplicationContext();
context.getEnvironment().setActiveProfiles("prod"); //允许设置多个活动的环境
context.register(MainConfigOfProfile.class);
context.refresh();
String[] names = context.getBeanNamesForType(DataSource.class);
for (String name : names) {
System.out.println(name);
}
.....
测试如下:
注意:出现devDataSource 数据源是因为,配置类上该数据源并没有指定环境,所以默认任何环境下都注册了。
也可以通过 指定虚拟机参数的方法激活环境 , 加上 -Dspring.profiles.active=prod
10. 切面相关注解
前置通知(@Before) :在目标方法执行前执行
后置通知(@After): 目标方法运行结束后执行(无论正常还是异常结束)
返回通知(@AfterReturning) : 在目标方法正常返回后执行
异常通知(@AfterThrowing): 在目标方法出现异常后执行
环绕通知(@Around):动态代理,手动推进目标方法运行(joinPoint.procced())
@Aspect :告诉spring这是一个切面类
@EnableAspectJAutoProxy :开启基于注解的aop模式
使用方法步骤如下:
假如有需求如下: 做除法运算,并返回结果。 并且 在业务逻辑执行的时候将日志进行打印(方法运行前、运行结束、出现异常)
1)首先定义一个业务类(默认已经导入了aop相关依赖):
public class MathCalculator {
public int div(int i, int j) {
System.out.println("MathCalculator..div...");
return i / j;
}
}
2)定义日志切面
@Aspect//指明这个一个切面类
public class LogAspects {
// @Before 在目标方法之前切入:切入点表达式(指定在哪个方法切入)
// @Before("com.zhao.springboot.aop.MathCalculator.div(int ,int )")
// @Before("com.zhao.springboot.aop.MathCalculator.*(..)")
@Before("pointCut()")
public void logStart(JoinPoint joinPoint) { // 注意:当方法有多个参数时,JoinPoint必须放置第一位置
Object[] args = joinPoint.getArgs();
System.out.println(joinPoint.getSignature().getName() + " 除法运行...参数列表: {" + Arrays.asList(args) + "}");
}
@After("com.zhao.springboot.aop.LogAspects.pointCut()")
public void logEnd(JoinPoint joinPoint) {
System.out.println(joinPoint.getSignature().getName() + " 除法结束");
}
@AfterReturning(value = "pointCut()", returning = "result")
public void logReturn(Object result) {
System.out.println("除法正常返回。。。运行结果: {" + result + "}");
}
@AfterThrowing(value = "pointCut()", throwing = "ex")
public void logException(Exception ex) {
System.out.println("除法异常。。。异常信息: {" + ex + "}");
}
// 抽取公共的切入点表达式
// 1. 本类引用 pointCut()
//2. 其他的切面引用 com.zhao.springboot.aop.LogAspects.pointCut()
@Pointcut("execution(public int com.zhao.springboot.aop.MathCalculator.*(..))")
public void pointCut() {
}
}
3) 将切面类和业务逻辑类加入到容器中,并开启基于注解的aop模式
@EnableAspectJAutoProxy // 开启注解
@Configuration
public class MainConfigOfAop {
@Bean
public MathCalculator mathCalculator() {
return new MathCalculator();
}
@Bean
public LogAspects logAspects() {
return new LogAspects();
}
}
测试代码:
@Test
public void test01() {
// 1. 不要自己创建对象 ,需要使用spring创建的实例,才能使用到spring的切面
// MathCalculator calculator =new MathCalculator();
// calculator.div(1,1);
MathCalculator calculator = context.getBean(MathCalculator.class);
// calculator.div(1, 1);
calculator.div(1, 1);
}
测试结果如下:
注意:切面类上必须加上 @Aspect 表名该类为一个切面类,并且 配置类上也需要使用 @EnableAspectJAutoProxy
开启基于注解的aop模式,此时切面才会生效。 除外次,要使用Spring容器创建的bean实例,才会触发切面。
11 与事务相关
@Transactional //标注在方法上,表示当前方法是一个事务方法
@EnableTransactionManagement // 开启事务管理功能
使用步骤如下:
1. 配置数据源、JdbcTemplate(Spring 提供的简化数据库操作的工具)操作数据
2. 给方法上标注@Transactional 表示当前方法是一个事务方法
3. 开启事务管理功能。
4. 往容器中 配置一个事务管理器,并管理 dataSource
代码使用如下:
@Configuration
@PropertySource("classpath:/dbConfig.properties")
@ComponentScan("com.zhao.springboot.config.tx")
@EnableTransactionManagement //开启事务管理功能
public class TxConfig {
@Value("${db.user}")
private String user;
@Value("${db.password}")
private String password;
@Value("${db.driverClass}")
private String driverClass;
@Bean
public DataSource comboPooledDataSource() throws Exception {
ComboPooledDataSource dataSource = new ComboPooledDataSource();
dataSource.setJdbcUrl("jdbc:mysql://localhost:3306/test");
dataSource.setUser(user);
dataSource.setPassword(password);
dataSource.setDriverClass(driverClass);
return dataSource;
}
// 配置JdbcTemplate
@Bean
public JdbcTemplate jdbcTemplate() throws Exception {
JdbcTemplate jdbcTemplate = new JdbcTemplate();
jdbcTemplate.setDataSource(comboPooledDataSource());
return jdbcTemplate;
}
// 注册事务管理器
@Bean // PlatformTransactionManager 为事务管理器的顶层接口
public PlatformTransactionManager transactionManager() throws Exception {
return new DataSourceTransactionManager(comboPooledDataSource());
}
}
.... dao层
@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);
}
}
.... service层
@Service
public class UserService {
@Autowired
private UserDao userDao;
@Transactional // 标注 这个一个事务方法
public void addUser(){
userDao.insert();
System.out.println("插入成功");
int i=1/0;
}
}
测试代码:
@Test
public void test01() {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(TxConfig.class);
UserService userService = context.getBean(UserService.class);
userService.addUser();
}
测试前数据库数据:
测试后:
数据库数据: