Spring学习记录之Spring-IoC注解式开发
Spring学习记录之Spring-IoC注解式开发
前言
这篇文章是我第二次学习b站老杜的spring
相关课程所进行的学习记录
,算是对课程内容及笔记的二次整理,以自己的理解方式进行二次记录,其中理解可能存在错误,欢迎且接受各位大佬们的批评指正;
关于本笔记,只是我对于相关知识遗忘时快速查阅了解使用,至于课程中实际实验配置等,也只是记录关键,并不会记录详细步骤,若想了解可以关注我博客的项目经验模块,我会在实际项目开发过程中总结项目经验,在该模块发布!
学习视频地址:https://www.bilibili.com/video/BV1Ft4y1g7Fb/
视频配套笔记:https://www.yuque.com/dujubin/ltckqu/kipzgd?singleDoc# 《Spring6》 密码:mg9b
目录
一、我个人对这部分学习的一些见解
这部分需要重点掌握,该部分讲到的都是Spring
实际项目开发中的常用注解,同时Spring6本身也倡导全注解开发。这部分的学习主要是为了通过注解的方式代替我们前面学习过程中所使用到的xml
文件,同时在我目前参与过的实际公司项目中,都是在用注解开发。这样是否代表前面的知识所谓"经典白雪"了呢?其实并不是这样的,我在初学时也觉得学了那么多最终都抛弃不用,让人很恼火,但是待你学习更加深入时会发现,前面的知识可以很好的帮你理解Spring
的实际执行原理。这一点显然比你上来就学习注解开发要好得多。
这部分我将继续引用老杜的笔记。
二、回顾注解
在Spring
中注解的存在主要是为了简化XML
的配置。Spring6倡导全注解开发。
我们来回顾一下:
- 第一:注解怎么定义,注解中的属性怎么定义?
- 第二:注解怎么使用?
- 第三:通过反射机制怎么读取注解?
① 注解怎么定义,注解中的属性怎么定义?
自定义注解:
package com.powernode.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target(value = {ElementType.TYPE})
@Retention(value = RetentionPolicy.RUNTIME)
public @interface Component {
String value();
}
以上是自定义了一个注解:Component
该注解上面修饰的注解包括:Target
注解和Retention
注解,这两个注解被称为元注解(即Java
本身自带的注解)。
Target
注解用来设置Component
注解可以出现的位置,以上代表表示Component
注解只能用在类和接口上。
Retention
注解用来设置Component
注解的保持性策略(即该注解可以保留在哪些阶段),以上代表Component
注解可以被反射机制读取(保留在运行时)。
String value();
是Component
注解中的一个属性。该属性类型String
,属性名是value
。
② 注解怎么使用?
User
package com.powernode.bean;
import com.powernode.annotation.Component;
@Component(value = "userBean")
public class User {
}
用法简单,语法格式:@注解类型名(属性名=属性值, 属性名=属性值, 属性名=属性值......)
userBean
为什么使用双引号括起来,因为value
属性是String
类型,字符串。
另外如果属性名是value
,则在使用的时候可以省略属性名,例如:
User
package com.powernode.bean;
import com.powernode.annotation.Component;
//@Component(value = "userBean")
@Component("userBean")
public class User {
}
③ 通过反射机制怎么读取注解?
接下来,我们来写一段程序,当Bean
类上有Component
注解时,则实例化Bean
对象,如果没有,则不实例化对象。
我们准备两个Bean
,一个上面有注解,一个上面没有注解。
有注解的Bean
:
package com.powernode.bean;
import com.powernode.annotation.Component;
@Component("userBean")
public class User {
}
没有注解的Bean
:
package com.powernode.bean;
public class Vip {
}
假设我们现在只知道包名:com.powernode.bean
。至于这个包下有多少个Bean
我们不知道。哪些Bean
上有注解,哪些Bean
上没有注解,这些我们都不知道,如何通过程序全自动化判断。
反射解析注解:
package com.powernode.test;
import com.powernode.annotation.Component;
import java.io.File;
import java.net.URL;
import java.util.Arrays;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Map;
/**
* @author 动力节点
* @version 1.0
* @className Test
* @since 1.0
**/
public class Test {
public static void main(String[] args) throws Exception {
// 存放Bean的Map集合。key存储beanId。value存储Bean。
Map<String,Object> beanMap = new HashMap<>();
String packageName = "com.powernode.bean";
String path = packageName.replaceAll("\\.", "/");
URL url = ClassLoader.getSystemClassLoader().getResource(path);
File file = new File(url.getPath());
File[] files = file.listFiles();
Arrays.stream(files).forEach(f -> {
String className = packageName + "." + f.getName().split("\\.")[0];
try {
Class<?> clazz = Class.forName(className);
if (clazz.isAnnotationPresent(Component.class)) {
Component component = clazz.getAnnotation(Component.class);
String beanId = component.value();
Object bean = clazz.newInstance();
beanMap.put(beanId, bean);
}
} catch (Exception e) {
e.printStackTrace();
}
});
System.out.println(beanMap);
}
}
执行结果:
经过以上回顾注解的案例,你是否对为什么注解能够代替xml
配置文件,有了模糊的概念?
三、声明Bean的注解
补充说明:所谓声明Bean的注解,其实就是代替xml
文件中的如下配置。
负责声明Bean
的注解,常见的包括四个:
@Component
@Controller
@Service
@Repository
源码如下:
@Component
注解
package com.powernode.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target(value = {ElementType.TYPE})
@Retention(value = RetentionPolicy.RUNTIME)
public @interface Component {
String value();
}
@Controller
注解
package org.springframework.stereotype;
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.core.annotation.AliasFor;
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface Controller {
@AliasFor(
annotation = Component.class
)
String value() default "";
}
@Service
注解
package org.springframework.stereotype;
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.core.annotation.AliasFor;
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface Service {
@AliasFor(
annotation = Component.class
)
String value() default "";
}
@Repository
注解
package org.springframework.stereotype;
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.core.annotation.AliasFor;
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface Repository {
@AliasFor(
annotation = Component.class
)
String value() default "";
}
通过源码可以看到,@Controller
、@Service
、@Repository
这三个注解都是@Component
注解的别名。
也就是说:这四个注解的功能都一样。用哪个都可以。
只是为了增强程序的可读性,建议:
- 控制器类上使用:
Controller
service
类上使用:Service
dao
类上使用:Repository
他们都是只有一个value
属性。value
属性用来指定bean
的id
,也就是bean
的名字。
四、Spring注解的使用
如何使用以上的注解呢?
- 第一步:加入
aop
的依赖 - 第二步:在配置文件中添加
context
命名空间 - 第三步:在配置文件中指定扫描的包
- 第四步:在
Bean
类上使用注解
第一步:加入aop的依赖
我们可以看到当加入spring-context
依赖之后,会关联加入aop
的依赖。所以这一步不用做。
第二步:在配置文件中添加context命名空间
spring.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">
</beans>
第三步:在配置文件中指定要扫描的包
spring.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">
<context:component-scan base-package="com.powernode.spring6.bean"/>
</beans>
第四步:在Bean类上使用注解
User
package com.powernode.spring6.bean;
import org.springframework.stereotype.Component;
@Component(value = "userBean")
public class User {
}
编写测试程序:
package com.powernode.spring6.test;
import com.powernode.spring6.bean.User;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class AnnotationTest {
@Test
public void testBean(){
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.xml");
User userBean = applicationContext.getBean("userBean", User.class);
System.out.println(userBean);
}
}
执行结果:
如果注解的属性名是value
,那么value
是可以省略的。
package com.powernode.spring6.bean;
import org.springframework.stereotype.Component;
@Component("vipBean")
public class Vip {
}
测试程序:
package com.powernode.spring6.test;
import com.powernode.spring6.bean.Vip;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class AnnotationTest {
@Test
public void testBean(){
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.xml");
Vip vipBean = applicationContext.getBean("vipBean", Vip.class);
System.out.println(vipBean);
}
}
执行结果:
如果把value
属性彻底去掉,spring
会被Bean
自动取名吗?会的。并且默认名字的规律是:Bean
类名首字母小写即可。
package com.powernode.spring6.bean;
import org.springframework.stereotype.Component;
@Component
public class BankDao {
}
也就是说,这个BankDao
的bean的名字为:bankDao
测试一下
package com.powernode.spring6.test;
import com.powernode.spring6.bean.BankDao;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class AnnotationTest {
@Test
public void testBean(){
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.xml");
BankDao bankDao = applicationContext.getBean("bankDao", BankDao.class);
System.out.println(bankDao);
}
}
执行结果:
我们将Component
注解换成其它三个注解,看看是否可以用:
package com.powernode.spring6.bean;
import org.springframework.stereotype.Controller;
@Controller
public class BankDao {
}
执行结果:
剩下的两个注解大家可以测试一下。
如果是多个包怎么办?有两种解决方案:
- 第一种:在配置文件中指定多个包,用逗号隔开。
- 第二种:指定多个包的共同父包。
先来测试一下逗号(英文)的方式:
创建一个新的包:bean2
,定义一个Bean
类。
bean2
包下的Order
:
package com.powernode.spring6.bean2;
import org.springframework.stereotype.Service;
@Service
public class Order {
}
配置文件修改:
spring.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">
<context:component-scan base-package="com.powernode.spring6.bean,com.powernode.spring6.bean2"/>
</beans>
测试程序:
package com.powernode.spring6.test;
import com.powernode.spring6.bean.BankDao;
import com.powernode.spring6.bean2.Order;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class AnnotationTest {
@Test
public void testBean(){
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.xml");
BankDao bankDao = applicationContext.getBean("bankDao", BankDao.class);
System.out.println(bankDao);
Order order = applicationContext.getBean("order", Order.class);
System.out.println(order);
}
}
执行结果:
我们再来看看,指定共同的父包行不行:
<?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">
<context:component-scan base-package="com.powernode.spring6"/>
</beans>
执行测试程序:
五、选择性实例化Bean
假设在某个包下有很多Bean
,有的Bean
上标注了Component
,有的标注了Controller
,有的标注了Service
,有的标注了Repository
,现在由于某种特殊业务的需要,只允许其中所有的Controller
参与Bean
管理,其他的都不实例化。这应该怎么办呢?
这里为了方便,将这几个类都定义到同一个java
源文件中了:
package com.powernode.spring6.bean3;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Controller;
import org.springframework.stereotype.Repository;
import org.springframework.stereotype.Service;
@Component
public class A {
public A() {
System.out.println("A的无参数构造方法执行");
}
}
@Controller
class B {
public B() {
System.out.println("B的无参数构造方法执行");
}
}
@Service
class C {
public C() {
System.out.println("C的无参数构造方法执行");
}
}
@Repository
class D {
public D() {
System.out.println("D的无参数构造方法执行");
}
}
@Controller
class E {
public E() {
System.out.println("E的无参数构造方法执行");
}
}
@Controller
class F {
public F() {
System.out.println("F的无参数构造方法执行");
}
}
我只想实例化bean3
包下的Controller
。配置文件这样写:
spring-choose.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">
<context:component-scan base-package="com.powernode.spring6.bean3" use-default-filters="false">
<context:include-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
</context:component-scan>
</beans>
use-default-filters="true"
表示:使用spring
默认的规则,只要有Component
、Controller
、Service
、Repository
中的任意一个注解标注,则进行实例化。
use-default-filters="false"
表示:不再spring
默认实例化规则,即使有Component
、Controller
、Service
、Repository
这些注解标注,也不再实例化。
<context:include-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
表示只有Controller
进行实例化。
测试程序:
@Test
public void testChoose(){
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring-choose.xml");
}
执行结果:
也可以将use-default-filters
设置为true
(不写就是true
),并且采用exclude-filter
方式排出哪些注解标注的Bean
不参与实例化:
spring-choose.xml
<context:component-scan base-package="com.powernode.spring6.bean3">
<context:exclude-filter type="annotation" expression="org.springframework.stereotype.Repository"/>
<context:exclude-filter type="annotation" expression="org.springframework.stereotype.Service"/>
<context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
</context:component-scan>
执行测试程序:
六、注入Bean的注解
@Component、@Controller、@Service、@Repository
这四个注解是用来声明Bean
的,声明后这些Bean
将被实例化。接下来我们看一下,如何给Bean
的属性赋值。给Bean
属性赋值需要用到这些注解:
@Value
@Autowired
@Qualifier
@Resource
① @Value
当属性的类型是简单类型时,可以使用@Value
注解进行注入。
User
package com.powernode.spring6.bean4;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
@Component
public class User {
@Value(value = "zhangsan")
private String name;
@Value("20")
private int age;
@Override
public String toString() {
return "User{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
开启包扫描:
spring-injection.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">
<context:component-scan base-package="com.powernode.spring6.bean4"/>
</beans>
测试程序:
@Test
public void testValue(){
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring-injection.xml");
Object user = applicationContext.getBean("user");
System.out.println(user);
}
执行结果:
通过以上代码可以发现,我们并没有给属性提供setter
方法,但仍然可以完成属性赋值。
如果提供setter
方法,并且在setter
方法上添加@Value
注解,可以完成注入吗?尝试一下:
User
package com.powernode.spring6.bean4;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
@Component
public class User {
private String name;
private int age;
@Value("李四")
public void setName(String name) {
this.name = name;
}
@Value("30")
public void setAge(int age) {
this.age = age;
}
@Override
public String toString() {
return "User{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
执行结果:
通过测试可以得知,@Value
注解可以直接使用在属性上,也可以使用在setter
方法上。都是可以的。都可以完成属性的赋值。
为了简化代码,以后我们一般不提供setter
方法,直接在属性上使用@Value
注解完成属性赋值。
出于好奇,我们再来测试一下,是否能够通过构造方法完成注入:
User
package com.powernode.spring6.bean4;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
@Component
public class User {
private String name;
private int age;
public User(@Value("隔壁老王") String name, @Value("33") int age) {
this.name = name;
this.age = age;
}
@Override
public String toString() {
return "User{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
执行结果:
通过测试得知:@Value
注解可以出现在属性上、setter
方法上、以及构造方法的形参上。可见Spring
给我们提供了多样化的注入。太灵活了。
② @Autowired与@Qualifier
@Autowired
注解可以用来注入非简单类型。被翻译为:自动连线的,或者自动装配。
单独使用@Autowired
注解,默认根据类型装配。【默认是byType
】
看一下它的源码:
@Autowired
源码
package org.springframework.beans.factory.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;
@Target({ElementType.CONSTRUCTOR, ElementType.METHOD, ElementType.PARAMETER, ElementType.FIELD, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Autowired {
boolean required() default true;
}
源码中有两处需要注意:
-
第一处:该注解可以标注在哪里?
-
- 构造方法上
- 方法上
- 形参上
- 属性上
- 注解上
-
第二处:该注解有一个
required
属性,默认值是true
,表示在注入的时候要求被注入的Bean
必须是存在的,如果不存在则报错。如果required
属性设置为false
,表示注入的Bean
存在或者不存在都没关系,存在的话就注入,不存在的话,也不报错。
我们先在属性上使用@Autowired
注解:
UserDao
接口:
package com.powernode.spring6.dao;
public interface UserDao {
void insert();
}
UserDao
实现类:
package com.powernode.spring6.dao;
import org.springframework.stereotype.Repository;
@Repository //纳入bean管理
public class UserDaoForMySQL implements UserDao{
@Override
public void insert() {
System.out.println("正在向mysql数据库插入User数据");
}
}
UserService
package com.powernode.spring6.service;
import com.powernode.spring6.dao.UserDao;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service // 纳入bean管理
public class UserService {
@Autowired // 在属性上注入
private UserDao userDao;
// 没有提供构造方法和setter方法。
public void save(){
userDao.insert();
}
}
配置包扫描:
<?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">
<context:component-scan base-package="com.powernode.spring6.dao,com.powernode.spring6.service"/>
</beans>
测试程序:
@Test
public void testAutowired(){
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring-injection.xml");
UserService userService = applicationContext.getBean("userService", UserService.class);
userService.save();
}
执行结果:
以上构造方法和setter
方法都没有提供,经过测试,仍然可以注入成功。
接下来,再来测试一下@Autowired
注解出现在setter
方法上:
UserService
package com.powernode.spring6.service;
import com.powernode.spring6.dao.UserDao;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
public class UserService {
private UserDao userDao;
@Autowired
public void setUserDao(UserDao userDao) {
this.userDao = userDao;
}
public void save(){
userDao.insert();
}
}
执行结果:
我们再来看看能不能出现在构造方法上:
UserService
package com.powernode.spring6.service;
import com.powernode.spring6.dao.UserDao;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
public class UserService {
private UserDao userDao;
@Autowired
public UserService(UserDao userDao) {
this.userDao = userDao;
}
public void save(){
userDao.insert();
}
}
执行结果:
再来看看,这个注解能不能只标注在构造方法的形参上:
UserService
package com.powernode.spring6.service;
import com.powernode.spring6.dao.UserDao;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
public class UserService {
private UserDao userDao;
public UserService(@Autowired UserDao userDao) {
this.userDao = userDao;
}
public void save(){
userDao.insert();
}
}
执行结果:
还有更劲爆的,当有参数的构造方法只有一个时,@Autowired
注解可以省略。
package com.powernode.spring6.service;
import com.powernode.spring6.dao.UserDao;
import org.springframework.stereotype.Service;
@Service
public class UserService {
private UserDao userDao;
public UserService(UserDao userDao) {
this.userDao = userDao;
}
public void save(){
userDao.insert();
}
}
执行结果:
当然,如果有多个构造方法,@Autowired
肯定是不能省略的。
UserService
package com.powernode.spring6.service;
import com.powernode.spring6.dao.UserDao;
import org.springframework.stereotype.Service;
@Service
public class UserService {
private UserDao userDao;
public UserService(UserDao userDao) {
this.userDao = userDao;
}
public UserService(){
}
public void save(){
userDao.insert();
}
}
执行结果:
到此为止,我们已经清楚@Autowired
注解可以出现在哪些位置了。
@Autowired
注解默认是byType
进行注入的,也就是说根据类型注入的,如果以上程序中,UserDao
接口还有另外一个实现类,会出现问题吗?
UserDaoForOracle
,接口另一个实现类:
package com.powernode.spring6.dao;
import org.springframework.stereotype.Repository;
@Repository //纳入bean管理
public class UserDaoForOracle implements UserDao{
@Override
public void insert() {
System.out.println("正在向Oracle数据库插入User数据");
}
}
当你写完这个新的实现类之后,此时IDEA
工具已经提示错误信息了:
错误信息中说:不能装配,UserDao
这个Bean
的数量大于1.
怎么解决这个问题呢?当然要byName
,根据名称进行装配了。
@Autowired
注解和@Qualifier
注解联合起来才可以根据名称进行装配,在@Qualifier
注解中指定Bean
名称。
UserDaoForOracle
package com.powernode.spring6.dao;
import org.springframework.stereotype.Repository;
@Repository // 这里没有给bean起名,默认名字是:userDaoForOracle
public class UserDaoForOracle implements UserDao{
@Override
public void insert() {
System.out.println("正在向Oracle数据库插入User数据");
}
}
UserService
package com.powernode.spring6.service;
import com.powernode.spring6.dao.UserDao;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Service;
@Service
public class UserService {
private UserDao userDao;
@Autowired
@Qualifier("userDaoForOracle") // 这个是bean的名字。
public void setUserDao(UserDao userDao) {
this.userDao = userDao;
}
public void save(){
userDao.insert();
}
}
执行结果:
③ @Resource
@Resource
注解也可以完成非简单类型注入。那它和@Autowired
注解有什么区别?
@Resource
注解是JDK
扩展包中的,也就是说属于JDK
的一部分。所以该注解是标准注解,更加具有通用性。(JSR-250标准中制定的注解类型。JSR是Java规范提案。)@Autowired
注解是Spring
框架自己的。@Resource
注解默认根据名称装配byName
,未指定name
时,使用属性名作为name
。通过name
找不到的话会自动启动通过类型byType
装配。@Autowired
注解默认根据类型装配byType
,如果想根据名称装配,需要配合@Qualifier
注解一起用。@Resource
注解用在属性上、setter
方法上。@Autowired
注解用在属性上、setter
方法上、构造方法上、构造方法参数上。
@Resource注解属于JDK扩展包,所以不在JDK当中,需要额外引入以下依赖:【如果是JDK8的话不需要额外引入依赖。高于JDK11或低于JDK8需要引入以下依赖。】
如果你是Spring6+
版本请使用这个依赖:
<dependency>
<groupId>jakarta.annotation</groupId>
<artifactId>jakarta.annotation-api</artifactId>
<version>2.1.1</version>
</dependency>
一定要注意:如果你用Spring6
,要知道Spring6
不再支持JavaEE
,它支持的是JakartaEE9
。(Oracle
把JavaEE
贡献给Apache
了,Apache
把JavaEE
的名字改成JakartaEE
了,大家之前所接触的所有的 javax.*
包名统一修改为 jakarta.*
包名了。)
如果你是spring5-
版本请使用这个依赖:
<dependency>
<groupId>javax.annotation</groupId>
<artifactId>javax.annotation-api</artifactId>
<version>1.3.2</version>
</dependency>
@Resource
注解的源码如下:
测试一下:
给这个UserDaoForOracle
起名xyz
:
package com.powernode.spring6.dao;
import org.springframework.stereotype.Repository;
@Repository("xyz")
public class UserDaoForOracle implements UserDao{
@Override
public void insert() {
System.out.println("正在向Oracle数据库插入User数据");
}
}
在UserService
中使用Resource
注解根据name
注入:
package com.powernode.spring6.service;
import com.powernode.spring6.dao.UserDao;
import jakarta.annotation.Resource;
import org.springframework.stereotype.Service;
@Service
public class UserService {
@Resource(name = "xyz")
private UserDao userDao;
public void save(){
userDao.insert();
}
}
执行测试程序:
我们把UserDaoForOracle
的名字xyz
修改为userDao
,让这个Bean
的名字和UserService
类中的UserDao
属性名一致:
UserDaoForOracle
package com.powernode.spring6.dao;
import org.springframework.stereotype.Repository;
@Repository("userDao")
public class UserDaoForOracle implements UserDao{
@Override
public void insert() {
System.out.println("正在向Oracle数据库插入User数据");
}
}
UserService
类中Resource
注解并没有指定name
:
package com.powernode.spring6.service;
import com.powernode.spring6.dao.UserDao;
import jakarta.annotation.Resource;
import org.springframework.stereotype.Service;
@Service
public class UserService {
@Resource
private UserDao userDao;
public void save(){
userDao.insert();
}
}
执行测试程序:
通过测试得知,当@Resource
注解使用时没有指定name
的时候,还是根据name
进行查找,这个name
是属性名。
接下来把UserService
类中的属性名修改一下:
UserService
的属性名修改为userDao2
:
package com.powernode.spring6.service;
import com.powernode.spring6.dao.UserDao;
import jakarta.annotation.Resource;
import org.springframework.stereotype.Service;
@Service
public class UserService {
@Resource
private UserDao userDao2;
public void save(){
userDao2.insert();
}
}
执行结果:
根据异常信息得知:显然当通过name
找不到的时候,自然会启动byType
进行注入。以上的错误是因为UserDao
接口下有两个实现类导致的。所以根据类型注入就会报错。
我们再来看@Resource
注解使用在setter
方法上可以吗?
UserService
添加setter
方法并使用注解标注:
package com.powernode.spring6.service;
import com.powernode.spring6.dao.UserDao;
import jakarta.annotation.Resource;
import org.springframework.stereotype.Service;
@Service
public class UserService {
private UserDao userDao;
@Resource
public void setUserDao(UserDao userDao) {
this.userDao = userDao;
}
public void save(){
userDao.insert();
}
}
注意这个setter
方法的方法名,setUserDao
去掉set
之后,将首字母变小写userDao
,userDao
就是name
执行结果:
当然,也可以指定name
:
UserService
package com.powernode.spring6.service;
import com.powernode.spring6.dao.UserDao;
import jakarta.annotation.Resource;
import org.springframework.stereotype.Service;
@Service
public class UserService {
private UserDao userDao;
@Resource(name = "userDaoForMySQL")
public void setUserDao(UserDao userDao) {
this.userDao = userDao;
}
public void save(){
userDao.insert();
}
}
执行结果:
一句话总结@Resource
注解:默认byName
注入,没有指定name
时把属性名当做name
,根据name
找不到时,才会byType
注入。byType
注入时,某种类型的Bean
只能有一个。
七、全注解式开发
在上述学习中,虽然不用把bean
配置到xml
文件里面了,但是我们还是需要依赖xml
文件配置包扫描路径等配置项,并没有实现全注解开发的要求,所谓的全注解开发就是不再使用spring
配置文件了。写一个配置类来代替配置文件。
配置类代替spring
配置文件:
package com.powernode.spring6.config;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.ComponentScans;
import org.springframework.context.annotation.Configuration;
@Configuration
@ComponentScan({"com.powernode.spring6.dao", "com.powernode.spring6.service"})
public class Spring6Configuration {
}
编写测试程序:不再new ClassPathXmlApplicationContext()
对象了。
@Test
public void testNoXml(){
ApplicationContext applicationContext = new AnnotationConfigApplicationContext(Spring6Configuration.class);
UserService userService = applicationContext.getBean("userService", UserService.class);
userService.save();
}
执行结果:
八、总结
这部分我们了解到:
- 在
Spring
中注解的存在主要是为了简化XML
的配置,Spring6倡导全注解开发。 - 如果注解的属性名是
value
,那么value
是可以省略的。 @Controller
、@Service
、@Repository
这三个注解都是@Component
注解的别名。- 声明
Bean
的注解只有一个value
属性用来指定bean
的id
。 - 把声明
Bean
的注解的value
属性彻底去掉(即也不保留value
值),spring
会给Bean
自动取名为Bean
类名首字母小写。 - 选择性实例化
bean
的方式。 - 当属性的类型是简单类型时,可以使用
@Value
注解进行注入。 - 使用
@Value
注解可以不提供setter
方法,一般为了简化代码我们通常也不提供setter
方法。 @Value
注解可以出现在属性上、setter
方法上、以及构造方法的形参上。@Autowired
注解可以用来注入非简单类型。单独使用@Autowired
注解,默认根据类型装配。【默认是byType
】。- 当有参数的构造方法只有一个时,
@Autowired
注解可以省略。 @Autowired
注解可以出现在:属性上、构造方法上、构造方法的参数上、setter
方法上。@Autowired
注解默认根据类型注入。如果要根据名称注入的话,需要配合@Qualifier
注解一起使用。@Resource
注解默认根据名称装配byName
,未指定name
时,使用属性名作为name
。通过name
找不到的话会自动启动通过类型byType
装配。@Resource
注解用在属性上、setter
方法上。@Resource
注解属于JDK
扩展包,所以不在JDK当中
,需要额外引入以下依赖:【如果是JDK8的话不需要额外引入依赖。高于JDK11或低于JDK8需要引入依赖。】- 通过配置类来代替
spring-xml
配置文件来实现全注解开发。
这里需要去了解老杜这节相关讲解,可以直接点击下面链接跳转到对应课程学习了解!