代码和配置
代码结构
maven依赖
<properties>
<java.version>1.8</java.version>
<spring.verson>5.3.9</spring.verson>
<junit.version>5.7.2</junit.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>${spring.verson}</version>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-engine</artifactId>
<version>${junit.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.20</version>
<scope>provided</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.22.2</version>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.1</version>
<configuration>
<source>${java.version}</source>
<target>${java.version}</target>
</configuration>
</plugin>
</plugins>
</build>
User.java
代码
package org.spring.ss.pojo;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.Map;
import java.util.Properties;
@Data
@NoArgsConstructor
public class User {
private String name;
private Integer age;
private String[] favorites;
private Map<String, Object> map;
private Properties props;
public User(String name, Integer age) {
this.name = name;
this.age = age;
}
}
UserDao.java
代码
package org.spring.ss.dao;
import org.spring.ss.pojo.User;
import org.springframework.stereotype.Repository;
@Repository
public class UserDao {
public User find() {
return new User("赵小八", 30);
}
}
UserService.java
代码
package org.spring.ss.service;
import org.spring.ss.dao.UserDao;
import org.spring.ss.pojo.User;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
@Service
public class UserService {
@Resource
public UserDao userDao;
public User find() {
return userDao.find();
}
}
UserController.java
代码
package org.spring.ss.controller;
import org.spring.ss.pojo.User;
import org.spring.ss.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
@Controller
public class UserController {
@Autowired
public UserService userService;
public void find() {
User userBean = userService.find();
System.out.println(userBean);
}
}
UserTest.java 代码:
package org.spring.ss.pojo;
import org.junit.jupiter.api.Test;
import org.spring.ss.controller.UserController;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class UserTest {
}
基于XML方式的实现
新建Spring
的配置文件applicationContext.xml
,并添加包扫描路径。
如果
xmlns:context
不存在,注意添加
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
">
<!-- 添加扫描的路径 指定从哪些 package下加载被 @Component @Controller @Service @Repository 等标注的类型-->
<!-- 指定一个大的范围 -->
<!-- <context:component-scan base-package="org.spring.ss"/>-->
<!-- 通过逗号分割一起指定 -->
<!-- <context:component-scan base-package="org.spring.ss.controller,org.spring.ss.service,org.spring.ss.dao"/>-->
<!-- 分开指定 -->
<context:component-scan base-package="org.spring.ss.controller"/>
<context:component-scan base-package="org.spring.ss.service"/>
<context:component-scan base-package="org.spring.ss.dao"/>
<!--
base-package 的包路径还可以使用通配符配置:
* 标示一层包的通配,com.*.dao可以包括范围如:com.aa.dao,com.bb.dao,com.cc.dao
** 标示不确定层包通配,com.**.dao可以表示的范围如:com.aa.dao,com.aa.aa1.dao,com.bb.dao,com.bb.bb1.dao
-->
</beans>
在UserTest.java
里添加测试代码:
@Test
public void testBySpringXML() {
// ioc 容器初始化
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
// ioc 通过bean 的 id 获取指定的bean
UserController userController = applicationContext.getBean("userController", UserController.class);
// 使用bean
userController.find();
}
输出User(name=赵小八, age=30, favorites=null, map=null, props=null)
限制指定包可以使用的注解
修改applicationContext.xml
配置文件:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
">
<!-- 添加扫描的路径 指定从哪些 package下加载被 @Component @Controller @Service @Repository 等标注的类型-->
<!-- 指定一个大的范围 -->
<!-- <context:component-scan base-package="org.spring.ss"/>-->
<!-- 通过逗号分割一起指定 -->
<!-- <context:component-scan base-package="org.spring.ss.controller,org.spring.ss.service,org.spring.ss.dao"/>-->
<!-- 分开指定 -->
<!--<context:component-scan base-package="org.spring.ss.controller"/>
<context:component-scan base-package="org.spring.ss.service"/>
<context:component-scan base-package="org.spring.ss.dao"/>-->
<!--
还可以使用通配符
* 标示一层包的通配,com.*.dao可以包括范围如:com.aa.dao,com.bb.dao,com.cc.dao
** 标示不确定层包通配,com.**.dao可以表示的范围如:com.aa.dao,com.aa.aa1.dao,com.bb.dao,com.bb.bb1.dao
-->
<!--
use-default-filters="false" 表示不适用默认的过滤器
默认过滤器会识别 @Component @Controller @Service @Repository
-->
<context:component-scan base-package="org.spring.ss.controller" use-default-filters="false">
<context:include-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
</context:component-scan>
<context:component-scan base-package="org.spring.ss.service,org.spring.ss.dao">
<!-- 排除掉某个注解 -->
<context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller" />
</context:component-scan>
</beans>
这个时候尝试修改UserController
上面的注解为@Component
。
package org.spring.ss.controller;
import org.spring.ss.pojo.User;
import org.spring.ss.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Controller;
@Component
public class UserController {
@Autowired
public UserService userService;
public void find() {
User userBean = userService.find();
System.out.println(userBean);
}
}
重新运行测试用例testBySpringXML
,可以看到下面的错误提示。
基于java类配置实现
添加SpringConfig.java
文件,代码如下,以下代码实现了和上面xml
代码配置一样的功能
package org.spring.ss;
import org.spring.ss.pojo.User;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.ComponentScans;
import org.springframework.context.annotation.Configuration;
/*
* java配置类,相当于 applicationContext.xml
* */
@Configuration
@ComponentScans({ // 配置扫描路径
@ComponentScan("org.spring.ss.controller"),
@ComponentScan("org.spring.ss.service"),
@ComponentScan("org.spring.ss.dao"),
})
public class SpringConfig {
/**
* @return
* @Bean 作用和我们在applicationContext.xml中添加的<bean> 效果一样</>
* 默认的name是方法名称
* 自定义的name 可以通过value属性或者name属性来指定
*/
@Bean(name = {"aaa", "bbb"})
public User getUser() {
User user = new User();
user.setName("王小六");
return user;
}
}
添加测试代码:
@Test
public void testBySpringConfig() {
// ioc 容器初始化
ApplicationContext applicationContext = new AnnotationConfigApplicationContext(SpringConfig.class);
// ioc 获取配置类中设置的 bean
User userBean = (User) applicationContext.getBean("aaa");
// 使用bean
System.out.println(userBean);
// 获取容器中被扫描标记的
UserController userController = applicationContext.getBean("userController", UserController.class);
// 使用bean
userController.find();
}
输出
User(name=王小六, age=null, favorites=null, map=null, props=null)
User(name=赵小八, age=30, favorites=null, map=null, props=null)
限制指定包可以使用的注解
修改spring
配置类SpringConfig.java
:
package org.spring.ss;
import org.spring.ss.pojo.User;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.ComponentScans;
import org.springframework.context.annotation.Configuration;
import org.springframework.stereotype.Controller;
/*
* java配置类,相当于 applicationContext.xml
* */
@Configuration
/*
* @ComponentScan 如果不去指定扫描的路基,默认是会扫描当前目录及其子目录下的所有的
* 被@Componenet @Controller @Service @Repository标注的类型
* */
@ComponentScans({
@ComponentScan(
value = "org.spring.ss.controller",
useDefaultFilters = false,
includeFilters = {@ComponentScan.Filter(Controller.class)}),
@ComponentScan(
value = {"org.spring.ss.service","org.spring.ss.dao"},
excludeFilters = {@ComponentScan.Filter(Controller.class)}),
})
public class SpringConfig {
/**
* @return
* @Bean 作用和我们在applicationContext.xml中添加的<bean> 效果一样</>
* 默认的name是方法名称
* 自定义的name 可以通过value属性或者name属性来指定
*/
@Bean(name = {"aaa", "bbb"})
public User getUser() {
User user = new User();
user.setName("王小六");
return user;
}
}
这个时候尝试修改UserController
上面的注解为@Component
。
package org.spring.ss.controller;
import org.spring.ss.pojo.User;
import org.spring.ss.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Controller;
@Component
public class UserController {
@Autowired
public UserService userService;
public void find() {
User userBean = userService.find();
System.out.println(userBean);
}
}
重新运行测试用例testBySpringConfig
,可以看到下面的错误提示。
@Autowired
和@Resource
的区别
@Autowired
:默认只能根据类型来查找,可以结合@Qualifier("abc")
注解来实现通过name
查找
@Resource
:默认是根据类型来查找,但是提供的有type
和name
属性类实现不同的查找方式,@Resource(name="abc")
或者@Resource(type=UserService.class)
@Value
注解的使用
修改SpringConfig.java
添加对org.spring.ss.pojo
包的扫描。
package org.spring.ss;
import org.spring.ss.pojo.User;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.ComponentScans;
import org.springframework.context.annotation.Configuration;
import org.springframework.stereotype.Controller;
/*
* java配置类,相当于 applicationContext.xml
* */
@Configuration
/*
* @ComponentScan 如果不去指定扫描的路基,默认是会扫描当前目录及其子目录下的所有的
* 被@Componenet @Controller @Service @Repository标注的类型
* */
@ComponentScans({
@ComponentScan("org.spring.ss.pojo"),
@ComponentScan(
value = {"org.spring.ss.controller"},
useDefaultFilters = false,
includeFilters = {@ComponentScan.Filter(Controller.class)}),
@ComponentScan(
value = {"org.spring.ss.service","org.spring.ss.dao"},
excludeFilters = {@ComponentScan.Filter(Controller.class)}),
})
public class SpringConfig {
/**
* @return
* @Bean 作用和我们在applicationContext.xml中添加的<bean> 效果一样</>
* 默认的name是方法名称
* 自定义的name 可以通过value属性或者name属性来指定
* @Primary 表示如果遇到同类型的bean, 优先返回
*/
@Bean(name = {"aaa", "bbb"})
@Primary
public User getUser() {
User user = new User();
user.setName("王小六");
return user;
}
}
添加Account.java
package org.spring.ss.pojo;
import lombok.Data;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.io.Resource;
import org.springframework.stereotype.Component;
@Data
@Component
public class Account {
@Value("bobo") // 注入普通的字符串
private String userName;
@Value("#{systemProperties['os.name']}")
private String systemPropertiesName; // 注入操作系统的信息
@Value("#{T(java.lang.Math).random()*100}")
private double randomNumber; // 注入表达式的结果
// 注入其他Bean的属性, aaa 来自于 SpringConfig,是通过 java 配置类注入的
@Value("#{aaa.name}")
private String fromPersonName;
@Value("classpath:test.txt")
private Resource resourceFile;
@Value("http://www.baidu.com")
private Resource baiduFile;
}
添加测试方法
@Test
public void testBySpringConfig2() {
// ioc 容器初始化
ApplicationContext applicationContext = new AnnotationConfigApplicationContext(SpringConfig.class);
// ioc 通过bean 的 id 获取指定的bean
Account account = applicationContext.getBean("account", Account.class);
// 使用bean
System.out.println(account);
}
输出Account(userName=bobo, systemPropertiesName=Windows 10, randomNumber=16.667755826649376, fromPersonName=王小六, resourceFile=class path resource [test.txt], baiduFile=URL [http://www.baidu.com])
@PropertySouce
引入属性文件
添加属性文件
在Java
配置类中通过@PropertySouce
注解来显示的引入属性文件
效果
@Lazy注解
@Lazy
注解可以提高系统加载速度,@Component 和 @Lazy 一起使用的时候,被修饰的类在启动的时候不会被初始化,只有通过 ApplicationContext 对象的 getBean 方法获取的时候,或者第一次被使用的时候才会初始化。
需要注意的是 bean 的作用域范围为
prototype
的时候,也不会在加载的时候初始化。
作用范围
- 可以作用于在类上和
@Component
注解搭配使用 - 也可以作用在方法上和
@Bean
注解搭配使用 - 当作用在类上和
@Configuration
注解搭配使用的情况下,该类下面所有带有@Bean
注解的对象都将受到同样的影响
属性功能
- value 的默认值为 true
- 如果为 true 并且在其他 Bean 没有对其依赖或者没有使用的情况下将不会初始化
- 如果为 false,跟其他的 Bean 一样正常加载
生命周期注解@PostConstruct @PreDestory
Spring负责管理Bean的初始化和销毁,但同时也提供方式让我们在bean初始化之后、销毁之前执行特定业务。@PostConstruct 和 @PreDestroy 注解,主要实现Bean在初始化之后、销毁之前执行自定义业务。
实现Bean在初始化之后,销毁之前执行自定义业务,还有其他两种方式。
Spring会在初始化bean属性之后,调用一次拥有@PostConstruct注解的方法。该方法可以为任何访问级别,但不能为static。
@Component
public class DbInit {
@Autowired
private UserRepository userRepository;
// 该示例首先初始化userRepository,然后调用postConstruct()方法。
@PostConstruct
private void postConstruct() {
User admin = new User("admin", "admin password");
User normalUser = new User("user", "user password");
userRepository.save(admin, normalUser);
}
}
@Component
public class UserRepository {
private DbConnection dbConnection;
// 该方法一般用于在bean销毁之前释放资源或执行其他清理任务,如关闭数据库连接。
@PreDestroy
public void preDestroy() {
dbConnection.close();
}
}
需要注意的是 @PostConstruct
和 @PreDestroy
注解是Java EE
的一部分。自Java 9
开始 Java EE
已被标注不建议使用,在Java 11
中已经被移除,因此需要手动增加相应依赖:
<dependency>
<groupId>javax.annotation</groupId>
<artifactId>javax.annotation-api</artifactId>
<version>1.3.2</version>
</dependency>
@DependsOn注解
@DependsOn
指定实例化对象的先后顺序
@Component
@DependsOn({"user"}) // Person的实例化依赖于User对象的实例化,也就是User先于Person实例化
public class Person {
public Person(){
System.out.println("Person 构造方法执行了...");
}
}
@Import注解
添加Cat.java
package org.spring.ss.pojo;
import lombok.Data;
import org.springframework.beans.factory.annotation.Value;
@Data
public class Cat {
@Value("加菲猫")
private String color;
private String nick;
}
静态使用
在@Import
注解中将前面创建的Cat.java
引入到IOC
容器,这种方式的缺点是:无法灵活的指定引入的类型。
测试代码:
@Test
public void testBySpringConfig3() {
// ioc 容器初始化
ApplicationContext applicationContext = new AnnotationConfigApplicationContext(SpringConfig.class);
// ioc 通过bean 的 id 获取指定的bean
Cat cat = applicationContext.getBean(Cat.class);
// 使用bean
System.out.println(cat);
}
结果:Cat(color=加菲猫, nick=null)
动态使用
动态可以通过一定的逻辑来指定加入 IOC 容器中的对象。
LoggerService.java
package org.spring.ss.service;
public class LoggerService {
}
CacheService .java
package org.spring.ss.service;
public class CacheService {
}
ImportSelector
通过实现接口 ImportSelector
的 selectImports 方法,来灵活控制引入的类型。
package org.spring.ss;
import org.spring.ss.service.CacheService;
import org.spring.ss.service.LoggerService;
import org.springframework.context.annotation.ImportSelector;
import org.springframework.core.type.AnnotationMetadata;
public class ImportSelectorImpl implements ImportSelector {
/**
*
* @param annotationMetadata
* @return
* IoC 要加载的类型的全路径的字符串数组
*/
@Override
public String[] selectImports(AnnotationMetadata annotationMetadata) {
// 在此处实现不同的业务逻辑控制
return new String[]{LoggerService.class.getName(), CacheService.class.getName()};
}
}
修改SpringConfig.java
测试代码
@Test
public void testBySpringConfigImport() {
// ioc 容器初始化
ApplicationContext applicationContext = new AnnotationConfigApplicationContext(SpringConfig.class);
// 获取 IOC 容器中所有 bean 的名字
String[] beanDefinitionNames = applicationContext.getBeanDefinitionNames();
for (String beanDefinitionName : beanDefinitionNames) {
System.out.println(beanDefinitionName);
}
}
ImportBeanDefinitionRegistrar
通过重写 ImportBeanDefinitionRegistrar
的 registerBeanDefinitions
方法,来灵活控制引入的类型。
package org.spring.ss;
import org.spring.ss.service.CacheService;
import org.spring.ss.service.LoggerService;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.context.annotation.ImportBeanDefinitionRegistrar;
import org.springframework.core.type.AnnotationMetadata;
public class ImportBeanDefinitionRegistrarImpl implements ImportBeanDefinitionRegistrar {
/**
*
* @param annotationMetadata
* @param beanDefinitionRegistry IoC容器中管理对象的一个注册器
*/
public void registerBeanDefinitions(AnnotationMetadata annotationMetadata, BeanDefinitionRegistry beanDefinitionRegistry) {
// 需要将添加的对象包装为一个RootBeanDefinition对象
RootBeanDefinition cache = new RootBeanDefinition(CacheService.class);
beanDefinitionRegistry.registerBeanDefinition("cache",cache);
RootBeanDefinition logger = new RootBeanDefinition(LoggerService.class);
beanDefinitionRegistry.registerBeanDefinition("logger",logger);
}
}
修改SpringConfig.java
执行测试用例
SpringBoot中的ConditionalXXX
@Conditional扩展注解 | 作用(判断是否满足当前指定条件) |
---|---|
@ConditionalOnJava | 系统的Java版本是否符合要全 |
@ConditionalOnBean | 容器中存在指定的Bean |
@ConditionalOnMissingBean | 容器中不存在指定的Bean |
@ConditionalOnExpression | 满足SpEL表达式 |
@ConditionalOnClass | 系统中有指定的类 |
@ConditionalOnMissingClass | 系统中没有指定的类 |
@ConditionalOnSingleCandidate | 容器中只有一个指定的Bean,或者这个Bean是首选Bean |
@ConditionalOnProperty | 系统中指定的属性是否有指定的值 |
@ConditionalOnResource | 类路径下是否存在指定的资源文件 |
@ConditionalOnWebApplication | 当前是Web环境 |
@ConditionalOnNotWebApplication | 当前不是Web环境 |
@ConditionalOnJndi | JNDI存在指定项 |
具体案例
import org.springframework.context.annotation.Condition;
import org.springframework.context.annotation.ConditionContext;
import org.springframework.core.type.AnnotatedTypeMetadata;
public class ConditionalOnClass implements Condition {
public boolean matches(ConditionContext conditionContext, AnnotatedTypeMetadata annotatedTypeMetadata) {
try {
Class<?> aClass = conditionContext.getClassLoader().loadClass("com.gupaoedu.test.Test1");
return aClass==null?false:true;
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
return false;
}
}
import org.springframework.context.annotation.Condition;
import org.springframework.context.annotation.ConditionContext;
import org.springframework.core.type.AnnotatedTypeMetadata;
public class ConditionalOnBean implements Condition {
/**
* 如果IoC容器中有Person对象就返回true 否在返回false
*
* @param conditionContext
* @param annotatedTypeMetadata
* @return
* true 表示IoC容器加载该类型
* false 表示IoC容器不加载该类型
*/
public boolean matches(ConditionContext conditionContext, AnnotatedTypeMetadata annotatedTypeMetadata) {
boolean flag = conditionContext.getRegistry().containsBeanDefinition("person");
System.out.println(flag + " **** ");
return flag;
}
}
多环境下的解决方案之Profile
Profile
本质就是Conditional
的实现。
添加DataSource
类:
package org.spring.ss.pojo;
import lombok.AllArgsConstructor;
import lombok.Data;
@Data
@AllArgsConstructor
public class DataSource {
private String username;
private String password;
private String url;
}
在SpringConfig.java
中添加环境配置。
@Bean
@Profile("pro") // 其实Profile注解本质上就是Conditional的一种实现
public DataSource proDataSource() {
DataSource ds = new DataSource("root", "123", "192.168.11.190");
return ds;
}
@Bean
@Profile("dev")
public DataSource devDataSource() {
DataSource ds = new DataSource("admin", "456", "192.168.12.190");
return ds;
}
添加测试代码:
public void testByProfile() {
AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext();
ac.getEnvironment().setActiveProfiles("pro");
ac.register(SpringConfig.class);
ac.refresh();
System.out.println(ac.getBean(DataSource.class));
}
输出DataSource(username=root, password=123, url=192.168.11.190)
Bean对象的作用域
作用域 | 说明 |
---|---|
prototype | 每次请求,都是一个新的Bean( java原型模式 ) |
singleton | bean是单例的(Java单例模式) |
request | 在一次请求中,bean的声明周期和request同步 |
session | bean的生命周期和session同步 |
默认的情况是 singleton
@Bean
@Scope("prototype")
public Person person(){
return new Person();
}
@Bean
@Scope("singleton")
public Person person(){
return new Person();
}