Spring - 注解
一:何为注解
指的是在类或者方法上加入特定的注解,完成特定功能的开发的
在java中,注解的声明类型是@interface
为什么要使用注解 -> 代码简洁,开发速度大大提高,同时注解开发时Spring框架的潮流和趋势
注解可以替换掉XML这种配置的形式,可以大大的简化配置
注解还可以替换掉接口来维持实现调用双方的契约性
Spring基于注解配置后,依然可以解耦合,因为可以使用配置为案件对注解内容进行覆盖
二:Spring 常用注解
1:对象创建的相关注解
1.1:@Component
简单来说核心作用就是:替换掉原有Spring配置文件中的<bean>
标签
@Component注解是一个通用的注解,用于标记一个类,该类充当Spring应用程序上下文中的组件。
@Component注解表明这个类是一个组件,并且应该由Spring容器管理。
这个注解是Spring框架的核心注解之一,可以用于标记任何类型的组件。
@Component注解不仅可以用于标记普通Java类,还可以用于标记其他类型的组件,例如控制器、拦截器、过滤器等。
这个注解的优点是,它很灵活,并且可以在任何情况下使用。
如果您想在Spring应用程序中创建一个组件,但不确定使用哪个注解,那么@Component注解是一个很好的选择。
对于<bean>
中的id属性,component注解提供了默认的设置方式【首单词首字母小写】
也可以通过指定component注解的value属性,手动设置对应的id值
@Component(value="uuuu") // 手动指定id = uuuu
如果要使用Spring配置文件覆盖注解配置的内容,要保证id值和class值和注解中的保持一致
<bean id="uuuu" class="com.cui.xxx.User"/>
1.2:衍生注解及其区别
@Service
, @Repository
和@Controller
是@Component
的衍生注解
除了在功能和@Component一样,还是有一些区别的
@Component spring基础的注解,被spring管理的组件或bean
@Repository 用于持久层,数据库访问层
@Service 用于服务层,处理业务逻辑
@Controller 用于呈现层,(spring-mvc)
主要说一下@Service和@Controller两个注解,这两个注解虽然在Spring中只表示两个组件,可以互相转换,但是在SpringMVC中却不行,原因如下:
// SpringMVC中有一个RequestMappingHandlerMapping
// RequestMappingHandlerMapping 看这个名就可以知道他的意思,请求映射处理映射器。
// 该类间接实现了InitializingBean方法, bean初始化后执行回调afterPropertiesSet方法
// 里面调用initHandlerMethods方法进行初始化handlermapping。
//类有没有加Controller的注解
protected boolean isHandler(Class<?> beanType) {
// 有@Controller注解或者@RequestMapping注解
return (AnnotatedElementUtils.hasAnnotation(beanType, Controller.class) ||
AnnotatedElementUtils.hasAnnotation(beanType, RequestMapping.class));
}
protected void initHandlerMethods() {
//所有的bean
String[] beanNames= applicationContext().getBeanNamesForType(Object.class);
for (String beanName : beanNames) {
Class<?> beanType = obtainApplicationContext().getType(beanName);
//有Controller注解的bean
if (beanType != null && isHandler(beanType)) {
detectHandlerMethods(beanName);
}
}
handlerMethodsInitialized(getHandlerMethods());
}
1.3:@Scope
控制简单对象的创建的次数【复杂对象是FactoryBean接口的实现】
如果不添加@Scope,将使用默认值singleton[单例模式,只创建一次]
@Scope(value = "singleton") // 单例模式 -> 在Spring容器的创建时创建一个,剩下的调用都用这一个
@Scope(value = "prototype") // 多例模式 -> 每一次声明都创建一个新的
上面等价于xml中的配置:
<bean id="xxx" class="xxx" scope="singleton|prototype"></bean>
1.4:@Lazy
因为单例中默认是在Spring容器创建的时候创建的单例对象,@Lazy可以将指定的单例bean延迟到对象声明的时候再创建这个bean
@Lazy
上面等价于xml中的配置:
<bean id="xxx" class="xxx" lazy="true"></bean>
1.5:@ComponentScan
1.5.1:XML方式
<context:component-scan base-package="com.cui"/>
作用:让Spring框架在设置的包及其子包中扫描对应的注解,使其生效
可以使用exclude-filter对一些包进行排除
可以使用include-filter对一些包进行包含声明
1.5.2:注解方式
@ComponentScan在配置bean中进行,等同于XML配置文件中的<context:component-scan>
标签
存在目的也是对相关的注解进行扫描
只要在指定的包下标注了 @Controller、@Service、@Repository、@Component就会被装配到IOC容器中
使用注解方式说明
在配置类文件中加入注解,声明要扫描的包
@Configuration // 声明这是一个配置类,相当于一个配置文件
@ComponentScan(value = {"要扫描的包的名称", "要扫描的包的名称"})
public class PersonConfig {
@Bean // 给容器注册一个bean, 类型为返回值类型。bean.xml中的id对应这里的函数的名称
public Person person {
return new Person("Jone", 18);
}
}
如果要指定包含哪些,或者排除哪些,可以使用对应的属性
@ComponentScan(value = {"要扫描的包的名称", "要扫描的包的名称"},
includeFilters = {
// 按照注解类型进行过滤,@Controller类型的注解,或者Service类型的注解都会包含
@Filter(type = FilterType.ANNOTATION, classes = {Controller.class, Service.class}),
// 按照指定类型进行过滤,BookService的子类或者实现类都会包含
@Filter(type = FilterType.ASSIGNABLE_TYPE, classes = {BookService.class})
},
useDefaultFilters = false)
↪️excludeFilters同理,没有useDefaultFilters = false
@Filter注解常用规则
public enum FilterType {
ANNOTATION, // 按照注解进行过滤
ASSIGNABLE_TYPE, // 按照指定的类型进行过滤
ASPECTJ, // 使用 ASPECTJ 表达式 (不使用)
REGEX, // 使用正则表达式(regexp)
CUSTOM; // 自定义的过滤规则,--> 需要实现TypeFilter中的match方法
private FilterType() {
}
}
1.6:@Configuration
如果使用传统的spring方式,需要建立一个对应的bean.xml
<?xml version = "1.0" encoding = "UTF8"?>
<beans xxx>
<bean id = "方便调度起的名,唯一性" class = "s实体类的路径">
<property name = "age" vlaue = 18/>
<property name = "name" value = "Jone"/>
</bean>
</beans>
使用ClassPathXmlApplicationContext进行装配
public class MainTest {
public static void main(String[] args) {
// 加入到IOC中
ApplicationContext app = new ClassPathXmlApplicationContext("bean.xml的路径");
// 容器在手,天下我有
Person person = app.getBean("person");
...;
// 查看Person类下有哪些bean名字
String[] beanNames = app.getBeanNameByType(Person.class);
for (String beanName: beanNames) {
sout(beanName);
}
}
}
使用@Configuration & @Bean
写一个配置类
@Configuration // 声明这是一个配置类,相当于一个配置文件
public class PersonConfig {
@Bean // 给容器注册一个bean, 类型为返回值类型。bean.xml中的id对应这里的函数的名称
public Person person {
return new Person("Jone", 18);
}
}
AnnocationConfigApplicationContext装配调用
public class MainTest {
public static void main(String[] args) {
// 创建IOC
ApplicationContext app = new AnnocationConfigApplicationContext(PersonConfig.class);
// 容器在手,天下我有
Person person = app.getBean(Person.class);
...;
// 查看Person类下有哪些bean名字
String[] beanNames = app.getBeanNameByType(Person.class);
for (String beanName: beanNames) {
sout(beanName); // person 因为默认使用方法名作为id
}
}
}
修改id
可以使用 @Bean 中的value属性指定bean的名称,从而不使用默认的方法名
@Bean(value = "person01") // id = "person01"
public Person person {
return new Person("Jone", 18);
}
对应源码
// -------- @Bean ----------
package org.springframework.context.annotation;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.beans.factory.annotation.Autowire;
import org.springframework.core.annotation.AliasFor;
@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Bean {
// value & name互为别名,用来为bean设置对应的id,如果没有设置,将用函数的名称作为bean的id
@AliasFor("name")
String[] value() default {};
@AliasFor("value")
String[] name() default {};
// 默认不使用Autowired
Autowire autowire() default Autowire.NO;
// 初始化方法 & 销毁方法
String initMethod() default "";
String destroyMethod() default "(inferred)";
}
// ---------- @Configuration --------------
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface Configuration {
// 其实内部就是一个@Component注解
@AliasFor(
annotation = Component.class
)
String value() default "";
}
1.7:@Conditional
基本了解
按照条件给容器中注册bean
假如在上面的例子中, 在Windows系统的情况下注册id = bill的bean; 在linux系统中注册id = linus的bean
就可以先实现matches函数
// ----------- windows 系统的时候匹配成功, 返回true ------------
// 实现Condition接口,重写matches方法
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")) {
// 如果是windows系统就返回true,对应的bean将会被注册
return true;
}
// 其他系统返回false
return false;
}
}
// ----------- linux 系统的时候匹配成功, 返回true ------------
// 实现Condition接口,重写matches方法
public class LinuxCondition implements Condition {
@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
// 获得当前的环境
Environment environment = context.getEnvironment();
// 获取操作系统的属性值
String property = environment.getProperty("os.name");
if (property.contains("Linux")) {
// 如果是linux系统就返回true,对应的bean将会被注册
return true;
}
// 其他系统返回false
return false;
}
}
在注解处加上:
// 按照一定的条件进行判断,如果满足条件,就进行注册,否则不进行注册
// 如果是windows系统才注册这个bean
@Conditional({WindowsCondition.class})
@Bean("bill")
public Person person01() {
return new Person("bill gates", 62);
}
// 如果是linux系统才注册这个bean
@Conditional({LinuxCondition.class})
@Bean("linus")
public Person person01() {
return new Person("linus", 48);
}
如果@Conditional注解放在类上,就是说明满足条件时,这个类上的所有的bean才会被注册
1.8 @Import
快速的给容器中导入组件
@Import({要导入的组件})
public class Color {
...;
}
public class Red {
...;
}
@Import({Color.class, Red.class}) // 导入Color & Red组件,id是全类名
public class MainConfig {
// ...;
}
实现ImportSelector接口,实现selectImports方法,返回选择要导入的全类名的数组
ImportSelector接口源码:
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//
package org.springframework.context.annotation;
import org.springframework.core.type.AnnotationMetadata;
// AnnotationMetadata -> 当前标注@Import注解类的其他全部的注解的信息
public interface ImportSelector {
String[] selectImports(AnnotationMetadata var1);
}
🌰举一个简单的例子
// 实现这个方法
public class MyImportSelector implements ImportSelector {
// 返回值为导入容器的组件全类名
@Override
public String[] selectImports(AnnotationMetadata data) {
return new String[] {"com.bean.Blue", "com.bean.Yellow"}; // 全类名是这两个的将会被导入到容器中
}
}
@Import(MyImportSelector.class)
public class MainConfig {
// ...;
}
实现ImportBeanDefinitionRegistrar接口中的registerBeanDefinitions
ImportBeanDefinitionRegistrar接口源码:
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//
package org.springframework.context.annotation;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.core.type.AnnotationMetadata;
// AnnotationMetadata -> 当前类的注解的信息
// BeanDefinitionRegistry -> BeanDefinition注册类
public interface ImportBeanDefinitionRegistrar {
void registerBeanDefinitions(AnnotationMetadata var1, BeanDefinitionRegistry var2);
}
🌰举一个简单的例子
public class Rainbow {
// ...;
}
public class MyImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {
@Override
public void registerBeanDefinitions(AnnotationMetadata var1, BeanDefinitionRegistry var2) {
// 指定bean名称
RootBeanDefinition beanDefinition = new RootBeanDefinition(Rainbow.class);
// 编写代码逻辑,要插入的内容
// 如果容器中有id = red && id = blue的bean,就将rainbow注入
boolean def1 = var2.containsBeanDefinition("red");
boolean def2 = var2.containsBeanDefinition("blue");
if (def1 && def2) {
var2.registerBeanDefinition("rainbow", beanDefinition); // 第一个参数为注册后bean的id
}
}
}
@Import(MyImportBeanDefinitionRegistrar.class)
public class MainConfig {
// ...;
}
2:生命周期相关注解
这两个注解属于JSR520规范,不属于Spring管理范畴
2.1:@PostConstruct
Spring工厂在创建完成对象[完成依赖注入]之后,调用对象的初始化方法,完成对应的初始化操作:
- 初始化方法的提供:由程序员根据需求提供初始化方法,最终完成初始化操作
- 初始化方法的调用:由Spring工厂进行调用
在前面介绍了定义初始化方法有两种途径:
- 实现InitializingBean这个接口实现afterPropertiesSet这个方法
- 第二种方法是配置文件中配置普通方法
<bean id="product" class="xxx.Product" init-method="myInit"></bean>
🎉 如果既实现了InitializingBean接口,又指定了init-method方法那么接口的顺序 > init-method方法的顺序
而@PostConstruct就是和init-method的功能是类似的,它可以作用在方法上,然后成为指定bean的初始化方法
对于这个注解的使用,有如下几个要求:
- 该方法不能有任何参数,除非在拦截器的情况下,在这种情况下,它接受一个由拦截器规范定义的InvocationContext对象。
- 而且在拦截器的情况下,必须具有以下签名之一
void <METHOD>(InvocationContext)
Object <METHOD>(InvocationContext) throws Exception
- 如果不是作用在拦截器上,只能是如下签名:
void <Method>()
- PostConstruct应用的方法可以是public、protected、package private或private。
- 除了应用程序客户端之外,该方法绝对不能是静态的。方法可能是最终的
- 如果该方法抛出未检查的异常,则该类绝对不能投入服务,除非EJB可以处理异常甚至从中恢复。
@PostConstruct
public void doForInitMethod() {
System.out.println("haha");
}
2.2:@PreDestroy
Spring在调用ctx.close()
之后便会进行对象的销毁,
在前面介绍了对象的销毁也是有两种方式
- 第一种方式是通过实现DisposableBean接口中的
destroy()
方法 - 第二种方法是配置文件中配置普通方法为
destroy-method
属性
<bean id="product" class="xxx.Product" init-method="xxx" destory-method="myDestroy"></bean>
🎉 销毁方法只适用于单例模式的bean
🎉 所谓的销毁操作就是资源的释放操作io.close();connection.close()
而@PreDestroy就是和destroy-method的功能是类似的,它可以作用在方法上,在bean销毁之前提供额外的功能
对于@PreDestroy注意事项和@PostConsturct方法是一样的
@PreDestroy
public void doForPreDestroy() {
System.out.println("this a method of pre destroy for this bean");
}
3:注入相关注解
3.1:@Autowired
对于用户自定义类型,可以使用@Autowired进行注入
3.1.1:注意事项和性质
该注解默认是按照Bean的类型完成自动装配的,通常有如下几种用法:
性质一:
-
容器中只存在着一个指定类型的
Bean
:会直接按照类型完成Bean
的自动装配; -
当容器中同时存在着多个类型相同的
Bean
时,会默认再去按照bean
的名称进行装配
性质二:可以结合@Qualifier
注解完成装配
@Service
public class UserService {
/**
* 使用@Qualifier + @Autowired注解,标注注入名称为userDao,类型为UserDao的Bean
*/
@Qualifier("userDao")
@Autowired
private UserDao userDao2;
public void printUserDao() {
System.out.println(userDao2);
}
}
性质三:当容器中没有指定类型的Bean
时,但是可以指明忽略bean
// 使用required注解标识当没有找到bean时,如果想要忽略该Bean的自动装配,不会抛异常
@Autowired(required=false) // 可以忽略该bean的自动装配
@Qualifier("userDao")
private UserDao userDao;
性质四:同时存在多个名称和类型都相同的bean
,可以通过@Primary
来指定优先装配哪一个注解
@Configuration
@ComponentScan({"com.wb.spring.autowired"})
public class AutowiredConfig {
// 使用@Primary指定如果类型容器中有多个类型为UserDao的Bean,优先装配该Bean
@Primary
@Bean("userDao2")
public UserDao userDao() {
UserDao userDao = new UserDao();
userDao.setFlag("2");
return userDao;
}
}
性质五:@Qualifier
的优先级比@Primary
的高。
3.1.2:作用位置
@Autowired 字段
我们可以把 @Autowired 注解标注在类文件中的字段属性上,
通过这种方式,Spring 容器启动的时候会查找相应的 Bean ,然后通过反射的方式注入到这个字段中。
这种方式使用起来非常方便,而且对于字段的要求也比较低,它可以是 public , 也可以是 private 范围。
日常编程中,我一般是直接使用这种方式,简洁又便利。
@Controller
public class PayController {
@Autowired
private PayService aliPayService;
}
@Autowired 构造方法
我们可以使用类中的构造函数注入相应属性
构造方法里的传参,可以是一个,也可以是多个。如果是多个参数,那 Spring 一起都注入进来。
这种方式在注入多个属性的时候用起来还是比较方便的
@Controller
public class PayController {
PayService aliPayService;
OrderService orderService;
@Autowired
public PayController(PayService aliPayService) {
this.aliPayService = aliPayService;
}
// 可以多个参数
@Autowired
public PayController(PayService aliPayService, OrderService orderService) {
this.aliPayService = aliPayService;
this.orderService=orderService;
}
}
@Autowired 方法
方法注入,其实跟构造方法一样,也可以同时有多个传参,Spring 将会把这些属性一起注入。
@Controller
public class PayController {
PayService aliPayService;
OrderService orderService;
// 可以多个参数
@Autowired
public xxx(PayService aliPayService, OrderService orderService) {
this.aliPayService = aliPayService;
this.orderService=orderService;
}
}
@Autowired 多个bean
最后一种方式,Spring 可以使用 @Autowired 标注 Array (数组),Collections(集合),甚至是 Map (散列表)
通过这种命方式注入多个相同类型的 Bean。
// 比如这种方式,将 PayService 接口所有实现类注入到 Array 数组中。
@Controller
public class PayController {
@Autowired
PayService[] payServiceArray;
}
数组与集合中的 Bean 的顺序是根据 Spring 创建的顺序。
如果你想指定里面排序的优先级,那你就需要使用 @Order 或者 @Priority 指定一下优先级,值越小,对应的优先级越高
还可以将 PayService 接口所有实现类注入到 Map 中,其中里面的 key 就是Spring 的 Bean 的名字【key必须是string类型】
3.2:@Value
JDK类型的注入可以使用@Value注解实现
3.2.1:基本说明
可以使用@Value实现xml方式下的property标签中内容
<bean id = "person", class = "Person">
<property name = "name" value = "zhangSan"/>
<property name = "age" value = "liSi"/>
</bean>
现在如果使用注解的方式就是:
@Data
public class Person {
@Value("zhangSan")
private String name; // zhangSan
@Value("#{20 - 2}")
private int age; // 18
}
- 可以使用基本数据
- 可以使用SPEL -> #{}
关于属性名称
配置文件中的系统属性名用 驼峰标识 或 小写字母加中划线的组合,spring都能找到配置类中的属性名userName进行赋值。
由此可见,配置文件中的系统属性名,可以跟配置类中的属性名不一样。不过,有个前提,前缀susan.test必须相同。
⚠️ @Value注解中指定的系统属性名,必须跟配置文件中的相同。
susan.test.user-name=\u5f20\u4e09
@Service
@ConfigurationProperties(prefix = "susan.test")
public class UserService {
@Value("${userName}")
private String userName;
public String test() {
System.out.println(userName);
return userName;
}
}
乱码问题
在springboot的CharacterReader类中,默认的编码格式是 ISO-8859-1,该类负责.properties
文件中系统属性的读取。
如果系统属性包含中文字符,就会出现乱码。
对于这个问题,主要有下面几种解决方式:
- 如果可以,不使用properites文件,改用yaml
- 如果用的idea, 可以改变编码格式,进入setting -> editer -> fileEncoding中,都改成utf-8即可
乱码问题一般出现在本地环境,因为本地直接读取的.properties配置文件。
在dev、test、生产等环境,如果从zookeeper、apollo、nacos等配置中心中获取系统参数值,走的是另外的逻辑,并不会出现乱码问题。
默认值
如果配置了系统属性,userName就用配置的属性值。如果没有配置,则userName用默认值susan
非常的简单,在需要设置默认值的系统属性名后,加:
符号。紧接着,在:
右边设置默认值。
平时在使用 @Value 时,尽量都设置一个默认值。如果不需要默认值,宁可设置一个空。
@Value(value = "${susan.test.userName:susan}")
private String userName;
3.2.2:static变量
如何才能给静态变量注入系统属性值呢,就需要一些骚操作了:
提供一个静态参数的setter
方法,在该方法上使用@Value注入属性值,并且同时在该方法中给静态变量赋值。
@Service
public class UserService {
private static String userName;
@Value("${susan.test.userName}")
public void setUserName(String userName) {
UserService.userName = userName;
}
public String test() {
return userName;
}
}
3.2.3:变量类型
一:基本类型
众所周知,在Java中的基本数据类型有4类8种,然我们一起回顾一下:
- 整型:byte、short、int、long
- 浮点型:float、double
- 布尔型:boolean
- 字符型:char
相对应地提供了8种包装类:
- 整型:Byte、Short、Integer、Long
- 浮点型:Float、Double
- 布尔型:Boolean
- 字符型:Character
@Value注解对这8中基本类型和相应的包装类,有非常良好的支持,例如:
@Value("${susan.test.f:6.1}")
private Double f1;
@Value("${susan.test.g:false}")
private Boolean g1;
@Value("${susan.test.h:h}")
private Character h1;
二:数组
数组在日常开发中使用的频率很高,spring默认使用逗号分隔参数值
@Value("${susan.test.array:1,2,3,4,5}")
private int[] array;
⚠️ 如果在配置文件中用的空格进行分割,spring会自动把空格去掉,导致数据中只有一个值
⚠️ 定义数组时一定要注意属性值的类型,必须完全一致才可以
三:集合类(List/Set)
List是数组的变种,它的长度是可变的,而数组的长度是固定的
使用spring的EL表达式,可以完成List的使用
susan.test.list=10,11,12,13
// 如果没有,将默认是null
@Value("#{'${susan.test.list:}'.empty ? null : '${susan.test.list:}'.split(',')}")
private List<String> list;
可以不用 @Value 而使用强大的 @ConfigurationProperties
配置文件这样写:
susan.test.list[0]=10 susan.test.list[1]=11 susan.test.list[2]=12 susan.test.list[3]=13
需要定义一个MyConfig类:
@Configuration @ConfigurationProperties(prefix = "susan.test") @Data public class MyConfig { private List<String> list; }
然后在调用的地方这样写:
@Service public class UserService { @Autowired private MyConfig myConfig; public String test() { System.out.println(myConfig.getList()); return null; } }
四:Map
配置文件是这样的:
susan.test.map={"name":"苏三", "age":"18"}
设置默认值的代码如下:
@Value("#{'${susan.test.map:}'.empty ? null : '${susan.test.map:}'}")
private Map<String, String> map;
3.2.4:EL高端玩法
一:注入bean
@Value("#{roleService}")
private RoleService roleService;
通过这种方式,可以注入id为roleService的bean。
二:bean的变量和方法
通过EL表达式,@Value注解已经可以注入bean了。既然能够拿到bean实例,接下来,可以再进一步。
// 在RoleService类中定义了:成员变量、常量、方法、静态方法。
@Service
public class RoleService {
public static final int DEFAULT_AGE = 18;
public int id = 1000;
public String getRoleName() {
return "管理员";
}
public static int getParentId() {
return 2000;
}
}
可以在调用的地方这样写:
在UserService类中通过@Value可以注入:成员变量、常量、方法、静态方法获取到的值,到相应的成员变量中。
@Service
public class UserService {
@Value("#{roleService.DEFAULT_AGE}")
private int myAge;
@Value("#{roleService.id}")
private int id;
@Value("#{roleService.getRoleName()}")
private String myRoleName;
@Value("#{roleService.getParentId()}")
private String myParentId;
public String test() {
System.out.println(myAge);
System.out.println(id);
System.out.println(myRoleName);
System.out.println(myParentId);
return null;
}
}
三:静态类
有时我们需要调用静态类,比如:Math、xxxUtil等静态工具类的方法,该怎么办呢? -> 用T加括号。
示例1:
@Value("#{T(java.io.File).separator}")
private String path;
可以注入系统的路径分隔符到path中。
示例2:
@Value("#{T(java.lang.Math).random()}")
private double randomValue;
可以注入一个随机数到randomValue中。
四:逻辑运算
通过上面介绍的内容,我们可以获取到绝大多数类的变量和方法的值了。但有了这些值,还不够,我们能不能在EL表达式中加点逻辑?
- 拼接字符串:
@Value("#{roleService.roleName + '' + roleService.DEFAULT_AGE}")
private String value;
- 逻辑判断:
@Value("#{roleService.DEFAULT_AGE > 16 and roleService.roleName.equals('苏三')}")
private String operation;
- 三目运算:
@Value("#{roleService.DEFAULT_AGE > 16 ? roleService.roleName: '苏三' }")
private String realRoleName;
3.2.5:${}和#{}的区别
一:${}
主要用于获取配置文件中的系统属性值
如果在配置文件中找不到susan.test.userName的配置,也没有设置默认值,则启动项目时会报错。
@Value(value = "${susan.test.userName:susan}")
private String userName;
二:#{}
主要用于通过spring的EL表达式:
- 获取bean的属性,或者调用bean的某个方法
- 调用类的静态常量和静态方法 【如果是调用类的静态方法,则需要加T(包名 + 方法名称)。】
- 读取配置文件中map和集合类的时候。
@Value("#{roleService.DEFAULT_AGE > 16 ? roleService.roleName: '苏三' }")
private String realRoleName;
@Value("#{T(java.lang.Math).random()}")
private double randomValue;
3.2.6:@PropertySourse
# 现在配置文件有如下的key-value
person.name = "zhangSan"
person.age = 18
person.nickName = "Jone"
使用外部的配置文件读取property中的配置的key/value
@PropertySourse(value = {"classpath:/person.properties"}) // 配置
@Configuration
public class MyCOnfiguration {
@Bean
public Person person() {
return new Person();
}
}
@Data
public class Person {
@Value("${person.name}")
private String name; // zhangSan
@Value("${person.age}")
private int age; // 18
@Value("${person.nickName}")
private String nickName; //Jone
}
直接使用${配置文件中的key}
3.3:@Resource
JSR中提供 ->Java Specification Requests
性质一:@Resource
使用时,如果不指定bean的名称,默认会按照Bean的名称去进行自动装配。
@Service
public class UserService {
// 使用@Resource进行Bean的自动装配
@Resource
private UserDao userDao; // 这个就是默认的要装配的bean名称,Resource会上spring管理的bean中去寻找是不是有这个
public void printUserDao() {
System.out.println(userDao);
}
}
性质二:@Resource
不支持使用@Primary标注优先注入Bean的功能。还是优先按照bean
的名称进行装配。
性质三:如果使用@Resource
,还需要指定装配某一个特定的Bean
,则可以结合@Resource的属性name来指定需要装配的Bean
// 修改service层
@Service
public class UserService {
// 使用@Resource进行Bean的自动装配
@Resource(name = "userDao2") // 通过指定name属性指定要装配的bean
private UserDao userDao; // 表示自己将自动装配Userdao
public void printUserDao() {
System.out.println(userDao);
}
}
性质四:使用@Resource
指定的bean必须得存在,不能像Autowired
可以指定bean
不存在时是否可以忽略
// 修改service层
@Service
public class UserService {
// 使用@Resource进行Bean的自动装配
@Resource(name = "userDao111222") // will error: No bean named ‘userDao111222’ available
private UserDao userDao; // 表示自己将自动装配Userdao
public void printUserDao() {
System.out.println(userDao);
}
}
3.4:@Inject
JSR
中提供 ->Java Specification Requests
,和@Resource
同属java
规范请求
@Inject默认也是按照bean
的名称完成Bean
的自动装配
要是用该注解要先导入依赖
<dependency>
<groupId>javax.inject</groupId>
<artifactId>javax.inject</artifactId>
<version>1</version>
</dependency>
和@Resource
不同的是:@Inject
注解可以结合@Primary
注解一块使用。优先装配@Primary
标注的Bean
。
依然使用上述@Resource
的例子说明这个问题
@Service
public class UserService {
// 使用@Inject进行Bean的自动装配.
@Inject
private UserDao userDao; // 在这里将装配name = userDao2的bean, 因为其上面有@Primary注解
public void printUserDao() {
System.out.println(userDao); // {userDao{flag = 2}}
}
}
@Inject
类似@Autowired
,支持@Primary
的功能,但是不支持required
属性
@Resource
和@Inject
是Java
中JSR
提供的规范,在脱离了Spring
框架之后,如其他框架对该注解提供了支持,也可以使用
3.5:Aware注入(了解)
Spring
中除了提供一些使用注解方式进行Bean
自动装配的能力之外,还提供了Aware
接口注入
可以实现在定义的组件中去使用Spring的一些底层组件,例如:
Spring
上下文对象ApplicationContext
,Bean
工厂BeanFactory
等。
同时还可以使用这些对象可以完成一些特殊的操作,如:
- 修改
bean
定义, - 给
bean
容器中设置property
属性等等。
Aware使用示例
通过在pen组件中获取Spring上下文及当前bean名称,使用方法很简单,即:
- 让自定义组件
Pen
去实现ApplicationContextAware
接口和BeanNameAware
接口,然后实现接口中定义的方法即可
@Component
public class Pen implements ApplicationContextAware,BeanNameAware {
/** 保存Spring容器的引用,如果在本类中其他地方使用,可以直接用 */
private ApplicationContext applicationContext;
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
System.out.println("当前IOC容器对象为:"+applicationContext);
this.applicationContext = applicationContext;
// 获取到上下文之后,就可以操作里面的某个bean定义
}
@Override
public void setBeanName(String name) {
System.out.println("当前Bean的名称为:" + name);
}
}
public class TestMain {
public static void main(String[] args) {
ApplicationContext acx = new AnnotationConfigApplicationContext(AutowiredConfig.class);
System.out.println(acx);
}
}
常使用的Aware方法如下
-
BeanFactoryAware
:可用于获取用于创建Bean
的工厂; -
BeanNameAware
:可用于获取当前bean
的名称; -
ApplicationContextAware
:可用于获取当前Bean
所在的Spring
应用上下文对象,利用该对象可以获取到Spring
容器中Bean
的很多信息,例如:所有bean
的名称,所有bean
的定义,bean
注册中心等等,除此之外,还可以再获取到上下文之后,然后创建一个特定的Bean
定义,并注册到Spring
的BeanDefinitionRegistry
中; -
MessageSourceAware
:可用于获取当前应用的国际化信息,包括区域,语言等,在做web应用的国际化时使用; -
EnvironmentAware
:可用于获取当前Bean
运行的环境信息 -
EmbeddedValueResolverAware
:可用于获取一个String
类型的值解析器,解析容器中占位符中的字符串值,例如:${name}
,#{12+10}
等。
3.6:profile
Profile
是Spring
中提供的一个可以根据当前环境来动态切换和激活一些列组件的功能。
假设:开发过程中的数据库为db_dev,测试环境中使用的数据库为db_test,生产环境中使用的数据库为db_prod
db.user=root
db.password=root
db.driverClass=com.mysql.jdbc.Driver
@Configuration
@ComponentScan({"com.wb.spring.profile"})
@PropertySource({"classpath:db.properties"})
public class ProfileConfig {
// 数据库用户名
@Value("${db.user}")
private String user;
// 数据库密码
@Value("${db.password}")
private String password;
// 数据库连接驱动
@Value("${db.driverClass}")
private String driverClass;
// 开发环境数据源
@Bean
// @Profile("dev") // 标记当前数据源为开发环境
public DataSource dataSourceDev() throws Exception {
ComboPooledDataSource dataSource = new ComboPooledDataSource();
setParams(dataSource, "jdbc:mysql://127.0.0.1:3306/db_dev");
return dataSource;
}
// 测试环境数据源
@Bean
// @Profile("test") // 标记当前数据源为测试环境
public DataSource dataSourceTest() throws Exception {
ComboPooledDataSource dataSource = new ComboPooledDataSource();
setParams(dataSource, "jdbc:mysql://127.0.0.1:3306/db_test");
return dataSource;
}
// 生产环境数据源
@Bean
// @Profile("prod") // 标记当前数据源为生产环境
public DataSource dataSourceProd() throws Exception {
ComboPooledDataSource dataSource = new ComboPooledDataSource();
setParams(dataSource, "jdbc:mysql://127.0.0.1:3306/db_prod");
return dataSource;
}
// 公共方法
private void setParams(ComboPooledDataSource dataSource, String jdbcUrl) throws Exception {
dataSource.setJdbcUrl(jdbcUrl);
dataSource.setUser(user);
dataSource.setPassword(password);
dataSource.setDriverClass(driverClass);
}
}
如果将@Profile
全部注释掉,运行测试方法之后,会发现容器中存在着三个数据源。
如果将@Profile
注解全部放开之后,再次运行测试类,会发现容器中不存在任何数据源,控制台输出内容为空。
因为@Profile
如果标注在Bean
上之后,只有当前profile
的环境被激活之后,才会被注册到Spring
容器中。
而Profile
默认激活的环境为default
,即:@Profile(“default”)
Profile
的四种激活方式
1:使用VM options
参数的形式,通过指定-Dspring.profiles.active=xxx
的方式来激活指定的环境
2:在Spring
容器启动之前,设置好当前环境中需要激活的某个组件,来激活特定环境中的bean
组件
public class TestMain {
public static void main(String[] args) {
// 1.创建上下文对象,使用无参构造函数
AnnotationConfigApplicationContext acx = new AnnotationConfigApplicationContext();
// 2.设置需要激活的环境表示,可以写多个
// 此处表示同时激活开发环境dev和测试环境test.
acx.getEnvironment().setActiveProfiles("dev","test");
// 3.注册配置类
acx.register(ProfileConfig.class);
// 4.执行刷新
acx.refresh();
// ...
}
}
3:通过查看@Profile
注解源码,可以看到,该注解可以放在两个地方,一个是方法上,另外一个是类上,当放在类上时,表示如果当前运行环境为被激活的环境,则该类中的所有组件都将被激活,并加入到Spring
容器中。
用于类上时,通常标注的是配置类。
// profileConfig
@Configuration
@ComponentScan({"com.wb.spring.profile"})
@PropertySource({"classpath:db.properties"})
// 表示当运行环境为测试环境时,激活该配置中的所有Bean组件
@Profile("test")
public class ProfileConfig {
// 其他配置代码省略
}
4:如果配置类上标注了@Profile
,@Bean
方法上也标注了@Profile
,而且方法上的Profile
标识和类上的环境标识不同,则只会激活@Bean
上环境标识对应的组件。利用这个特性,可以实现激活某个配置类下所有的组件,但是不包括某些组件。
@Configuration
@ComponentScan({"com.wb.spring.profile"})
@PropertySource({"classpath:db.properties"})
// 当dev被激活时,未标注Profile标识的bean将会被注入容器
@Profile("dev")
public class ProfileConfig {
@Value("${db.user}")
private String user;
@Value("${db.password}")
private String password;
@Value("${db.driverClass}")
private String driverClass;
// 如果激活了配置类上的环境标识dev,则该bean会被注册
@Bean
public DataSource dataSourceDev() throws Exception {
ComboPooledDataSource dataSource = new ComboPooledDataSource();
setParams(dataSource, "jdbc:mysql://127.0.0.1:3306/db_dev");
return dataSource;
}
@Bean
// 只有当运行环境为test时,才会被激活
// 由于配置类上标识的是dev,所以该bean不会被注册
@Profile("test")
public DataSource dataSourceTest() throws Exception {
ComboPooledDataSource dataSource = new ComboPooledDataSource();
setParams(dataSource, "jdbc:mysql://127.0.0.1:3306/db_test");
return dataSource;
}
// 如果激活了配置类上的dev环境标识,则该bean会被注册
@Bean
public DataSource dataSourceProd() throws Exception {
ComboPooledDataSource dataSource = new ComboPooledDataSource();
setParams(dataSource, "jdbc:mysql://127.0.0.1:3306/db_prod");
return dataSource;
}
private void setParams(ComboPooledDataSource dataSource, String jdbcUrl) throws Exception {
dataSource.setJdbcUrl(jdbcUrl);
dataSource.setUser(user);
dataSource.setPassword(password);
dataSource.setDriverClass(driverClass);
}
}