spring
文章目录
前言
本章我们介绍如何使用注解代替xml进行开发。
1.注解回顾
1.1原理
注解添加后可以使用反射机制来检查注解是否存在,然后取出其中的属性值,这样一来,我们在框架中就可以使用注解进行快速配置了。
比如:框架在调用某方法时,先检查该方法有没有注解,如果有就取出注解中的属性值作为方法参数,否则就使用默认参数。
详情看java基础。
1.2springIOC注解扫描原理
1.2.1解释
前面我们使用xml配置文件完成spring的bean配置功能,了解到spring从xml文件中读取id 作为bean的名称,读取class 获取类路径来使用反射机制创建bean实例。
如果我们能够注解来提供这两项信息,那么也就说明我们使用注解也能完成bean的实例化,这就是我们使用注解进行开发的原理。
具体实现思路:
我们可以在只知到包路径的情况下,逐一扫描该包下所有的类,来确定哪些类添加了注解,并为这些添加了注解的类进行实例化。
1.2.2案例
我们现在自定义一个注解------Component
提供一些可能需要管理的bean类------Order、User、Vip
提供扫描注解的功能类------ComponentScan
以下是它们的代码:
Component
java
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Component {
String value() default "";
}
注意:
要添加元注解**Retention(RetntionPolicy.RUNTIME)**才可以让该注解被反射机制读取
Order
java
@Component("orderBean")
public class Order {
}
User
java
@Component("userBean")
public class User {
}
Vip
java
public class Vip {
}
ComponentScan
java
public class ComponentScan {
public static void main(String[] args) throws ClassNotFoundException, InstantiationException, IllegalAccessException {
// 定义Map集合存储实例化完成的bean
HashMap beanMap = new HashMap<>();
// 目前只知道一个包的名字,我们需要扫描该包下所有的带注解的类,并为其实例化
String packageName = "org.example.bean";
// 把包名替换为路径名
String replace = packageName.replace('.', '/');
// 获取绝对路径
URL resource = ClassLoader.getSystemClassLoader().getResource(replace);
String path = resource.getPath();
// 使用绝对路径创建File对象
File file = new File(path);
// 遍历bean包下的所有文件
for (File listFile : file.listFiles()) {
// 从文件名中提取出每个类的类名,比如从User.class中提取出User
String s = listFile.getName().split("\\.")[0];
// 拼接包名,得到类路径
String className = packageName + "." + s;
// 使用类路径和反射机制获取Class对象
Class aClass = Class.forName(className);
// 判断当前类是否使用Component注解
if (aClass.isAnnotationPresent(Component.class)) {
// 有Component注解,为其实例化
Component component = aClass.getAnnotation(Component.class);
// 从注解中取出属性,即我们的beanName,或者说id
String id = component.value();
Object o = aClass.newInstance();
// 把Component中的属性看作beanName,作为key传入map,并把创建的实例对象作为value传入
beanMap.put(id, o);
}
}
// 查看map集合中是否有数据
System.out.println(beanMap);
}
}
结果为:
可以看到,添加了我们自定义注解**@Component**的Order类和User类都成功实例化并存储。
2.声明bean的注解
spring提供了专门声明Bean的注解,常见的包括以下四个:
- @Component:常规声明Bean的注解
java
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:声明控制层类为Bean的注解
java
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:声明业务层类为Bean的注解
java
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:声明DAO层为bean的注解
java
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 "";
}
从源码上看,四个注解没什么区别 ,后三个注解是@Component注解的别名注解。但使用这四个注解都可以把当前类标注为bean,设置四个相同功能的注解只是为了从名称上进行区分,增加代码可读性。
补充:@Bean注解,管理三方包对象
上面的四个注解都是添加在类上,然后IOC会创建这些类的对象并管理成bean。
但是有时后我们可能想要把一个三方包中类的对象管理成bean使用,这种情况下我们不可能去三方包中改别人的源码(在他们的类上添加@component等注解)。
这种时候就需要使用@Bean注解,这个注解需要添加在一个有返回值的方法上。
这样一来,spring会执行这个方法,并将该方法返回的对象作为bean存到容器中。
这样一来我们就可以在容器中存储来自三方包中的对象。比如druid中会有一个数据源类DruidDataSource,我们可以用以下代码来存储一个创建好的DruidDataSource对象到容器中:
java
@Bean
public DataSource getDataSource(){
DruidDataSource dataSource = new DruidDataSource();
dataSource.setDriverClassName("com.mysql.cj.jdbc.Driver");
dataSource.setUrl("jdbc:mysql://localhost:3306/spring6");
dataSource.setUsername("root");
dataSource.setPassword("root");
return dataSource;
}
这样一来其他数据库相关框架在连接连接数据库时需要配置数据源,我们就可以使用该bean进行注入使用。
3.spring注解的使用
接下来我们使用上述的四个注解来对bean进行实例化。
3.1加入aop依赖
虽然说的是要导入spring-aop依赖,但我们导入spring-context依赖后就因为依赖继承的原因已经把spring-aop一起导入了,所有这里可以只要导入spring-context即可。
3.2配置文件中添加context命名空间
java
<?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>
3.3配置文件中指定要扫描的包
java
<?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>
3.4在Bean上使用注解
java
package com.powernode.spring6.bean;
import org.springframework.stereotype.Component;
@Component(value = "userBean")
public class User {
}
3.5测试
java
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);
}
}
3.6注意事项
- 如果注解的属性名是value,那么**value=**是可以省略的。
- 如果把value属性彻底去掉,spring会为bean自动取名,并且默认名字的规律是:Bean类名首字母小写即可。如BankDao的bean的名字为:bankDao
如果是多个包怎么办?有两种解决方案:
- 第一种:在配置文件中指定多个包,用逗号隔开。
- 第二种:指定多个包的共同父包。
4.选择性实例化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的无参数构造方法执行");
}
}
我只想实例化包下的Controller。配置文件这样写:
java
<?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进行实例化。
5.负责注入的注解
spring提供了专门的注解完成依赖注入功能:
- @Value:注入简单类型
- @Autowired:根据类型自动完成注入
- @Qualifier:配合Autowired完成根据名称自动装配
- @Resource:和@Autowired一样完成非简单类型装配,区别看下面
5.1@Value
可以注入简单类型,直接传入简单类型的参数值即可,比如我们现在注入一个User类中的属性。
User类
java
package org.example.pojo;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
@Component
public class User {
@Value("张三")
private String name;
@Value("20")
private int age;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public String toString() {
return "User{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
- 需要先将User类管理成bean
- 不要求必须提供属性对应的set方法
- 注解中的参数值是字符串
配置文件
实例化操作、注入操作都使用注解完成,这里的配置文件作用是指定bean扫描路径,让spring能够根据传入的路径正确的扫描到我们的User类,并根据注解对其完成实例化和属性注入操作。
java
<?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="org.example.pojo"/> </beans>
测试类
编写测试类看是否能够成功获取注入完成的user实例
java
import org.example.pojo.User;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class Test {
@org.junit.jupiter.api.Test
public void valueTest(){
ApplicationContext context = new ClassPathXmlApplicationContext("spring-di-annotation.xml");
User user = context.getBean("user", User.class);
System.out.println(user);
}
}
补充:
@Value注解还可以用在方法中,包括构造方法
用在普通方法中
用在构造方法中
5.2@Autowired和@Qualifier
这两个注解用来进行非简单类型属性的注入。
先看注解的源码
java
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存在或者不存在都没关系,存在的话就注入,不存在的话,也不报错。
5.2.1@Autowired按类型注入
单独使用@Autowired注解会按类型进行属性注入。
接下来进行使用,我们还是以UserDao和UserService类为例进行测试,会向UserService类中注入UserDao.
UserDao接口
java
package com.powernode.spring6.dao;
public interface UserDao {
void insert();
}
UserDao实现类
java
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数据");
}
}
需要将其管理为bean并进行实例化,后面作为值注入
UserService
java
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();
}
}
- 不依靠set方法
配置文件
需要配置扫描路径,让spring能够成功扫描到类并
java
<?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>
测试类
java
@Test
public void testAutowired(){
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring-injection.xml");
UserService userService = applicationContext.getBean("userService", UserService.class);
userService.save();
}
补充
- 当有参数的构造方法只有一个并且参数名与属性名对应时,@Autowired注解可以省略,比如以下代码虽然没使用注解,但userDao属性也会自动注入.
java
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();
}
}
5.2.2@Autowired和@Qualifier按名称注入
当我们有多个同类型的bean可以作为注入的参数值时,就无法按类型进行注入了,这个时候需要通过指定bean的名称来进行注入。
@Autowired和@Qualifier注解一起使用可以按照名称进行注入,在@Qualifier注解中指定Bean名称。
比如:
UserDaoForOracle
java
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
java
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();
}
}
5.3@Resource
@Resource注解和@Autowired注解一样用来为非简单属性进行注入,但是两者之间还是有以下差别:
- @Resource是JDK扩展包 中的注解,@Autowired是spring框架中提供的注解
- @Resource注解默认先根据名称进行装配,不传入参数值作为名称时,就先把属性名当作实例名尝试进行装配,如果找不到对应的bean就按类型进行装配。
- @Autowired默认按类型进行装配,想要经过名称装配需要配合@Qualifier
- @Resource可用于属性、setter方法,@Atowrired可用于属性、setter方法、构造方法、构造
5.3.1使用
导入依赖包
要使用@Resource注解需要先导入依赖包,对于spring6版本需要导入以下依赖
<dependency>
<groupId>jakarta.annotation</groupId>
<artifactId>jakarta.annotation-api</artifactId>
<version>2.1.1</version> </dependency>
如果是之前的版本,需要导入其他版本的依赖。
创建bean
这里创建一个dao层对象并管理成bean,并且手动设置bean的名称
java
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数据");
}
}
使用@Resource进行注入
上面设置了bean的名称为"xyz",所以下面要为注解传入参数yz,才可以根据名称成功注入。
java
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();
}
}
如果不传入参数,最终根据类型注入也会成功,但是当有多个同类型的bean对象时还是会失效。
最后还是要使用配置文件指定bean的扫描目录。
6.全注解式开发
前面已经把bean的实例化、依赖注入两个工作交给注解完成,但还是需要在配置文件中指定bean的扫描目录。
如果有注解能够完成这个工作,那么我们就不再需要配置文件了。
spring中提供了两个注解来完成这个工作:
- @Configuration:标识一个类为配置类
- @ComponentScan:配置bean的扫描路径,可以传入字符串数组作为参数。
如:
java
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 {
}
因为已经没有配置文件了,所以我们获取容器的方式也有所改变
java
@Test
public void testNoXml() {
ApplicationContext applicationContext =
new AnnotationConfigApplicationContext(Spring6Configuration.class);
UserService userService = applicationContext.getBean(
"userService",
UserService.class
);
userService.save();
}
总结
本章我们介绍了如何使用注解来进行开发。