目录
十二、Spring IoC注解式开发
12.1回顾注解
12.1.1自定义注解
12.1.2使用注解
12.1.3通过反射机制读取注解
12.2声明Bean的注解
12.3Spring注解的使用
12.4选择性实例化Bean
12.5负责注入的注解
12.5.1@Value
12.5.2@Autowired与@Qualifier
12.5.3@Resource
12.6全注解式开发
注解的存在主要是为了简化XML的配置,Spring6倡导全注解开发。
package com.hhb.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* 自定义注解
*/
//标注注解的注解叫做元注解。@Target注解用来修饰@Component可以出现的位置
//以下表示@Component注解可以出现在类上、属性上
//@Target(value = {ElementType.TYPE, ElementType.FIELD})
//以下表示@Component注解可以出现在类上
//@Target(value = {ElementType.TYPE})
//使用注解的时候,如果注解的属性是value的话,value可以省略
//@Target({ElementType.TYPE})
//使用某个注解的时候,如果注解的属性值是数组,并且数组中只有一个元素,大括号可以省略
@Target(ElementType.TYPE)
//@Retention也是一个元注解,用来标注@Component注解最终保留在class文件当中,并且可以被反射机制读取。
@Retention(RetentionPolicy.RUNTIME)
public @interface Component {
//定义注解的属性,String是属性类型,value是属性名
String value();
//数组属性,String[]是属性类型,names是属性名
//String[] names();
}
以上是自定义的一个注解:Component
该注解上面修饰的注解包括:Target注解和Retention注解,这两个注解被称为元注解。
Target注解用来设置Component注解可以出现的位置,以上代表表示Component注解只能在类和接口上。
Retention注解用来设置Component注解的保持性策略,以上代表Component注解可以被反射机制读取。
package com.hhb.bean;
import com.hhb.annotation.Component;
//@Component(属性名=属性值,属性名=属性值,...)
//@Component(value = "userBean")
//如果属性名是value,value可以省略
@Component("userBean")
public class User {
//编译器报错,不能出现在这里
//@Component(value = "test")
//private String name;
}
测试
package com.hhb.client;
import com.hhb.annotation.Component;
public class ReflectAnnotation {
public static void main(String[] args) throws Exception{
//通过反射机制怎么获取注解
//获取类
Class> aClass = Class.forName("com.hhb.bean.User");
//判断类上面有没有这个注解
if (aClass.isAnnotationPresent(Component.class)) {
//获取类上的注解
Component annotation = aClass.getAnnotation(Component.class);
//访问注解属性
System.out.println(annotation.value());
}
}
}
当Bean类上有Component注解时,则实例化Bean对象,如果没有,则不实例化对象。
有注解的Bean
package com.hhb.bean;
import com.hhb.annotation.Component;
@Component("vipBean")
public class Vip {
}
没有注解的Bean
package com.hhb.bean;
public class Order {
}
反射解析注解
package com.hhb.client;
import com.hhb.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 beanMap = new HashMap<>();
//目前只知道一个包的名字,扫描这个包下所有的类,当这个类上有@component注解的时候,实例化该对象,然后放到Map集合中
String packageName = "com.hhb.bean";
//开始写扫描程序
//使用正则表达式,将packageName中的 . 替换成 /
String packagePath = packageName.replaceAll("\\.", "/");
//com是在类的根路径下的一个目录
URL url = ClassLoader.getSystemClassLoader().getResource(packagePath);
String path = url.getPath();
//获取一个绝对路径下的所有文件
File file = new File(path);
File[] files = file.listFiles();
Arrays.stream(files).forEach(f -> {
try {
String className = packageName + "." + f.getName().split("\\.")[0];
//通过反射机制解析注解
Class> aClass = Class.forName(className);
//判断类上是否有这个注解
if (aClass.isAnnotationPresent(Component.class)) {
//获取注解
Component annotation = aClass.getAnnotation(Component.class);
String id = annotation.value();
//创建对象
Object obj = aClass.newInstance();
beanMap.put(id,obj);
}
} catch (Exception e) {
throw new RuntimeException(e);
}
});
System.out.println(beanMap);
}
}
执行结果
负责声明Bean的注解,常见的包括四个:
@Component
@Controller
@Service
@Repository
@Controller、@Service、@Repository这三个注解都是@Component注解的别名,也就是说,这四个注解的功能都一样,用哪个都可以。
为了增强程序的可读性,建议:
控制器类上使用:Controller
service类上使用:Service
dao类上使用:Repository
他们都是只有一个value属性,value属性用来指定bean的id,也就是bean的名字。
第一步:加入aop的依赖
第二步:在配置文件中添加context命名空间
第三步:在配置文件中指定要扫描的包
第四步:在Bean类上使用注解
package com.hhb.bean;
import org.springframework.stereotype.Service;
@Service(value = "orderBean")
public class Order {
}
package com.hhb.bean;
import org.springframework.stereotype.Controller;
@Controller
public class Student {
}
测试
@Test
public void testBeanComponent() {
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.xml");
User userBean = applicationContext.getBean("userBean", User.class);
System.out.println(userBean);
Order orderBean = applicationContext.getBean("orderBean", Order.class);
System.out.println(orderBean);
Student student = applicationContext.getBean("student", Student.class);
System.out.println(student);
Vip vip = applicationContext.getBean("vip", Vip.class);
System.out.println(vip);
}
如果注解的属性名是value,那么value可以省略。
如果将value属性彻底去掉,spring会被Bean自动取名,默认名字为:Bean类名首字母小写即可。
如果是多个包,有两种解决方案
在配置文件中指定多个包,用逗号隔开。
指定多个包的共同父包。(会降低效率)
@Component、@Controller、@Service、@Repository这四个注解是用来声明Bean的,声明后这些Bean将被实例化。
给Bean属性赋值需要使用这些注解:
@Value
@Autowired
@Qualifier
@Resource
当属性的类型是简单类型时,可以使用@Value注解进行注入。
Product
package com.hhb.bean3;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
@Component
public class Product {
@Value("苹果")
private String name;
@Value("5")
private int num;
public void setName(String name) {
this.name = name;
}
public void setNum(int num) {
this.num = num;
}
@Override
public String toString() {
return "Product{" +
"name='" + name + '\'' +
", num=" + num +
'}';
}
}
@Value注解可以直接使用在属性上,也可以使用在setter方法上,也可以使用在构造方法中。
当@Value注解使用在属性上时,可以不用写setter方法。
public Product(@Value("苹果") String name, @Value("5") int num) {
this.name = name;
this.num = num;
}
spring-di-annotation.xml
测试
@Test
public void testDI() {
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring-di-annotation.xml");
Product product = applicationContext.getBean("product", Product.class);
System.out.println(product);
}
@Autowired注解可以用来注入非简单类型,被翻译为:自动连线的或者自动装配的。
单独使用@Autowired注解,默认根据类型装配。(默认是byType)
@Autowired可以标注在哪里?
构造方法上
方法上
形参上
注解上
属性上
OrderDao接口
package org.hhb.dao;
public interface OrderDao {
void insert();
}
OrderDaoForMySQL
package org.hhb.dao.impl;
import org.hhb.dao.OrderDao;
import org.springframework.stereotype.Repository;
@Repository
public class OrderDaoForMySQL implements OrderDao {
@Override
public void insert() {
System.out.println("mysql~~~");
}
}
构造方法和setter方法都没有提供,在属性上使用@Autowired注解,注入成功。
@Autowired注解可以出现在setter方法上。
@Autowired注解可以出现在构造方法上。
@Autowired注解可以出现在构造方法的形参上。
当有参数的构造方法只有一个时,@Autowired注解可以省略。
OrderService
package org.hhb.service.impl;
import org.hhb.dao.OrderDao;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Service;
@Service
public class OrderService {
@Autowired
private OrderDao orderDao;
public void setOrderDao(OrderDao orderDao) {
this.orderDao = orderDao;
}
public void generate() {
orderDao.insert();
}
}
Autowired注解默认是byType进行注入的,也就是说根据类型注入的,如果以上程序中,OrderDao接口还有另外一个实现类,会出现问题。
解决方案:@Autowired注解和@Qualifier注解联合起来才可以根据名称进行装配,在@Qualifier注解中指定Bean名称。
package org.hhb.service.impl;
import org.hhb.dao.OrderDao;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Service;
@Service
public class OrderService {
@Autowired
@Qualifier("orderDaoForOracle")
private OrderDao orderDao;
public void setOrderDao(OrderDao orderDao) {
this.orderDao = orderDao;
}
public void generate() {
orderDao.insert();
}
}
@Resource注解也可以完成非简单类型注入,它和@Autowired注解有什么区别?
@Resource注解是JDK扩展包中的,也就是说属于JDK的一部分。所以该注解是标注注解,更加具有通用性。
@Autowired注解是Spring框架自己的。
@Autowired注解默认根据名称装配byName,未指定name时,使用属性名作为name。通过name找不到的话会自动启动通过类型byType装配。
@Autowired注解默认根据类型装配byType,如果想根据名称装配,需要配合@Qualifier注解一起用。
@Resource注解用在属性上、setter方法上。
@Autowired注解用在属性上、setter方法上、构造方法上、构造方法参数上。
@Resource注解属于JDK扩展包,所以不在JDK当中,需要额外引入以下依赖:如果是JDK8的话不需要额外引入依赖,高于JDK11或低于JDK8需要引入以下依赖。
jakarta.annotation
jakarta.annotation-api
2.1.1
UserDaoForMySQL
package org.hhb.dao.impl;
import org.hhb.dao.OrderDao;
import org.springframework.stereotype.Repository;
@Repository("orderDao")
public class OrderDaoForMySQL implements OrderDao {
@Override
public void insert() {
System.out.println("mysql~~~");
}
}
OrderService
package org.hhb.service.impl;
import jakarta.annotation.Resource;
import org.hhb.dao.OrderDao;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Service;
@Service
public class OrderService {
@Resource(name = "orderDao")
private OrderDao orderDao;
public void setOrderDao(OrderDao orderDao) {
this.orderDao = orderDao;
}
public void generate() {
orderDao.insert();
}
}
所谓的全注解开发就是不再使用spring配置文件了,写一个配置类来代替配置文件。
Spring6Config
package org.hhb;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
//编写一个类,代替Spring框架的配置文件
@Configuration
@ComponentScan({"org.hhb.dao", "org.hhb.service"})
public class Spring6Config {
}
测试
@Test
public void testNoXML(){
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(Spring6Config.class);
OrderService orderService = context.getBean("orderService", OrderService.class);
orderService.generate();
}