目录
十二、Spring IoC注解式开发
12.1 回顾注解
注解怎么定义,注解中的属性怎么定义?
注解怎么使用?
1--通过反射机制怎么读取注解?
代码
运行结果
2--通过反射机制怎么读取注解?
代码
运行结果
12.2 声明Bean的注解
12.3 Spring注解的使用
第一步:加入aop的依赖
在pom.xml上加入spring context依赖:
第二步:在配置文件中添加context命名空间
第三步:在配置文件中指定要扫描的包
即加入代码:
完整代码:
第四步:在Bean类上使用注解
Bean类:User、Student、Order、Vip
编写测试程序:
执行结果:
上述程序的pom文件完整代码:
如果是多个包怎么办?有两种解决方案:
12.4 选择性实例化Bean
测试程序
执行结果:
12.5 负责注入的注解【非常重要!!!!】
12.5.1 @Value
代码
运行结果:
12.5.2 @Autowired与@Qualifier
看一下它的源码:
源码中有两处需要注意:
我们先在属性上使用@Autowired注解:
配置包扫描
测试程序
执行结果:
接下来,再来测试一下@Autowired注解出现在setter方法上:
执行结果:
我们再来看看能不能出现在构造方法上:
执行结果:
再来看看,这个注解能不能只标注在构造方法的形参上:
执行结果:
还有更劲爆的,当有参数的构造方法只有一个时,@Autowired注解可以省略。
执行结果:
当然,如果有多个构造方法,@Autowired肯定是不能省略的。
执行结果:
总结:
12.5.3 @Resource
@Resource注解的源码如下:
测试一下:
给这个UserDaoForOracle起名xyz
在UserService中使用Resource注解根据name注入
执行测试程序:
我们把UserDaoForOracle的名字xyz修改为userDao,让这个Bean的名字和UserService类中的UserDao属性名一致:
执行测试程序:
UserService的属性名修改为userDao2
执行结果:
UserService添加setter方法并使用注解标注
执行结果:
当然,也可以指定name:
执行结果:
一句话总结@Resource注解:
12.6 全注解式开发
配置类代替spring配置文件
编写测试程序:不再new ClassPathXmlApplicationContext()对象了。
执行结果
十二、Spring IoC注解式开发
12.1 回顾注解
注解的存在主要是为了简化XML的配置。Spring6倡导全注解开发。
来回顾一下:
- 第一:注解怎么定义,注解中的属性怎么定义?
- 第二:注解怎么使用?
- 第三:通过反射机制怎么读取注解?
注解怎么定义,注解中的属性怎么定义?
package com.dong.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注解,这两个注解被称为元注解。
Target注解用来设置Component注解可以出现的位置,以上代表表示Component注解只能用在类和接口上。
Retention注解用来设置Component注解的保持性策略,以上代表Component注解可以被反射机制读取。
String value(); 是Component注解中的一个属性。该属性类型String,属性名是value。
注解怎么使用?
package com.dong.bean;
import com.powernode.annotation.Component;
@Component(value = "userBean")
public class User {
}
用法简单,语法格式:@注解类型名(属性名=属性值, 属性名=属性值, 属性名=属性值......)
userBean为什么使用双引号括起来,因为value属性是String类型,字符串。
另外如果属性名是value,则在使用的时候可以省略属性名,例如:
package com.dong.bean;
import com.powernode.annotation.Component;
//@Component(value = "userBean")
@Component("userBean")
public class User {
}
1--通过反射机制怎么读取注解?
代码
@Component 注解
package com.dong.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* 自定义注解
*/
//@Target注解用来修饰 @Component可以出现的位置
@Target(ElementType.TYPE)
//@Retention也是一个元注解,用来标注 @Component 注解最终保留在class文件当中,并且可以呗反射机制读取
@Retention(RetentionPolicy.RUNTIME)
public @interface Component {
//定义注解属性
String value();
}
User.java
package com.dong.bean;
import com.dong.annotation.Component;
@Component("userBean")
public class User {
/*private String name;*/
}
测试类:ReflectAnnotationTest1.java
package com.dong.client;
import com.dong.annotation.Component;
public class ReflectAnnotationTest1 {
public static void main(String[] args) throws Exception {
//通过反射机制读取注解
//获取类
Class<?> clazz = Class.forName("com.dong.bean.User");
//先判断你这类上有没有这个 @Component注解
if (clazz.isAnnotationPresent(Component.class)) {
//获取类上的注解
Component annotation = clazz.getAnnotation(Component.class);
//访问注解属性
System.out.println(annotation.value());
}
}
}
运行结果
2--通过反射机制怎么读取注解?
接下来,我们来写一段程序,当Bean类上有Component注解时,则实例化Bean对象,如果没有,则不实例化对象。
我们准备3个Bean,2个上面有注解,1个上面没有注解。
代码
自定义@Component 注解:
package com.dong.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* 自定义注解
*/
//@Target注解用来修饰 @Component可以出现的位置
@Target(ElementType.TYPE)
//@Retention也是一个元注解,用来标注 @Component 注解最终保留在class文件当中,并且可以呗反射机制读取
@Retention(RetentionPolicy.RUNTIME)
public @interface Component {
//定义注解属性
String value();
}
1-
package com.dong.bean;
import com.dong.annotation.Component;
@Component("userBean")
public class User {
/*private String name;*/
}
2-
package com.dong.bean;
import com.dong.annotation.Component;
@Component("orderBean")
public class Order {
}
3-【没有注解】
package com.dong.bean;
public class Vip {
}
测试类ComponentScan.Java
package com.dong.client;
import com.dong.annotation.Component;
import java.io.File;
import java.net.URL;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
public class ComponentScan {
public static void main(String[] args) {
Map<String,Object>beanMap=new HashMap<>();
//目前只知道一个包的名字,扫描这个包下的所有类,当这个类上有 @Component注解的时候,实例化该对象,然后放到Map集合中。
String packageName = "com.dong.bean";
//开始编写程序
String packagePath = packageName.replaceAll("\\.", "/");
// System.out.println(packagePath);
URL url = ClassLoader.getSystemClassLoader().getResource(packagePath);
//System.out.println(url);
String path = null;
if (url != null) {
path = url.getPath();
}
//System.out.println(path);//得到绝对路径:/D:/CS/Code/IdeaCode/spring6-demo/review-annotation/target/classes/com/dong/bean
File file = null;
if (path != null) {
file = new File(path);
}
File[] files = new File[0];//获取所有的子文件
if (file != null) {
files = file.listFiles();
}
if (files != null) {
Arrays.stream(files).forEach(f -> {
try{
/* f:
Order.class
User.class
Vip.class
*/
//System.out.println(f.getName());
// System.out.println(f.getName().split("\\.")[0]);
/*
Order 、User、Vip
*/
//拼凑出全限定类名: 包名+“.”+类名
String className=packageName+"."+f.getName().split("\\.")[0];
//System.out.println(className);//得到类名:com.dong.bean.Order、com.dong.bean.User、com.dong.bean.Vip
//通过反射机制解析注解
Class<?> clazz = Class.forName(className);
//先判断这些类上有没有 @Component注解
if (clazz.isAnnotationPresent(Component.class)) {
//有这个注解就创建对象
//获取注解
Component annotation = clazz.getAnnotation(Component.class);
String id = annotation.value();
//有这个注解的都要创建对象
Object obj = clazz.newInstance();
beanMap.put(id,obj);
}
}catch (Exception e){
e.printStackTrace();
}
});
}
System.out.println(beanMap);
}
}
运行结果
创建了两个对象
12.2 声明Bean的注解
负责声明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的名字。
12.3 Spring注解的使用
如何使用以上的注解呢?
- 第一步:加入aop的依赖
- 第二步:在配置文件中添加context命名空间
- 第三步:在配置文件中指定扫描的包
- 第四步:在Bean类上使用注解
第一步:加入aop的依赖
我们可以看到当加入spring-context依赖之后,会关联加入aop的依赖。所以这一步不用做。
在pom.xml上加入spring context依赖:
<!--spring context依赖-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>6.0.11</version>
</dependency>
第二步:在配置文件中添加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框架指定要扫描哪些包中的类-->
<context:component-scan base-package="com.dong.spring6.bean"/>
完整代码:
<?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">
<!--给spring框架指定要扫描哪些包中的类-->
<context:component-scan base-package="com.dong.spring6.bean"/>
</beans>
第四步:在Bean类上使用注解
Bean类:User、Student、Order、Vip
package com.dong.spring6.bean;
import org.springframework.stereotype.Component;
@Component
public class User {
}
package com.dong.spring6.bean;
import org.springframework.stereotype.Repository;
@Repository(value = "studentBean")
public class Student {
}
如果注解的属性名是value,那么value是可以省略的。
如果把value属性彻底去掉,spring会被Bean自动取名吗?会的。并且默认名字的规律是:Bean类名首字母小写即可。
package com.dong.spring6.bean;
import org.springframework.stereotype.Controller;
@Controller
public class Vip {
}
package com.dong.spring6.bean;
import org.springframework.stereotype.Service;
@Service
public class Order {
}
编写测试程序:
IOCAnnotationTest.java
package com.dong.spring6.test;
import com.dong.spring6.bean.Order;
import com.dong.spring6.bean.Student;
import com.dong.spring6.bean.User;
import com.dong.spring6.bean.Vip;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class IOCAnnotationTest {
@Test
public static void main(String[] args) {
ApplicationContext applicationContext=new ClassPathXmlApplicationContext("spring.xml");
User userBean = applicationContext.getBean("user", User.class);
System.out.println(userBean);
Vip vipBean = applicationContext.getBean("vip", Vip.class);
System.out.println(vipBean);
Object orderBean = applicationContext.getBean("order", Order.class);
System.out.println(orderBean);
Student studentBean = applicationContext.getBean("studentBean", Student.class);
System.out.println(studentBean);
}
}
执行结果:
4个对象创建成功!
上述程序的pom文件完整代码:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.dong</groupId>
<artifactId>spring6-008-ioc-annotation</artifactId>
<version>1.0-SNAPSHOT</version>
<!--依赖-->
<dependencies>
<!--spring context依赖-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>6.0.0</version>
</dependency>
<!--junit依赖-->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
</dependencies>
<properties>
<maven.compiler.source>19</maven.compiler.source>
<maven.compiler.target>19</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
</project>
如果是多个包怎么办?有两种解决方案:
- 第一种:在配置文件中指定多个包,用逗号隔开。
- 第二种:指定多个包的共同父包。
1、
<context:component-scan base-package="com.dong.spring6.bean,com.dong.spring6.bean2"/>
2、
<context:component-scan base-package="com.dong.spring6"/>
12.4 选择性实例化Bean
假设在某个包下有很多Bean,有的Bean上标注了Component,有的标注了Controller,有的标注了Service,有的标注了Repository,现在由于某种特殊业务的需要,只允许其中所有的Controller参与Bean管理,其他的都不实例化。这应该怎么办呢?
这里为了方便,将这几个类都定义到同一个java源文件中了
package com.dong.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.dong.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>
12.5 负责注入的注解【非常重要!!!!】
@Component @Controller @Service @Repository 这四个注解是用来声明Bean的,声明后这些Bean将被实例化。接下来我们看一下,如何给Bean的属性赋值。给Bean属性赋值需要用到这些注解:
- @Value
- @Autowired
- @Qualifier
- @Resource
12.5.1 @Value
当属性的类型是简单类型时,可以使用@Value注解进行注入。
代码
<?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.dong.spring6.bean3"/>
</beans>
package com.dong.spring6.bean3;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import javax.sql.DataSource;
import java.io.PrintWriter;
import java.sql.Connection;
import java.sql.SQLException;
import java.sql.SQLFeatureNotSupportedException;
import java.util.logging.Logger;
@Component
public class MyDataSource implements DataSource {
@Value(value = "com.mysql.cj.Driver")
private String driver;
@Value("jdbc:mysql://localhost:3306/spring6")
private String url;
@Value("root")
private String username;
@Value("123456")
private String password;
//使用@Value注解注入的话,可以用在属性上,并且可以不提供setter方法
/* public void setDriver(String driver) {
this.driver = driver;
}
public void setUrl(String url) {
this.url = url;
}
public void setUsername(String username) {
this.username = username;
}
public void setPassword(String password) {
this.password = password;
}*/
@Override
public String toString() {
return "MyDataSource{" +
"driver='" + driver + '\'' +
", url='" + url + '\'' +
", username='" + username + '\'' +
", password='" + password + '\'' +
'}';
}
@Override
public Connection getConnection() throws SQLException {
return null;
}
@Override
public Connection getConnection(String username, String password) throws SQLException {
return null;
}
@Override
public PrintWriter getLogWriter() throws SQLException {
return null;
}
@Override
public void setLogWriter(PrintWriter out) throws SQLException {
}
@Override
public void setLoginTimeout(int seconds) throws SQLException {
}
@Override
public int getLoginTimeout() throws SQLException {
return 0;
}
@Override
public Logger getParentLogger() throws SQLFeatureNotSupportedException {
return null;
}
@Override
public <T> T unwrap(Class<T> iface) throws SQLException {
return null;
}
@Override
public boolean isWrapperFor(Class<?> iface) throws SQLException {
return false;
}
}
package com.dong.spring6.test;
import com.dong.spring6.bean.Order;
import com.dong.spring6.bean.Student;
import com.dong.spring6.bean.User;
import com.dong.spring6.bean.Vip;
import com.dong.spring6.bean3.MyDataSource;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class IOCAnnotationTest {
@Test
public void testDIByAnnotation(){
ApplicationContext applicationContext=new ClassPathXmlApplicationContext("spring-di-annotation.xml");
MyDataSource myDataSource = applicationContext.getBean("myDataSource", MyDataSource.class);
/*
MyDataSource{driver='com.mysql.cj.Driver', url='jdbc:mysql://localhost:3306/spring6', username='root', password='123456'}
*/
System.out.println(myDataSource);
}
}
运行结果:
通过测试得知:@Value注解可以出现在属性上、setter方法上、以及构造方法的形参上。可见Spring给我们提供了多样化的注入。太灵活了。
12.5.2 @Autowired与@Qualifier
@Autowired注解可以用来注入非简单类型。被翻译为:自动连线的,或者自动装配。
单独使用@Autowired注解,默认根据类型装配。【默认是byType】
看一下它的源码:
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注解:
package com.dong.spring6.dao;
public interface UserDao {
void insert();
}
package com.dong.spring6.dao;
import org.springframework.stereotype.Repository;
@Repository //纳入bean管理
public class UserDaoForMySQL implements UserDao{
@Override
public void insert() {
System.out.println("正在向mysql数据库插入User数据");
}
}
package com.dong.spring6.service;
import com.dong.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.dong.spring6.service;
import com.dong.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();
}
}
执行结果:
我们再来看看能不能出现在构造方法上:
package com.dong.spring6.service;
import com.dong.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();
}
}
执行结果:
再来看看,这个注解能不能只标注在构造方法的形参上:
package com.dong.spring6.service;
import com.dong.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.dong.spring6.service;
import com.dong.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肯定是不能省略的。
package com.dong.spring6.service;
import com.dong.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接口还有另外一个实现类,会出现问题吗?
错误信息中说:不能装配,UserDao这个Bean的数量大于1.
怎么解决这个问题呢?当然要byName,根据名称进行装配了。
@Autowired注解和@Qualifier注解联合起来才可以根据名称进行装配,在@Qualifier注解中指定Bean名称。
package com.dong.spring6.dao;
import org.springframework.stereotype.Repository;
@Repository // 这里没有给bean起名,默认名字是:userDaoForOracle
public class UserDaoForOracle implements UserDao{
@Override
public void insert() {
System.out.println("正在向Oracle数据库插入User数据");
}
}
package com.dong.spring6.service;
import com.dong.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();
}
}
执行结果:
总结:
- @Autowired注解可以出现在:属性上、构造方法上、构造方法的参数上、setter方法上。
- 当带参数的构造方法只有一个,@Autowired注解可以省略。
- @Autowired注解默认根据类型注入。如果要根据名称注入的话,需要配合@Qualifier注解一起使用。
12.5.3 @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.dong.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.dong.spring6.service;
import com.dong.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.dong.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.dong.spring6.service;
import com.dong.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.dong.spring6.service;
import com.dong.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.dong.spring6.service;
import com.dong.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.dong.spring6.service;
import com.dong.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只能有一个。
12.6 全注解式开发
所谓的全注解开发就是不再使用spring配置文件了。写一个配置类来代替配置文件。
配置类代替spring配置文件
package com.dong.spring6.config;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.ComponentScans;
import org.springframework.context.annotation.Configuration;
@Configuration
@ComponentScan({"com.dong.spring6.dao", "com.dong.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();
}