重新思考spring之依赖注入

news/2024/5/19 2:26:55 标签: spring, ioc, 反射, DI, 马丁福勒

依赖注入

      • 从一个例子说起
      • 控制反转
      • 依赖注入
          • 构造器注入
          • setter注入
      • 依赖注入的本质

从一个例子说起

我们看看Martin Fowler的一个例子。

我们有一个MoiveListerclass:

class MovieLister...
  public Movie[] moviesDirectedBy(String arg) {
      List allMovies = finder.findAll();
      for (Iterator it = allMovies.iterator(); it.hasNext();) {
          Movie movie = (Movie) it.next();
          if (!movie.getDirector().equals(arg)) it.remove();
      }
      return (Movie[]) allMovies.toArray(new Movie[allMovies.size()]);
  }

它有一个功能,就是给出导演名字,就能够找到该导演所有的影片。

(我给出Movie类的代码:

package com.ocean.spring.pre;

public class Movie {
	private String title;
	private String director;
	private String actor;

	String getTitle() {
		return title;
	}

	void setTitle(String title) {
		this.title = title;
	}

	String getDirector() {
		return director;
	}

	void setDirector(String director) {
		this.director = director;
	}

	String getActor() {
		return actor;
	}

	void setActor(String actor) {
		this.actor = actor;
	}

	@Override
	public String toString() {
		return "Movie{" +
				"title='" + title + '\'' +
				", director='" + director + '\'' +
				", actor='" + actor + '\'' +
				'}';
	}
}

为此,它需要一个finder来告诉它所有的电影。

我们知道,组件与组件之间是通过接口进行交流的,所以我们定义:

public interface MovieFinder {
	List findAll();
}

接口能够定义功能,但是无法具体地工作。

所以我们不得不给出实现类:

private MovieFinder finder;
	public MovieLister() {
		finder = new ColonDelimitedMovieFinder("movie1.txt");
	}
package com.ocean.spring.pre;

import java.io.File;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Scanner;

public class ColonDelimitedMovieFinder implements MovieFinder {
	private String filename;

	public ColonDelimitedMovieFinder(String filename) {
		this.filename = filename;
	}

	@Override
	public List findAll() {
		List movies = new ArrayList();

		File file = new File("src/main/resources/" + filename);
		try {
			Scanner scanner = new Scanner(file);
			Map<String, String> movieData = new HashMap<>();
			while (scanner.hasNext()) {

				String next = scanner.nextLine();

				if (!"".equals(next.trim())) {
					String[] data = next.split(": ");
					if ("title".equals(data[0])) {
						movieData.put("title", data[1]);
					}
					if ("director".equals(data[0])) {
						movieData.put("director", data[1]);
					}
					if ("actor".equals(data[0])) {
						movieData.put("actor", data[1]);
					}
				}
				else {

					Movie movie = new Movie();
					movie.setTitle(movieData.get("title"));
					movie.setDirector(movieData.get("director"));
					movie.setActor(movieData.get("actor"));
					movies.add(movie);
				}
			}

		}
		catch (Exception e) {

		}
		return movies;
	}

}


比如我的movie1.txt是这么写的:

title: dragon
director: ocean
actor: lin

title: peace
director: ocean
actor: peter

title: nobody
director: sting
actor: bob

---------------------

这当然毫无问题。

突然有一天,我朋友要了这份代码的拷贝。如果他的movie格式也是我这样的用冒号分隔开的,一切工作如故。

可是,如果他的电影使用xml存储的呢,是用json存储的呢?

这样我们就要写一个XMLMovieFinder,写个JsonMovieFinder……

根据上图,MovieLister依赖了MovieFinder接口以及该接口的实现类。

如果我们只让MovieLister依赖MovieFinder接口、而非它的实现类行不行呢?

也就是说,在编译期,MovieLister只与MovieFinder合作,因为我根本不知道我的朋友用的是什么文件格式。只有到了运行期,才能动态地知道究竟是哪个具体的实现类在工作。

Martin Fowler将实现类比作一种插件,我们的lister既要不知道它,又要和它合作。

我们该怎么做呢?
(为什么这个问题这么重要?因为项目扩大,成百上千的组件,我们希望用不同的方式部署,因此我们一定要找到使不同组件沟通的办法)


控制反转

学过spring的朋友会说,spring的特点是控制反转。但是Martin Fowler说,说控制反转是特点就好像说车子的特点是有四个轮子一样。

关键是:

what aspect of control are they inverting?

它们反转的是什么层面的控制?

前面的例子中,反转指的是如何找到插件。例子中使用的是直接在构造方法里实例化了。但我们希望插件是一个额外的模块注入到lister中。

因此,我们把这种行为叫做依赖注入

(控制反转太泛泛而谈了,所以Martin Fowler改了名字。)


依赖注入

依赖注入的核心是:我们需要一个分开的组件,这个东西能够在lister中注入字段,而这个字段就是MovieFinder接口的某个实现类。

依赖注入的三种方式:构造器注入、setter注入、接口注入。接口注入会使项目变得庞大,我们只使用前两种。


构造器注入

我们只需使用@Autowired注解即可(spring4.3后可以省略)。

@Autowired
public MovieLister(MovieFinder finder) {
		this.finder = finder;
	}

如此便可将MovieFinder的实现类告知给MovieLister

当然我们也要给确定的实现类注入值:


	public ColonDelimitedMovieFinder(@Value("movie1.txt") String filename) {
		this.filename = filename;
	}

为了使得这两个类能够被容器管理,我们需要加上@Component注解。

@Component
public class MovieLister
@Component
public class ColonDelimitedMovieFinder implements MovieFinder

如果MovieFinder有多个实现类,只用将我们需要的那个加上@Component注解即可。

最后,我们要让spring能够扫到这些注解:

@Configuration
@ComponentScan(value = "com.ocean.spring.pre")
public class MovieConfig {

}

测试:

public class MovieTest {
	public static void main(String[] args) {
		AnnotationConfigApplicationContext context
				 = new AnnotationConfigApplicationContext(MovieConfig.class);
		MovieLister movieLister =
				(MovieLister) context.getBean("movieLister");

		Movie[] oceans = movieLister.moviesDirectedBy("ocean");
		Stream.of(oceans).forEach(System.out::println);
	}
}

现在,所有东西都是从AnnotationConfigApplicationContext这么一个东西里拿出来的。


setter注入

为了防止构造器注入带来的干扰,我们将构造器注入代码删掉,不再提供有参构造。

并且使用

@Autowired
void setFinder(MovieFinder finder) {
		this.finder = finder;
	}

MovieLister注入一个finder

这就完成了。


依赖注入的本质

以上的两种注入,更确切的说,应该是基于构造器的注入基于setter的注入

为什么要加基于呢?

我们知道,@Autowired还可以用在字段上来为其注入值,难道这又是一种新的注入方式吗?

我们在谈依赖注入的时候,谈的是如何将A注入B。

要使A注入B,只能是通过B的构造方法将A传入,或者通过setA(A a)的方式。

因此,本质上只有这两种方式。

那么字段是如何注入值的呢?

比如我有一个UserController,它依赖于UserService

public class UserController {

    @Autowired
    private   UserService userService;

    public UserService getUserService() {
        return userService;
    }
}

这里的@Autowired是我们自己写的:

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface Autowired {
}
  @Test
    public void testAutowired(){
        UserController userController = new UserController();
        Class<? extends UserController> aClass = userController.getClass();

        Stream.of(aClass.getDeclaredFields()).forEach(field -> {
            Autowired annotation = field.getAnnotation(Autowired.class);
            if(annotation!=null){
                field.setAccessible(true);
                Class<?> type = field.getType();
                try {
                    //spring中这个object不可能直接new出来
                    Object newInstance = type.getConstructor().newInstance();
                    //setter注入
                    field.set(userController,newInstance);
                } catch (Exception e){
                    e.printStackTrace();
                }
            }
        });

        System.out.println(userController.getUserService());
    }

能够在运行期动态地做某些事情,要么通过反射,要么通过动态代理。

以上是用反射拿到@Autowired注解的字段。

然后拿到字段类型并实例化。

然后用 field.set()UserService的实例注入到UserController中。


http://www.niftyadmin.cn/n/1312188.html

相关文章

如何使用Matlab做数字信号处理的仿真1

例如 第三版数字信号处理P51 -1.14习题时域离散信号的相关性研究x(n)Asin(ωn)u(n),其中ωπ/16,u(n)是白噪声&#xff0c;现要求 ⑴、产生均值为0&#xff0c;功率P0.1的均匀分布白噪声u(n),求u(n)自相关函数ru(m) ⑵、使x(n)的信噪比10dB决定A的数值并画出x(n)的图形及其…

1035(未完成)

归并排序不了解&#xff0c;先做题&#xff0c;后面再看&#xff0c;现在没有解决的就是求归并排序的中间序列的下一个序列 package com.company;import java.util.Arrays; import java.util.Scanner;public class Main {public static void main(String[] args) {Scanner scne…

BeanFactory与ApplicationContext

BeanFactory与ApplicationContext是否懒加载是否自动注册后置处理器既然 BeanFactory是spring的顶级接口&#xff0c;那么我们常用的 ApplicationContext又是什么呢&#xff1f;首先它们都提供了spring容器的api。但前者是简化版&#xff0c;后者有更多的功能。 从Application…

PHP校验日期格式是否合法

在后端开发中&#xff0c;我们常常需要校验前端传入参数的合法性&#xff0c;如果是校验日期参数&#xff0c;我们可以通过下面的方法来校验: /*** 校验日期格式是否合法* param string $date* param array $formats* return bool*/ function isDateValid($date, $formats arr…

英语每日阅读---4、VOA慢速英语(翻译+字幕+讲解):专家:城市发展将加剧住房危机...

英语每日阅读---4、VOA慢速英语(翻译字幕讲解):专家:城市发展将加剧住房危机 一、总结 一句话总结&#xff1a;城市化&#xff08;越来越多的人会住进城市&#xff09;是必然趋势&#xff0c;人口增长也是必然趋势&#xff0c;人口增长必然会加大住房危机&#xff0c;房价肯定还…

FactoryBean的使用案例以及BeanFactory与FactoryBean的区别

BeanFactory与FactoryBeandocumentation例子应用ProxyFactoryBeanFactoryBean与BeanFactory的联系为了进一步深入BeanFactory&#xff0c;我们还要了解另一个和BeanFactory相似的名词FactoryBean。 从名字就可以看出&#xff0c;一个的本质是Factory&#xff0c;另一个的本质是…

springboot自动配置魔法

Springboot Auto ConfigurationdemoSpringBootApplication注解将demo改成自动配置解决bean的冲突使用yml配置属性更多自定义的自动配置demo 我们会用一个小demo来开启springboot自动配置的讲解。 父工程的pom&#xff1a; <dependencyManagement><dependencies>&…

Azure KeyVault设置策略和自动化添加secrets键值对

一. 关于Azure Key Vault Azure 密钥保管库可帮助保护云应用程序和服务使用的加密密钥和机密。 借助 Key Vault&#xff0c;可使用密钥来加密密钥和机密&#xff08;例如身份验证密钥、存储帐户密钥、数据加密密钥、.PFX 文件和密码&#xff09;。密钥保管库简化了密钥管理过程…