Spring IOC和DI注解开发
什么是注解驱动
- 注解概述
注解启动时使用注解的形式替代xml配置,将繁杂的spring配置文件从工程中彻底消除掉,简化书写 - 缺点
- 为了达成注解驱动的目的,可能会将原先很简单的书写,变的更加复杂
- XML中配置第三方开发的资源是很方便的,但使用注解驱动无法在第三方开发的资源中进行编辑,因此会增大开发工作量
比如: 自定义的类, 可以在类上加上注解,通过扫描注解,初始化该类,存到容器中.
比如: 项目中引入第三方的依赖, 要想让第三方的依赖的类, 交给spring容器来管理.需要定义一个方法,返回该对象,在方法上面加上@Bean
注解开发
步骤
- 步骤一: 创建spring的配置类,替代配置文件,启动注解扫描,加载类中配置的注解项
/**
* 创建一个spring的配置类,这个类的作用就是替代spring的配置文件
* @Configuration: 指定该类是 一个配置类
* @ComponentScan("com.service.impl"): 指定扫描的包
*/
@ComponentScan("com.service.impl")
@Configuration
public class SpringConfiguration {
}
相当于下面 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
https://www.springframework.org/schema/context/spring-context.xsd">
<!--
spring容器扫描注解:
通过扫描带注解的类的所在的包, 扫描带注解的类,初始化bean对象,存到容器中
配置扫描包的书写格式:
1. base-package="com.dao,com.service,com.web", 扫描com下的dao,service,web包
2.base-package="com", 扫描com包以及com下面所有子包
-->
<context:component-scan base-package="com.service.impl"></context:component-scan>
</beans>
- 步骤二: 在所在包以及子包下面的类上面, 加上bean注解
/**
* @Component注解作用: 定义在类上,
* 通过spring扫描机制,如果扫描到带@Component注解的类,
* 那么就会初始化bean( DeptServiceImpl),存到容器里面(1.根据名称存,2,根据类型存储)
*/
@Component
public class DeptServiceImpl implements DeptService {
@Override
public void addOne(){
System.out.println("执行部门的添加方法11111");
}
}
- 测试
public class Demo {
@Test
public void testX3(){
//1.创建注解容器,加载配置文件
BeanFactory bf = new AnnotationConfigApplicationContext(SpringConfiguration.class);
//2.根据名称(根据id的属性值)获取bean对象
DeptServiceImpl bean = (DeptServiceImpl) bf.getBean("deptServiceImpl");
bean.addOne();
}
@Test
public void testX2(){
//1.创建注解容器,加载配置文件
BeanFactory bf = new AnnotationConfigApplicationContext(SpringConfiguration.class);
//2.获取bean对象
DeptServiceImpl bean = bf.getBean(DeptServiceImpl.class);
bean.addOne();
}
@Test
public void testX(){
//1.创建容器,加载配置文件
BeanFactory bf = new ClassPathXmlApplicationContext("applicaitonContext.xml");
DeptServiceImpl bean = bf.getBean(DeptServiceImpl.class);
bean.addOne();
}
}
小结
-
在进行包所扫描时,会对配置的包及其子包中所有文件进行扫描
-
扫描过程是以文件夹(包结构其实就是文件夹)递归迭代的形式进行的
-
扫描过程仅读取合法的java文件对带注解的java类进行初始化, 初始化存到容器里面(常见1.根据名称存储 2. 根据class来存储)
-
扫描结束后会将可识别的有效注解转化为spring对应的资源加入IOC容器
注意
-
无论是注解格式还是XML配置格式,最终都是将资源加载到IoC容器中,差别仅仅是数据读取方式不同
-
从加载效率上来说注解优于XML配置文件
模拟Spring容器基于注解开发,初始化bean对象,存到容器中
- 步骤一: 定义注解 @MyComponent, @MyConfiguration
@Target(ElementType.TYPE)//限定注解的作用位置, 在类上上面
@Retention(RetentionPolicy.RUNTIME)//指定注解的保留时长,设置注解一直活到运行时
public @interface MyComponent {
String value() default "";//指定bean的名称
}
@Target(ElementType.TYPE)//限定注解的作用位置, 在类上上面
@Retention(RetentionPolicy.RUNTIME)//指定注解的保留时长,设置注解一直活到运行时
public @interface MyConfiguration {//注解类: 作用配置扫描的包
String value();//配置扫描的包
//String[] value();
//String[] basePackages();
}
- 步骤二: 定义一个测试类, 类上面加上自定义的注解@MyComponent
@MyConfiguration("system.my.bean")
public class MySpringConfig {//定义配置类
}
//使用自定义的注解,通过value属性设置bean的名称
@MyComponent("demoService")
public class DemoService {//定义一个bean类测试
public void testService(){
System.out.println("执行业务层的方法");
}
}
- 步骤三: 自定义注解的容器对象: 1. 引入配置类,读取配置类上面扫描的包 2. 递归调用包文件夹,获取java类
/**
* 在进行包所扫描时,会对配置的包及其子包中所有文件进行扫描
* 扫描过程是以文件夹(包结构其实就是文件夹)递归迭代的形式进行的
* 扫描过程仅读取合法的java文件
* 对带注解的java类进行初始化, 初始化存到容器里面(常见1.根据名称存储 2. 根据class来存储)
* 扫描结束后会将可识别的有效注解转化为spring对应的资源加入IoC容器
*/
public class MyAnnotationContainer {
//0.定义容器
//map的key: bean的名称或者bean的class对象
//map的value: bean的对象
private Map<Object,Object> container = new ConcurrentHashMap<Object,Object>();
//1.引入注解的配置类
private Class configClass;
//2.通过构造方法给注解的配置类赋值
public MyAnnotationContainer(Class configClass){
//3.给配置类赋值
this.configClass = configClass;//MySpringConfig
//4.通过配置类,获取配置注解 MySpringConfig
boolean flag = configClass.isAnnotationPresent(MyConfiguration.class);
if(flag){
//说明MySpringConfig类上面有注解: MyConfiguration
MyConfiguration annotation =(MyConfiguration)configClass.getAnnotation(MyConfiguration.class);
//获取MyConfiguration配置的包
String packageName = annotation.value();//system.my.bean.son
//递归调用包里面的所有的java文件,获取到包下面的所有类,判断类上面是否有 MyComponent
scanAnnotationCreateBeanTOMpa(packageName);
}
}
//定义一个方法: 获取包下面的所有类,判断类上面是否有注解 MyComponent ,如果有初始bean,存到容器中
public void scanAnnotationCreateBeanTOMpa(String packageName){
//1.解析包.//修改包结构变成文件夹system/my/bean/son
String packagePathName= packageName.replace(".","/");
try {
//2.通过类加载器获取包的绝对路径(带盘符的)
Enumeration<URL> resources = MyAnnotationContainer.class.getClassLoader().getResources(packagePathName);
//3.获取包下面的所有java文件: class文件
Set<String> classNames=null;
while (resources.hasMoreElements()){
URL url = resources.nextElement();//拿到枚举类里面保存的文件的绝对路径
String filePath = url.getFile();
classNames = FileUtils.getAllFilesName(new File(filePath));
}
//4.根据class文件,创建bean对象,存到容器中
if(classNames!=null && classNames.size()>0){
//5.判断bean是否带有MyComponent
for (String className : classNames) {
//className: DemoMapper.class,DemoService.clas
// 截取class,获取类名DemoMapper,DemoMapper
String typeName= className.substring(0,className.lastIndexOf("."));
//拼接类的全路径: 包名+类名
String typeAllName = packageName+"."+typeName;
//利用反射: 得到Class
Class clz = Class.forName(typeAllName);
//判断该类上面是否有MyComponent
//DemoMapper, DemoService上面是有注解MyComponent
boolean flag = clz.isAnnotationPresent(MyComponent.class);
if(flag){
//获取类上面的注解对象
MyComponent myComponent =(MyComponent) clz.getAnnotation(MyComponent.class);
//获取注解里面配置的bean的名称
String beanName = myComponent.value();
//利用无参数的构造方法创建bean对象
Object beanObj = clz.newInstance();
//根据名称添加到容器中
container.put(beanName,beanObj);
//根据类型存添加到容器中
container.put(clz,beanObj);
}
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
//3.根据名称从容器中获取bean对象
public Object getBeanForName(String name){
return container.get(name);
}
//4.根据class类型,从容器中获取bean对象
public <T> T getBeanForClass(Class<T> clz){
Object o = container.get(clz);
T bean = (T) o;
return bean;
}
}
引用类型注解
引用类型注解— 自定义类被初始化bean对象的注解
- 自定义bean类,初始化对象的注解
注解名称:@Component
,@Controller
,@Service
,@Repository
- bean的作用域的注解
注解名称 @Scope
- bean的生命周期的注解
注解的名称@PostConstruct @PreDestroy
引用类型注解—加载第三方资源, 一般是第三方框架里面bean类,
注解名称为@Bean
注意: 第三方资源如果被其它地方引用, 需要添加value属性,指定bean的名称
注入
非引用类型注入
注解的名称为@Value
引用类型属性注入
实现方式有两种
- 方式一: @Autowired(可以单独使用)、@Qualifier(不能单独使用,必须配合Autowired使用)
- 方式二: @Resource,是JSR250(JAVAEE)规范中的注解,可以简化书写格式
@Resource相关属性- name:设置注入的bean的id
- type:设置注入的bean的类型,接收的参数为Class类型
- 方式一和方式二的区别
@Resource,默认根据名称从容器中取出bean,如果没有根据类型去取 - 细节
- 其他
常用注解
- 加载外部的properties文件注解
- 扫描注解和替代文件注解
注意: 纯注解开发的话, 使用AnnotationConfigApplicationContext 注解容器对象
- 导入第三方Bean的注解
注意: @Import通常和@Configuration一块使用(加上@Configuration目的是被spring扫描到)
Spring注解总结
- Spring原始注解, 主要是替代bean标签的配置
注解 | 说明 |
---|---|
@Component | 使用在类上用于实例化Bean |
@Controller | 使用在web层类上用于实例化Bean |
@Service | 使用在service层类上用于实例化Bean |
@Repository | 使用在dao层类上用于实例化Bean |
@Autowired | 使用在字段上用于根据类型依赖注入 |
@Qualifier | 结合@Autowired一起使用用于根据名称进行依赖注入 |
@Resource | 相当于@Autowired+@Qualifier,按照名称进行注入 |
@Value | 注入普通属性 |
@Scope | 标注Bean的作用范围 |
@PostConstruct | 使用在方法上标注该方法是Bean的初始化方法 |
@PreDestroy | 使用在方法上标注该方法是Bean的销毁方法 |
注意:
使用注解进行开发时,需要在applicationContext.xml中配置组件扫描,作用是指定哪个包及其子包下的Bean需要进行扫描以便识别使用注解配置的类、字段和方法。
<!--注解的组件扫描-->
<context:component-scan base-package="com"></context:component-scan>
- 使用@Compont或@Repository标识UserDaoImpl需要Spring进行实例化。
//@Component("userDao")
@Repository("userDao")
public class UserDaoImpl implements UserDao {
@Override
public void save() {
System.out.println("save running... ...");
}
}
- 使用@Compont或@Service标识UserServiceImpl需要Spring进行实例化
- 使用@Autowired或者@Autowired+@Qulifier或者@Resource进行userDao的注入
//@Component("userService")
@Service("userService")
public class UserServiceImpl implements UserService {
/*@Autowired
@Qualifier("userDao")*/
@Resource(name="userDao")
private UserDao userDao;
@Override
public void save() {
userDao.save();
}
}
- 使用@Value进行字符串的注入
@Repository("userDao")
public class UserDaoImpl implements UserDao {
@Value("注入普通数据")
private String str;
@Value("${jdbc.driver}")
private String driver;
@Override
public void save() {
System.out.println(str);
System.out.println(driver);
System.out.println("save running... ...");
}
}
- 使用@Scope标注Bean的范围
//@Scope("prototype")
@Scope("singleton")
public class UserDaoImpl implements UserDao {
//此处省略代码
}
- 使用@PostConstruct标注初始化方法,使用@PreDestroy标注销毁方法
@PostConstruct
public void init(){
System.out.println("初始化方法....");
}
@PreDestroy
public void destroy(){
System.out.println("销毁方法.....");
}