在上篇文章中,我们都是通过xml文件进行bean或者某些属性的赋值,其实还有另外一种注解的方式,在企业开发中使用的很多,在bean上添加注解,可以快速的将bean注册到ioc容器。
一、使用注解的方式注册bean到IOC容器中
1、首先我们先创建一个maven项目(后面熟悉了,可以直接创建spring项目)
1、new 一个project
2、填写项目相关信息
3、修改maven配置信息
3-1、打开maven配置窗口
3-2、修改maven配置信息
2、配置spring maven依赖
<!-- https://mvnrepository.com/artifact/org.springframework/spring-context -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.2.19.RELEASE</version>
</dependency>
可以看到spring的核心jar包已经全部导入。
3、我们安装MVC结构创建我们项目的目录结构
我们在 com.jony下分别创建了各个层的package。上篇文章中如果我们需要让spring实现这些类的注入,我们需要把这些类都在spring配置文件中进行bean的注入,那我们通过注解该如何实现呢?
4、配置spring的配置文件
我们在resource中新加spring的配置文件,并在xml中通过使用
<co4text:component-scan base-package="com.jony"></context:component-scan>
配置spring需要扫码的package路径,这样在这个package下的类就可以自动被spring扫描到了
5、核心注解
上面通过配置扫描的package可以扫描到java类,但是如果要注入的IOC容器中,还需要给类添加注解,平时用到的主要注解如下:
@Controller:控制器,推荐给controller层添加此注解
@Service:业务逻辑,推荐给业务逻辑层添加此注解
@Repository:仓库管理,推荐给数据访问层添加此注解
@Component:给不属于以上基层的组件添加此注解
注意 :
1、我们虽然人为的给不同的层添加不同的注解,但是在spring看来,可以在任意层添加任意注解 spring底层是不会给具体的层次验证注解,这样写的目的只是为了提高可读性,最偷懒的方式 就是给所有想交由IOC容器管理的bean对象添加component注解
2、注解不可以加到interface上面,因为接口是不允许注册到IOC容器中,spring IOC容器在解析的时候会忽略到接口的注解
5-1、测试注解的效果
5-1-1、不添加注解测试
通过下图我们可以看到,虽然在xml中已经配置了,扫描com.jony的包,但是UserController.java 中并没有注解,这样在通过ioc取类对象的时候就会报错,告诉我们找不到。
5-1-2、添加注解
通过下图我们可以看到,加上@Controller之后,通过IOC我们就可以获取的类对象了。
5-1-3、给所有类添加注解
我们给service实现类,以及dao实现类分别加上@service和@Repository。通常情况下,我们不需要给entity加注解,如果需要可以添加@Component
注意 :当使用注解注册组件和使用配置文件注册组件是一样的,但是要注意:
1、组件的id默认就是组件的类名首字符小写,如果非要改名字的话,直接在注解中添加即可(如我们的UserController在测试类中使用userController),如果需要自定义可以设置为:@Controller("ab")\
2、组件默认情况下都是单例的,如果需要配置多例模式的话,可以在注解下添加@Scope注解
二、定义扫描包时要包含的类和不要包含的类
当定义好基础的扫描包后,在某情况下可能要有选择性的配置是否要注册bean到IOC容器中,此时可以通过如下的方式进行配置。
2-1 设置不扫描
2-1-1、spring 配置文件如下
在 context:conponent-san中添加context:exclude-filter就可以进行排除设置,其中
- type:表示指定过滤的规则 annotation :按照注解进行排除,标注了指定注解的组件不要,expression表示要过滤的注解
assignable :指定排除某个具体的类,按照类排除,expression表示不注册的具体类名
aspectj :后面讲aop的时候说明要使用的aspectj表达式,基本不用
custom :定义一个typeFilter,自己写代码决定哪些类被过滤掉,基本不用
regex :使用正则表达式过滤,基本不用
<!--定义扫描的包-->
<context:component-scan base-package="com.jony">
<!--排除不需要过滤的注解-->
<context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
</context:component-scan>
上面的配置了@Controller不被扫描,那expression的值是如何取到的呢?看下面截图
光标定位到@Controller 右键到 copy Reference即可复制@Controller的完整限定名。
2-1-2、测试如下
通过排除@Controller就无法获得类对象了,如下
2-1-3、通过类名设置不被扫描
2-2、设置扫描
2-2-1、给UserController的注解去掉,然后我们运行测试代码
这样是无法获得类对象的,上面也测试过
2-2-2、通过设置spring配置文件,进行指定扫描
铜鼓下图我们可以看到,设置了context:include-filter就可以让spring进行指定类的扫描了,配置中的type和上面context:exclude-filter中的type设置一直,这边就不做具体演示了。
三、使用@AutoWrite进行自动注入
使用注解的方式实现自动注入需要使用@AutoWired注解。
通过上图我们就可以看到,通过AutoWrite,就可以把对象注入到另外一个bean中了。
注意 :@AutoWrite默认根据ByType去匹配bean的,在Controller中通过@AutoWrite 注入的是UserService,然后我们的UserServiceImpl有实现了UserService接口,因此UserServiceImpl和UserService被spring认为是一个类型,所以此处实际注入的是UserServiceImpl类。
3-1、@AutoWrite的匹配方式
下图中,我们有两个service的实现类
3-1-1、UserController中注入service
package com.jony.controller;
import com.jony.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Controller;
@Controller
public class UserController {
@Autowired
private UserService userService;
public void getUser(){
userService.getUser();
}
}
这样实现我们userService的实现类就变成了两个
3-1-2、测试结果
通过下图,我们可以看到通过userService,进行匹配的时候发现了两个bean,user1ServiceImpl,userServiceImpl,又通过userService根据名词查找的时候,没有匹配到,则就会报错
以上问题,主要就是因为通过类型匹配到了两个,然后通过名字又没找到报错,所以解决方案就是我们修改一下注入的名称即可,如下
3-1-3、通过类型匹配到多个bean,通过名称无法找到ben报错解决
3-1-3-1、修改注入bean的名称
我们把之前userService,改为userServiceImpl,这样在通过类型匹配到user1ServiceImpl,userServiceImpl,然后就可以通过通过名称匹配到userServiceImpl,从而解决报错问题。
3-1-3-2、修改一开始注入bean的名称
可以在service的实现层,@Service添加一个别名,如下
3-1-3-3、通过@Qualifier指定bean名称(覆盖原来bean名称)
@Qualifier("userServiceImpl")
private UserService userService;
通过@Qualifier 设置名称之后,就会通过这个名称去找bean,即使这时候通过类型或者userService可以找到也不会起作用!
3-1-3-3-1、先去掉重复类型的类
删掉了之前User1ServiceImpl,这样根据类型查找的时候就可以匹配到UserServiceImpl,执行没问题
3-1-3-3-1、通过@Qualifier设置存在的bean
@Qualifier("userServiceImpl")
通过上面的配置就可以通过名称直接匹配到UserServiceImpl了
3-1-3-3-1、通过@Qualifier设置不存在的bean
设置了不存在的bean程序就会报错
3-1-3-4、通过@Primary设置主要注入bean
把之前删掉的User1ServiceImpl再次还原,我们既不设置@Service的名称,也不修改Controller注入的名称,同时也不使用@Qualifier的情况下,我们只需要通过@Primary设置通过类型匹配到多个bean中的某一个,将其设置为主要自动注入的类,就可以解决报错问题
3-1-3、总结
如果@AutoWrite根据类型去匹配的时候,如果匹配到了多个,那么就会通过名字去进行匹配,如果名字没有匹配到则会报错.我们就可以通过两种方式修改bean的 别名 或者添加@Qualifier设置bean 名称 来解决,或者通过设置@Primary设置主要自动注入bean来解决。
∗ ∗ 注意 ∗ ∗\color{red}{**注意**}∗∗注意∗∗:修改bean的名字@Controller @Service @Repository @component都可以修改bean的名字
3-2、AutoWrite的使用
我们进入@AutoWrite注解可以看到如下
@AutoWrite可以标注在构造函数、方法、参数、字段或者其他标注注解的类型。
3-3、@Resource和@AutoWrite的区别
在使用自动装配的时候,出了可以使用@AutoWired注解之外,还可以使用@Resource注解,大家需要知道这两个注解的区别。
1、@AutoWired:是spring中提供的注解,@Resource:是jdk中定义的注解,依靠的是java的标准
2、@AutoWired默认是按照类型进行装配,默认情况下要求依赖的对象必须存在,@Resource默认是按照名字进行匹配的,同时可以指定name属性。
3、@AutoWired只适合spring框架,而@Resource扩展性更好
四、通过@Value 设置依赖注入的属性
此处主要理解@Value的使用
@Value("xxx") :设置硬编码值
@Value("${}") :设置读取外部配置文件的值
@Value("#{}") :设置SpEL表达式
4-1、添加一个外部资源文件
4-2、设置spring配置文件读取资源文件
通过 context:property-placeholder 就可以读取外部资源文件了
4-3、我们修改User类,并添加@Value注解
package com.jony.entity;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
@Component
public class User {
//通过@Value设置硬编码值
@Value("张三")
private String name;
//通过${} 设置读取外部资源数据
@Value("${user.age}")
private Integer age;
//通过#{} 设置SpEL表达式
@Value("#{100*100}")
private Integer money;
@Value("#{role.name}")
private String role;
public String getRole() {
return role;
}
public void setRole(String role) {
this.role = role;
}
public Integer getMoney() {
return money;
}
public void setMoney(Integer money) {
this.money = money;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "User{" +
"name='" + name + ''' +
", age=" + age +
", money=" + money +
", role='" + role + ''' +
'}';
}
}
4-4、添加Role类
一定要添加@Component注解,否则在User中的@Value("#{role.name}")将会报错
package com.jony.entity;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
@Component
public class Role {
@Value("管理员")
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
4-4 测试
通过上图可以看到,我们使用@Value 的三种方式已经成功将值注入的bean中。
五、泛型依赖注入
平时实际项目中,我们会把一些公共的方法放到一个base接口中,比如实现对数据库的基本CURD抽出去,然后在各自的服务中进行实现,下面对上面的代码就行修改,目录结构如下
5-1、修改UserService 接口为公共的service,设置公共的CRUD,我这就先只做getBean操作
package com.jony.service;
public interface BaseService<T> {
T getBean();
}
5-2、修改UserServiceImpl
在实现BaseService的时候泛型传入User,最终getBean就是查询User
package com.jony.service.impl;
import com.jony.dao.UserDao;
import com.jony.entity.User;
import com.jony.service.BaseService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
public class UserServiceImpl implements BaseService<User> {
@Autowired
private UserDao userDao;
@Override
public User getBean() {
System.out.println("UserServiceImpl");
userDao.getBean();
return null;
}
}
5-3、修改RoleServiceImpl
在实现BaseService的时候泛型传入Role,最终getBean就是查询Role
package com.jony.service.impl;
import com.jony.dao.UserDao;
import com.jony.entity.Role;
import com.jony.service.BaseService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
public class RoleServiceImpl implements BaseService<Role> {
@Autowired
private UserDao userDao;
@Override
public Role getBean() {
System.out.println("RoleServiceImpl");
userDao.getBean();
return null;
}
}
5-4、UserController测试
在注入BaseService的时候泛型指定User,这样执行的service实现就是UserServiceImpl了,这样也可以解决@AutoWrite通过类型查找多个bean报错的问题了
六、使用@DependOn 设置依赖优先加载顺序
6-1、我们先准备两个类
6-1-1、User.java
package com.jony.entity;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
@Component
public class User {
//通过@Value设置硬编码值
@Value("张三")
private String name;
//通过${} 设置读取外部资源数据
@Value("${user.age}")
private Integer age;
//通过#{} 设置SpEL表达式
@Value("#{100*100}")
private Integer money;
@Value("#{role.name}")
private String role;
public String getRole() {
return role;
}
public void setRole(String role) {
this.role = role;
}
public Integer getMoney() {
return money;
}
public void setMoney(Integer money) {
this.money = money;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "User{" +
"name='" + name + ''' +
", age=" + age +
", money=" + money +
", role='" + role + ''' +
'}';
}
public User(){
System.out.println("User 加载了");
}
}
6-1-2、Role.java
package com.jony.entity;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
@Component
public class Role {
@Value("管理员")
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Role(){
System.out.println("Role 加载了");
}
}
6-2、测试加载
通过给User和Role 两个类添加@Component,让spring 进行管理,然后启动的时候,默认加载顺序如下,那如果我们要让User先加载呢?
6-3、设置优先加载顺序
我们可以在Role中设置User先加载,可以通过如下来进行设置
@DependsOn("user")
最终Role代码为:
package com.jony.entity;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.DependsOn;
import org.springframework.stereotype.Component;
@Component
@DependsOn("user")
public class Role {
@Value("管理员")
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Role(){
System.out.println("Role 加载了");
}
}
6-3-1、通过设置@DependOn查看结果
可以看到这次User先加载了
七、使用@Lazy懒加载
顾明思议,在bean上加上@Lazy,在一开始加载的时候,spring就不会把类加载到IOC中,当使用的时候再进行注入,下面就不做演示了。
八、使用@Scope 设置bean的作用域
默认是单例模式@Scope("singleton"),同一个类只会创建一次。如下图
8-1、通过设置@Scope("prototype")多例注入
package com.jony.entity;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.DependsOn;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;
@Component
@DependsOn("user")
@Scope("prototype")
public class Role {
@Value("管理员")
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Role(){
System.out.println("Role 加载了");
}
}
8-2、测试结果
九、监控Bean的生命周期
9-1、通过@PostConstruct 监控bean的初始化
@PostConstruct
public void init(){
System.out.println("User 初始化");
}
9-2、通过@PreDestroy 监控bean的销毁
@PreDestroy
public void desstroy(){
System.out.println("user 销毁");
}
9-3 测试
通过下图结果,我们就可以看到bean的初始化和注销结果了
总结
1 、本篇文章主要介绍了通过配置spring.xml,利用context:component-scan进行包扫描,达到我们要注入bean的第一步
2 、使用@Controller/@Service/@Repository/@Component添加注解,告诉spring被注解的对象需要添加到IOC中。
3 、通过context:exclude-filter排除不需要注入IOC容器的对象,以及使用context:include-filter添加需要注入IOC容器的对象,这两个配置中都有type属性,需要记住常用的annotation、assignable
- type:表示指定过滤的规则 annotation :按照注解进行排除,标注了指定注解的组件不要,expression表示要过滤的注解
assignable :指定排除某个具体的类,按照类排除,expression表示不注册的具体类名
aspectj :后面讲aop的时候说明要使用的aspectj表达式,基本不用
custom :定义一个typeFilter,自己写代码决定哪些类被过滤掉,基本不用
regex :使用正则表达式过滤,基本不用
4 、掌握@AutoWrite注解的方式,先通过byType查找,如果没找到或者找到多个,再通过byName查找对象。如果通过byType找到多个bean,可以通过修改bean的名称,精准注入,也可以通过修改bean的别名如@Service("userService"),同时还可以通过@Qualifier指定bean的名称,或者通过设置@primart设置有限加载的bean来解决报错问题。
5 、知道@AutoWrite和@Resource的区别。两者一个是spring的一个是jdk的,然后一个是先通过byType一个是通过byName。
6 、@Value的使用,可以通过直接赋值,或者${}读取外部资料文件,或者使用#{}使用SpEL表达式。
7 、通过@AutoWrite可以注入泛型,解决底层重复的代码。
8 、使用@DependsOn设置依赖优先加载的类。
9 、使用@Lazy 设置懒加载
10 、使用@scope设置bean的作用域,分别有singleton和prototype。
11 、使用@PostConstruct监控bean的初始化,以及使用@PreDestroy监控bean的注销。