Spring 应该是所有 Java 程序员中必知必会的一个框架了,现在无论大中小公司基本上应该都会有使用到 Spring了,因为它提供了丰富的功能以及极大的降低了开发者的难度,这边准备分两篇文章来介绍,第一篇主要介绍 Spring 常用注解的使用,第二篇介绍 Spring 中注解使用的底层原理,尽量让大家知其然知其所以然。
在本场 Chat 中,主要会涉及到的注解有:
- 组件添加
- @Bean 注解
- @ComponentScan 注解
- @Controller 注解
- @Service 注解
- @Repository 注解
- @Conditional 注解
- @Lazy 注解
- @Scope 注解
- 组件赋值
- @value 注解
- @PropertySource 注解
- @Resource 注解
- @Inject 注解
- @Pr
- 基于 xml 方式 bean 使用回顾
新建一个 maven 项目增加 spring-context 的 jar 包如下:
org.springframework spring-context 4.3.16.RELEASE
定义一个 user 用户 bean 对象如下:
/** * 定义一个用户对象 bean * * @author zhangqh * @date 2020 年 1 月 30 日 */public class User { /** * 用户名 */ private String userName; /** * 年龄 */ private Integer age; /** * 省略 get set 方法 */}
在 src\main\resources 目录下边新建一个 beans.xml 文件如下:
编写一个测试类如下:
/** * 测试类 * @author zhangqh * @date 2020 年 1 月 30 日 */public class ApplicationTest { @SuppressWarnings("resource") public static void main(String[] args) { // 使用 ClassPathXmlApplicationContext 获取 spring 容器 ApplicationContext ApplicationContext applicationContext = new ClassPathXmlApplicationContext("beans.xml"); // 根据 bean id 获取 bean 对象 User bean = (User) applicationContext.getBean("user"); System.out.println(bean); }}
运行结果如下:
User [userName=zhangsan, age=26]
- 注解@Bean 详细使用说明
以上的例子这边再用注解来实现一次,首先定义一个注解配置文件如下:
/** * 定义一个注解配置文件 必须要加上@Configuration 注解 * * @author zhangqh * @date 2020 年 1 月 30 日 */@Configurationpublic class MainConfig { /** * 定义一个 bean 对象 * @return */ @Bean public User getUser(){ return new User("zhangsan",26); }}
在以上测试类中增加注解的测试代码如下:
// 使用 AnnotationConfigApplicationContext 获取 spring 容器 ApplicationContext2ApplicationContext applicationContext2 = new AnnotationConfigApplicationContext(MainConfig.class);User bean2 = applicationContext2.getBean(User.class);System.out.println(bean2);
运行结果如下:
User [userName=zhangsan, age=26]
到此我们用注解@Bean 也同样实现了和 xml 一样 bean 的注册
1.2 @ComponentScan 注解主要从以下几个方面来介绍一下@ComponentScan 注解:@ComponentScan 注解是什么@ComponentScan 注解的详细使用
@ComponentScan 注解是什么 其实很简单,@ComponentScan 主要就是定义扫描的路径从中找出标识了需要装配的类自动装配到 spring 的 bean 容器中
@ComponentScan 注解的详细使用做过 web 开发的同学一定都有用过@Controller,@Service,@Repository 注解,查看其源码你会发现,他们中有一个 共同的注解@Component ,没错@ComponentScan 注解默认就会装配标识了@Controller,@Service,@Repository,@Component 注解的类到 spring 容器中,好下面咱们就先来简单演示一下这个例子
在包 com.zhang.controller 下新建一个 UserController 带@Controller 注解如下:
package com.zhang.controller;import org.springframework.stereotype.Controller;@Controllerpublic class UserController {}
在包 com.zhang.service 下新建一个 UserService 带@Service 注解如下:
package com.zhang.service;import org.springframework.stereotype.Service;@Servicepublic class UserService {}
在包 com.zhang.dao 下新建一个 UserDao 带@Repository 注解如下:
package com.zhang.dao;import org.springframework.stereotype.Repository;@Repositorypublic class UserDao {}
新建一个配置类如下:
/** * 主配置类 包扫描 com.zhang * * @author zhangqh * @date 2020 年 1 月 12 日 */@ComponentScan(value="com.zhang")@Configurationpublic class MainScanConfig {}
新建测试方法如下:
AnnotationConfigApplicationContext applicationContext2 = new AnnotationConfigApplicationContext(MainScanConfig.class); String[] definitionNames = applicationContext2.getBeanDefinitionNames(); for (String name : definitionNames) { System.out.println(name);}
运行结果如下:
mainScanConfig userController userDao userService
1.3 @Controller 注解参见 1.2 节的使用
1.4 @Service 注解参见 1.2 节的使用
1.5 @Repository 注解参见 1.2 节的使用
1.6 @Conditional 注解主要从以下几方面来介绍一下@Conditional 注解
- @Conditional 注解是什么
- @Conditional 注解怎么使用
@Conditional 注解是可以根据一些自定义的条件动态的选择是否加载该 bean 到 springIOC 容器中去,如果看过 springBoot 源码的同学会发现,springBoot 中大量使用了该注解
@Conditional 注解怎么使用@Conditional 作用在方法上
定义一个 Condition 如下:
/** * 定义一个 bean 的 Condition * * @author zhangqh * @date 2018 年 5 月 1 日 */public class MyCondition implements Condition { public boolean matches(ConditionContext context,AnnotatedTypeMetadata metadata) { Environment env = context.getEnvironment(); String system = env.getProperty("os.name"); System.out.println("系统环境为 ==="+system); // 系统环境在 Windows 才加载该 bean 到容器中 if(system.contains("Windows")){ return true; } return false; }}
定义一个 bean 加上@Conditional 注解如下:
@Conditional({MyCondition.class})@Bean(value="user1")public User getUser1(){ System.out.println("创建 user1 实例"); return new User("李四",26);}
测试如下:
AnnotationConfigApplicationContext applicationContext2 = new AnnotationConfigApplicationContext(MainConfig.class);String[] beanNames = applicationContext2.getBeanDefinitionNames();for(int i=0;i createPropertySource(String name, EncodedResource resource) throws IOException;}
里边只有一个 createPropertySource 方法,进入其中的实现类中如下:
public class DefaultPropertySourceFactory implements PropertySourceFactory { @Override public PropertySource createPropertySource(String name, EncodedResource resource) throws IOException { return (name != null ? new ResourcePropertySource(name, resource) : new ResourcePropertySource(resource)); }}
注意,重要的类 ResourcePropertySource 出现了,进去可以看到两个主要的构造方法如下:
/** * Create a PropertySource having the given name based on Properties * loaded from the given encoded resource. */ public ResourcePropertySource(String name, EncodedResource resource) throws IOException { super(name, PropertiesLoaderUtils.loadProperties(resource)); this.resourceName = getNameForResource(resource.getResource()); } /** * Create a PropertySource based on Properties loaded from the given resource. * The name of the PropertySource will be generated based on the * {@link Resource#getDescription() description} of the given resource. */ public ResourcePropertySource(EncodedResource resource) throws IOException { super(getNameForResource(resource.getResource()), PropertiesLoaderUtils.loadProperties(resource)); this.resourceName = null; }
在构造方法中你可以发现加载资源的地方 PropertiesLoaderUtils.loadProperties(resource),一路进去你可以返现如下代码:
static void fillProperties(Properties props, EncodedResource resource, PropertiesPersister persister) throws IOException { InputStream stream = null; Reader reader = null; try { String filename = resource.getResource().getFilename(); // 加载 xml 文件 if (filename != null && filename.endsWith(XML_FILE_EXTENSION)) { stream = resource.getInputStream(); persister.loadFromXml(props, stream); } // 判断是否有需要对应的字符编码设置 有的话处理对应的 InputStream else if (resource.requiresReader()) { reader = resource.getReader(); persister.load(props, reader); } else { stream = resource.getInputStream(); persister.load(props, stream); } }}
怎么样,是不是可以发现@PropertySource 不仅可以解析 properties 的文件同样也可以解析 xml 文件,下边我们一起来演示一下解析 xml 的例子吧首先新增一个 user2.xml 如下:
王二小 22
配置类增加配置如下:
@PropertySource(value={"classpath:/user.properties","classpath:/user2.xml","file:/d://user2.properties"},encoding="utf-8",ignoreResourceNotFound=false)
测试运行结果如下:
实例 1 === User [userName=李四, age=29] 实例 2 === User [userName=王二小, age=22]
好了,到目前为止我们不仅学会了@PropertySource 注解的使用,而且了解到了其底层的具体实现,做到知其然知其所以然,以及了解了其默认的资源解析器 PropertySourceFactory,并且你也可以继承 PropertySourceFactory 实现自定义的解析器,感兴趣的同学可以自己去实现一个自定义解析类
2.3 @Resource 注解上文系统的介绍了 spring 自动装配@Autowired 注解的使用,没有查看的可以点击查看,@Autowired 注解大家使用过,那今天的两个自动注入可能就不是每个同学都有使用过,不信你看
今天将从以下几方面来学习一下@Resource 和@Inject 注解:
- @Resource 注解的使用演示
- @Inject 注解的使用演示
- @Resource 和@Inject 注解与@Autowired 注解的区别
@Resource 注解的使用演示
把上文中的例子注入修改如下:
// @Qualifier(value="userDao")// @Autowired(required=false)@Resourceprivate UserDao userDao2;
运行测试类结果如下:
实例 bean 为 === UserService [userDao=UserDao [version=2]]
让我们@Resource 注解中加上一个 name 参数如下:
@Resource(name="userDao")
继续运行测试结果如下:
实例 bean 为 === UserService [userDao=UserDao [version=0]]
从运行结果我们可以发现:@Resource 注解默认根据参数名字寻找 bean 注入@Resource 也可以通过 name 参数指定需要注入的 bean@Resource 注解不支持 spring 的@Primary 注解优先注入
2.4 @Inject 注解@Inject 注解的使用演示
在演示@Inject 注解前需要先在 pom 文件中增加如下依赖:
javax.inject javax.inject 1
UserService 类 UserDao 注入修改为如下:
@Injectprivate UserDao userDao;
对应的主配置文件如下:
@Primary@Bean(value = "userDao2")public UserDao getUserDao(){ UserDao userDao = new UserDao(); userDao.setVersion(2); return userDao;}
运行测试类结果如下:
实例 bean 为 === UserService [userDao=UserDao [version=2]]
UserService 类注入代码修改如下:
@Named("userDao")private UserDao userDao;
运行测试类结果如下:
实例 bean 为 === UserService [userDao=UserDao [version=0]]
从以上运行结果我们可以得出:
@Inject 注解默认是根据参数名去寻找 bean 注入@Inject 注解支持 spring 的@Primary 注解优先注入@Inject 注解可以增加@Named 注解指定注入的 bean
这边再介绍一下@Resource 和@Inject 注解与@Autowired 注解的区别
不同点
@Autowired 是 spring 专有注解,@Resource 是 java 中 JSR250 中的规范,@Inject 是 java 中 JSR330 中的规范@Autowired 支持参数 required=false,@Resource,@Inject 都不支持@Autowired,和@Inject 支持@Primary 注解优先注入,@Resource 不支持@Autowired 通过@Qualifier 指定注入特定 bean,@Resource 可以通过参数 name 指定注入 bean,@Inject 需要@Named 注解指定注入 bean
相同点
三种注解都可以实现 bean 的注入
2.5 @Profile 注解做 java 开发的同学都有用过即使没用过那我想至少应该听过 springBoot 框架,其中一个比较好用的功能就是它可以根据不同的环境加载不同的配置文件,今天这篇文章就带大家来使用 spring 的@Profile 注解来实现相同的功能
开发的同学应该都比较清楚,平时开发我们有对应的开发环境,beta 环境以及线上环境,那今天我们就来通过使用@Profile 注解实现根据不同的环境加载不同的数据库配置,话不多说直接看代码吧
新建一个配置文件如下:
#开发环境dev.db.user=rootdev.db.password=123456dev.db.driverClass=com.mysql.jdbc.Driverdev.db.url=jdbc:mysql://localhost:3306/dev#beta 环境beta.db.user=rootdev.db.password=123456dev.db.driverClass=com.mysql.jdbc.Driverdev.db.url=jdbc:mysql://localhost:3306/beta#pro 环境pro.db.user=rootpro.db.password=123456pro.db.driverClass=com.mysql.jdbc.Driverpro.db.url=jdbc:mysql://localhost:3306/pro
新建一个配置类如下:
@PropertySource("classpath:/dbconfig.properties")@Configurationpublic class MainConfigOfProfile implements EmbeddedValueResolverAware{ /** * 通过@Value 注解作用在 field 上获取值 */ @Value("${beta.db.user}") private String betauser; @Value("${beta.db.password}") private String betapwd; @Value("${beta.db.url}") private String betaurl; @Value("${beta.db.driverClass}") private String detadriverClass; private StringValueResolver valueResolver; /** * 通过@Value 注解获取对应的值 * @param user * @param pwd * @param url * @param driverClass * @return * @throws Exception */ @Profile("dev") @Bean("devDataSource") public DataSource dataSourceTest(@Value("${dev.db.user}")String user,@Value("${dev.db.password}")String pwd, @Value("${dev.db.url}")String url,@Value("${dev.db.driverClass}")String driverClass) throws Exception{ ComboPooledDataSource dataSource = new ComboPooledDataSource(); dataSource.setUser(user); dataSource.setPassword(pwd); dataSource.setJdbcUrl(url); dataSource.setDriverClass(driverClass); return dataSource; } /** * 通过@Value 注解作用在 field 上获取值 */ @Profile("beta") @Bean("betaDataSource") public DataSource dataSourceDev() throws Exception{ ComboPooledDataSource dataSource = new ComboPooledDataSource(); dataSource.setPassword(betapwd); dataSource.setJdbcUrl(betaurl); dataSource.setDriverClass(detadriverClass); return dataSource; } /** * 通过实现 EmbeddedValueResolverAware 接口获取解析器 去获取值 * @return * @throws Exception */ @Profile("prod") @Bean("prodDataSource") public DataSource dataSourceProd() throws Exception{ ComboPooledDataSource dataSource = new ComboPooledDataSource(); dataSource.setUser(valueResolver.resolveStringValue("${prod.db.user}")); dataSource.setPassword(valueResolver.resolveStringValue("${prod.db.password}")); dataSource.setJdbcUrl(valueResolver.resolveStringValue("${prod.db.url}")); dataSource.setDriverClass(valueResolver.resolveStringValue("${prod.db.driverClass}")); return dataSource; } public void setEmbeddedValueResolver(StringValueResolver resolver) { this.valueResolver = resolver; }}
编写测试类如下:
AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext();// 设置对应的环境applicationContext.getEnvironment().setActiveProfiles("dev");applicationContext.register(MainConfigOfProfile.class);applicationContext.refresh();String[] namesForType = applicationContext.getBeanNamesForType(DataSource.class);for (String string : namesForType) { System.out.println("我的数据库配置是:"+string);}
运行结果如下:
我的数据库配置是:devDataSource
从运行结果中我们可以看出我们获取到的是研发环境的数据库配置,需要获取其他环境配置只需设置 setActiveProfiles 参数就行
3,组件注入 3.1 @Import 注解主要从以下几方面来介绍一下@Import 注解
- @Import 注解是什么
- @Import 的三种使用方式
1,@Import 注解是什么
通过导入的方式实现把实例加入 springIOC 容器中
2,@Import 的三种使用方式
通过查看@Import 源码可以发现@Import 注解只能注解在类上,以及唯一的参数 value 上可以配置 3 种类型的值 Configuration,ImportSelector,ImportBeanDefinitionRegistrar,源码如下:
@Target(ElementType.TYPE)@Retention(RetentionPolicy.RUNTIME)@Documentedpublic @interface Import { /** * {@link Configuration}, {@link ImportSelector}, {@link ImportBeanDefinitionRegistrar} * or regular component classes to import. */ Class[] value();}
接下来就分别来看看三种方式具体使用:
a,基于 Configuration 也就是直接填对应的 class 数组
在 bean 目录下新增两个类 Square 和 Circular 如下:
/** * 定义一个圆形 * * @author zhangqh * @date 2020 年 2 月 1 日 */public class Circular {}/** * 定义一个正方形 * * @author zhangqh * @date 2020 年 2 月 1 日 */public class Square {}MainConfig 注解配置中增加@Import 注解如下:@Import({Square.class,Circular.class})@Configurationpublic class MainConfig
测试打印如下:
AnnotationConfigApplicationContext applicationContext2 = new AnnotationConfigApplicationContext(MainConfig.class);String[] beanNames = applicationContext2.getBeanDefinitionNames();for(int i=0;i
关注
打赏
最近更新
- 深拷贝和浅拷贝的区别(重点)
- 【Vue】走进Vue框架世界
- 【云服务器】项目部署—搭建网站—vue电商后台管理系统
- 【React介绍】 一文带你深入React
- 【React】React组件实例的三大属性之state,props,refs(你学废了吗)
- 【脚手架VueCLI】从零开始,创建一个VUE项目
- 【React】深入理解React组件生命周期----图文详解(含代码)
- 【React】DOM的Diffing算法是什么?以及DOM中key的作用----经典面试题
- 【React】1_使用React脚手架创建项目步骤--------详解(含项目结构说明)
- 【React】2_如何使用react脚手架写一个简单的页面?