如何假装成一个服务端开发(三)

目录

如何假装成一个服务端开发(一)java

如何假装成一个服务端开发(二)android

如何假装成一个服务端开发(三) spring

IoC(控制反转) 以及 DI(依赖注入)

    首先,咱们抛开Spring 或者 服务端开发,来聊聊这两个概念。app

    IoC和面向对象设计同样,从本质上来讲是一种概念。而这种概念的主要目的就在于对象之间的解耦。在原来的逻辑里面,假如咱们有一个User类,User类中须要使用UserInfo类,而后咱们还有一个Client类。框架

    如今我须要在Client中使用User,那么我首先须要在Client类中new 一个User类, 而后再new 一个UserInfo类,最后吧UserInfo,注入到User类中。这个时候,咱们发现Client类耦合了User类,和UserInfo类。ide

    因此聪明的咱们决定下降耦合,既然UserInfo是在User类内部的,那么咱们能够在User类内部本身建立UserInfo。这样的话,Client类就和UserInfo解耦了。不过引入的问题就是User类又和UserInfo类耦合了。函数

    因此怎么办,要不咱们建立一个中央平台吧,咱们在中央平台中建立User,在中央平台中建立UserInfo。当Client须要User的时候,直接去中央平台获取这个User便可。当User须要UserInfo时,也直接去中央平台获取这个UserInfo。这样的话,那么Client,User,UserInfo相互之间就那么耦合了。工具

    这种思想就是所谓的IOC,而这个中央平台就是所谓的容器,这种技术就是所谓的DI。学习

    这实现的过程当中,原来,咱们须要在Client中主动去建立对象,这是一个主动的过程,而使用中央平台以后,中央平台负责了对象的建立(固然高级的中央平台还会帮你管理这些对象的生命周期)。Client中若是须要使用一个对象,直接告诉中央平台,中央平台吐给你结果,这过程当中咱们没有主动去建立。ui

    ok,以上是网上的权威说法,可是……这TM也算反转?总以为多少有些牵强啊喂。

    再举个简单的例子,咱们是如何找女友的?常见的状况是,咱们处处去看哪里有长得漂亮身材又好的mm,而后打听她们的兴趣爱好、qq号、电话号、ip号、iq号………,想办法认识她们,投其所好送,这个过程是复杂深奥的,咱们必须本身设计和面对每一个环节。传统的程序开发也是如此,在一个对象中,若是要使用另外的对象,就必须获得它(本身new一个,或者从JNDI中查询一个),使用完以后还要将对象销毁(好比Connection等),对象始终会和其余的接口或类藕合起来。

  那么IoC是如何作的呢?有点像经过婚介找女友,在我和女友之间引入了一个第三者:婚姻介绍所。婚介管理了不少男男女女的资料,我能够向婚介提出一个列表,告诉它我想找个什么样的女友,好比长得像李嘉欣,身材像林熙雷,唱歌像周杰伦,速度像卡洛斯,技术像齐达内之类的,而后婚介就会按照咱们的要求,提供一个mm,咱们只须要去和她谈恋爱、结婚就好了。简单明了,若是婚介给咱们的人选不符合要求,咱们就会抛出异常。整个过程再也不由我本身控制,而是有婚介这样一个相似容器的机构来控制。

Spring中的IoC

    在Spring中,IoC的思想也体如今对象建立和管理的流程上。全部的类都会在spring容器中登记,告诉spring你是个什么东西,你须要什么东西,而后spring会在系统运行到适当的时候,把你要的东西主动给你,同时也把你交给其余须要你的东西。全部的类的建立、销毁都由 spring来控制,也就是说控制对象生存周期的再也不是引用它的对象,而是spring。

    在 Spring 中把每个须要管理的对象称为 Spring Bean(简称 Bean),而 Spring 管理这些 Bean 的容器,被咱们称为 Spring IoC 容器(或者简称 IoC 容器)。IoC 容器须要具有两个基本的功能:

  • 经过描述管理 Bean,包括发布和获取 Bean;
  • 经过描述完成 Bean 之间的依赖关系。
     

IoC容器

    在Spring中,全部的IoC容器都会实现BeanFactory这个接口。

public interface BeanFactory {
    String FACTORY_BEAN_PREFIX = "&";
    //getBean 表示从容器获取对象的方法,
    // 能够经过对象的名字获取,也能够经过对象的类型获取。
    Object getBean(String name) throws BeansException;

    <T> T getBean(String name, Class<T> var2) throws BeansException;

    Object getBean(String name, Object... var2) throws BeansException;

    <T> T getBean(Class<T> name) throws BeansException;

    <T> T getBean(Class<T> name, Object... var2) throws BeansException;
    //
    <T> ObjectProvider<T> getBeanProvider(Class<T> var1);

    <T> ObjectProvider<T> getBeanProvider(ResolvableType var1);
    //检测是否包含bean
    boolean containsBean(String name);
    //检测类是不是单例 默认状况下,全部的类型都是单例
    boolean isSingleton(String name) throws NoSuchBeanDefinitionException;
    //返回true时,每次getBean都会建立一个新的对象。
    boolean isPrototype(String name) throws NoSuchBeanDefinitionException;
    //检测bean是否匹配某个类型
    boolean isTypeMatch(String name, ResolvableType var2) throws NoSuchBeanDefinitionException;

    boolean isTypeMatch(String name, Class<?> var2) throws NoSuchBeanDefinitionException;
    
    //获取一个bean的类型
    @Nullable
    Class<?> getType(String name) throws NoSuchBeanDefinitionException;
    //获取一个bean的别名
    String[] getAliases(String name);
}

    Bean属于最基础的IoC容器,只提供了对于注入对象最基本的操做,Spring在BeanFactory基础上还派生出了更高级的接口ApplicationContext,在实际使用中,大部分的Ioc容器都是ApplicationContext接口的实现类。

    

        在BeanFactory基础上,ApplicationContext接口扩展了消息国际化接口,环境可配置接口,应用事件发布接口和资源模式解析接口。

        在当前的Spring版本中,咱们多会使用注解的方式来装配Spring IoC容器(还有经过xml的方式),因此这里咱们就使用一个基于注解的IoC容器——AnnotationConfigApplicationContext。

最原始的依赖注入

    咱们建立一个model包用于存放各类bean,而后建立一个bean 好比 User

public class User {
    private Long id;
    private String userName;
    private String note;

    /**setter and getter **/
    ....
}

    而后再建立一个另外的包,好比config,而后在config中建立一个AppConfig文件    

import com.guardz.first_spring_boot.model.User;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class AppConfig {
    @Bean(name = "user")
    public User initUser() {
        User user = new User();
        user.setId(1L);
        user.setUserName("user_name_1");
        user.setNote("note_1");
        return user;
    }
}

    关键在于注解,@Configuration表示这是一个Java配置文件,Spring会根据它来初始化IoC容器,用于装配Bean.

    @Bean表示将initUser返回的对象放入IoC容器中进行管理,而这个对象的名字就是name属性定义的名字,若是没有定义name属性,默认就会使用方法名,好比 "initUser"。

    而后,咱们就可使用AnnotationConfigApplicationContext 来建立一个IoC容器。

    咱们修改XXXApplication文件,在main方法中加入以下代码

private static Logger log = Logger.getLogger(FirstSpringBootApplication.class.getName());
    public static void main(String[] args) {
        SpringApplication.run(FirstSpringBootApplication.class, args);

        ApplicationContext applicationContext = new AnnotationConfigApplicationContext(AppConfig.class);
        User user = applicationContext.getBean(User.class);
        log.info("this is yxwang  " + user.getId()+"");
    }

    运行后,咱们会看到打印出了

        INFO 97781 --- [           main] c.g.f.FirstSpringBootApplication         : this is yxwang  1   

    这就表示user对象已经正确装载到了applicationContext这个IoC容器中,而且咱们经过getBean,顺利拿到了这个user对象。

    PS:这里有个疑惑,我在AppConfig中文件中移除了@Configuration的注解,发现也能正确注入,这让我很疑惑@Configuration的做用。不过姑且仍是加上吧,随着深刻学习,我想我会弄清楚的。

进阶

    当咱们拥有很是多的bean以后,每个bean都写入AppConfig中去注入也是一种略显麻烦事情,所以,Spring还提供了一种扫描注入的方式,让IoC容器本身去扫描须要装配的bean。

    首先须要改造一个bean

@Component("user")
public class User {

    @Value("1")
    private Long id;
    @Value("user_name_1")
    private String userName;
    @Value("note_1")
    private String note;
...
}

    添加了@Component注解,这个注解表示,这个类须要被Spring IoC容器扫描装载。

    @Value表示被注入时属性的值。

    而后,咱们须要修改AppConfig的内容

@Configuration
@ComponentScan
public class AppConfig {

}

    删除了initUser的方法,添加了@ComponentScan的注解,这个注解表示当使用AppConfig初始化IoC容器时,须要主动扫描(扫描哪些@Component)。

    运行,可是咱们发现报错了,没法找到User对象了,User没有被装配到IoC容器中。缘由在于@ComponentScan只会扫描当前包和它的子包。咱们的AppConfig和User并不在同一个包级下,因此没法被扫描到。

    一种解决方案是修改包的结构,可是这未免会破坏原有的代码结构。好在@ComponentScan提供了很多参数

public @interface ComponentScan {
    
    //定义扫描的包
	@AliasFor("basePackages")
	String[] value() default {};

    //定义扫描的包 和 value互为别名  @AliasFor 表示和xx是别名
	@AliasFor("value")
	String[] basePackages() default {};

    //主动指出须要扫描的类名	
	Class<?>[] basePackageClasses() default {};

	//Bean name的生成器
	Class<? extends BeanNameGenerator> nameGenerator() default BeanNameGenerator.class;

	//做用于解析器
	Class<? extends ScopeMetadataResolver> scopeResolver() default AnnotationScopeMetadataResolver.class;
    //做用于代理模式
	ScopedProxyMode scopedProxy() default ScopedProxyMode.DEFAULT;
    //资源匹配模式
	String resourcePattern() default ClassPathScanningCandidateComponentProvider.DEFAULT_RESOURCE_PATTERN;

    //是否启用默认过滤器
	boolean useDefaultFilters() default true;
    //过滤器,当知足条件时才会扫描载入
	Filter[] includeFilters() default {};
    //过滤器, 当知足条件时不会扫描载入
	Filter[] excludeFilters() default {};
    //是否延迟初始化 (对象被get时才会初始化bean)
	boolean lazyInit() default false;

    //过滤器
	@Retention(RetentionPolicy.RUNTIME)
	@Target({})
	@interface Filter {
        //过滤器类型,有不少种,默认是根据注解过滤
		FilterType type() default FilterType.ANNOTATION;

        //定义过滤的类
		@AliasFor("classes")
		Class<?>[] value() default {};

	    //定义过滤的类
		@AliasFor("value")
		Class<?>[] classes() default {};

        //匹配方式
		String[] pattern() default {};

	}

}

    比较经常使用的就是制定包名和过滤器。

    如今,咱们能够修改AppConfig,修改成

//三种选一种
@ComponentScan("com.guardz.first_spring_boot.model")
//@ComponentScan(basePackages = {"com.guardz.first_spring_boot.model"})
//@ComponentScan(basePackageClasses = {User.class})

    假设我如今有一个@Service类,(@Service类内部会带有@Component注解)

@Service
public class XXXService {

    ....
}

    这个时候AppConfig就会自动注入该类(假设该类的路径包含在了AppConfig设置的扫描路径中),这个时候,若是我不想注入该类,就可使用Filter进行排除,好比这样

@ComponentScan(basePackages = "xxxxx", 
    excludeFilters = {@Filter(classes = {XXXService.class})})

    PS: @AliasFor属性的互为别名必须在两个属性上都写上@AliasFor。另外AliasFor还能够用于覆盖指定注解中的值。

    好比

....
public @interface EnableAutoConfiguration {

	.....
	Class<?>[] exclude() default {};
}

...
@EnableAutoConfiguration  
...
public @interface SpringBootApplication {

	/**
	 * Exclude specific auto-configuration classes such that they will never be applied.
	 * @return the classes to exclude
	 */
    //表示重写了EnableAutoConfiguration中的exclude值
	@AliasFor(annotation = EnableAutoConfiguration.class) 
	Class<?>[] exclude() default {};
   .....
}

 

    虽然第二种方式看上去很好用,可是对于三方类库,咱们没办法在对方的类中添加@Component注解,这个时候,仍是会使用@Bean标签。

 注入中的注入

    引用咱们最上面的UserInfo和User类的例子,如今存在两个接口

public interface User{
    public static Logger log = Logger.getLogger(User.class.getName());
    public void setUserInfo(UserInfo userInfo);
    public void printUserInfo();
}

public interface UserInfo {
    public String getUserType();
}

    分别存在两个子类

@Component("clientInfo")
public class ClientInfo implements UserInfo {
    @Override
    public String getUserType() {
        return "client";
    }
}


@Component("gameUser")
public class GameUser implements User {
    private UserInfo userInfo = null;

    @Override
    public void setUserInfo(UserInfo userInfo) {
        log.info("setUserInfo is called");
        this.userInfo = userInfo;
    }

    @Override
    public void printUserInfo() {
        log.info("I am Game User ,type is "+userInfo.getUserType());
    }
}

    这两个类都会被自动注入到IoC容器中,而后我在XXXApplication中使用

ApplicationContext applicationContext = new AnnotationConfigApplicationContext(AppConfig.class);
User user = applicationContext.getBean(GameUser.class);
user.setUserInfo(applicationContext.getBean(ClientInfo.class));
user.printUserInfo();

    这操做并无问题,可是咱们发现咱们仍是参与了user的构造,调用了set方法,设置了一个值。而Spring的IoC容器正是致力于处理此类问题的工具。

    咱们能够修改GameUser类

@Component("gameUser")
public class GameUser implements User {
    @Autowired
    private UserInfo userInfo = null;
    ........
}

    在userInfo关键字上添加了@Autowired关键字,表示让spring 根据类型自动注入对象。

ApplicationContext applicationContext = new AnnotationConfigApplicationContext(AppConfig.class);
User user = applicationContext.getBean(GameUser.class);
user.printUserInfo();

    好了咱们能够直接如上这样获取对象,并调用方法了,UserInfo自动被附上了ClientInfo的值。

    PS: 这里的赋值应该是直接经过反射写入的,由于setUserInfo方法并无被调用。(android中有些依赖注入框架会根据属性寻找相关的set方法,而后调用set方法)

    那么问题来了,若是这是时候咱们新添加了一个类

@Component("adminInfo")
public class AdminInfo implements UserInfo{

    @Override
    public String getUserType() {
        return "admin";
    }
}

    这个时候再去编译就会报错(IDEA都不须要编译,直接下划线告诉你有问题),为何,由于Spring 懵逼了,我有两个UserInfo的子类,你让我注入哪一个?

    因此这个时候咱们能够将UserInfo类型改成ClientInfo,这样就能注入,可是这和设计思想不符啊,怎么就把我类型写死了?因此咱们就须要手动指出咱们须要的对象名字

@Component("gameUser")
public class GameUser implements User {
    @Autowired
    @Qualifier("adminInfo")
    private UserInfo userInfo = null;
    ........
}

    好比这里,咱们经过@Qualifier指定了注入的对象是"adminInfo"(这是AdminInfo在IoC容器中的名字,经过@Component指定了)。

    除此以外还有几个使用方法也常常出现。

    首先对于@Autowired注解,表示必定要找到一个惟一类进行注入,可是咱们能够经过 @Autowired(required = false) 表示要么找到惟一的类,要么找不到为空,不进行注入。

    对于上述出现注入分歧的状况,咱们能够在ClientInfo中添加注解 @Primary 表示优先注入ClientInfo类型,这个时候能够把@Qualifier 注解删掉,Spring会选择ClientInfo进行注入。可是若是多个子类同时拥有@Primary 仍是会出现分歧报错。另外若是同时出现@Primary@Qualifier,那么之后者为准。

    @Autowired能够用在方法上,也可以实现自动调用

@Override
    @Autowired
    @Qualifier("adminInfo")
    public void setUserInfo(UserInfo userInfo) {
        log.info("setUserInfo is called");
        this.userInfo = userInfo;
    }

       最后,不少状况下构造函数式带有参数的,这个时候简单的添加@Component是没法进行注入的,这个时候@Autowired还能够用在参数上,好比这样

public GameUser(@Autowired @Qualifier("adminInfo") UserInfo userInfo){
        this.userInfo = userInfo;
    }
相关文章
相关标签/搜索