spring开发笔记(2)spring原理及入门

spring容器原理
spring家族体系庞大,功能强大,但就其最根本的本质来说,spring就是一个IoC容器,通常也称为spring容器。什么是IoC容器呢?所谓容器,顾名思义,就是可以装很多东西的一个器物。spring容器里装的是什么呢?是一个个对象,也就是说,spring容器是一个可以装很多对象的容器。

在spring以前,我们在开发程序时,所有的对象都是要自己一个个new出来的,或者通过工厂模式使用对象工厂一个个创建出来的。比如我们有这么几个类,一个是UserController,另一个是UserService接口,第三个是实现了UserService接口的类UserServiceImpl,UserController里包含了一个实现了UserService接口的成员对象:

public class UserController {
    private UserService userService;

   public  void setUserService(UserService userService) {
       this.userService = userService;
    }

   public String sayHello(String to) {
        return userService.sayHello(to);
   }
}

public interface UserService {
    String sayHello(String to);
}

public class HelloServiceImpl implements HelloService {
    @Override
    public String sayHello(String to) {
        return "hello " + to;
    }
}
在main函数里,我们要自己new出来UserController对象和UserServieImpl对象,并将UserServieImpl对象设置到UserController对象中,代码如下:
public class Test{
    public static void main(String[] args) {
         UserController  uerController = new UserController();
         UserService userService = new UserServiceImpl();
         userController.setUserService(userService);
        System.out.println(userController.sayHello("tom"));
    }
}
显然,自己将对象一个个new出来,再将相关的对象组装起来(如上例中将userSerice对象设置到userController对象中,称之为注入)是很麻烦的,另外,上例中的UserController的成员userService本来只依赖于UserService接口,但我们却在main函数里直接new了一个实现类UserServiceImpl对象,将来如果我们要更换UserService的实现类,那我们也要改main函数里的代码,这就导致了UserController类和实现类UserServiceImpl耦合在了一起,违背了接口和实现分离的原则。

如果有这么一个框架,在程序启动时就自动将对象创建好,并将创建好的对象放入到一个容器中,并将容器中的对象都组装好,然后我们在用到某个对象时,直接从这个容器中获取,那该有多方便,用伪代码表示如下:
public class Test{
    public static void main(String[] args) {
        Container container = Container.scanAndCreateContainer();         
        UserController  uerController = container.getObject(UserController.class);
        System.out.println(userController.sayHello("tom"));
    }
}
伪代码Container.scanAndCreateContainer()会扫描相关类,创建好这些类的对象并放入容器中,再将对象组装好(比如将一个UserServiceImpl对象注入到UserController对象中),最后返回这个容器,然后我们直接从容器中获取UserController对象。可以看出,这样可以给我们带来几个好处,一是我们不必再关心对象的创建工作,二是解除了对象之间的依赖关系,在这个例子中,UserController对象依赖于UserService接口,容器将实现类UserServiceImpl对象注入到UserController对象中,将来如果我们要更换UserService的实现类,容器就会将更换后的实现类创建对象并注入到UserController对象中,我们main函数里的代码也无需更改,这样就实现了接口和实现的分离。

综上,使用容器可以带来以下好处:
1.不用关心对象的创建工作;
2.解除了对象之间的依赖关系。

显然,容器需要做这么几件事:
1.扫描相关类,为每个类创建对象,并放入容器中;
2.将容器中的对象装配好。

从根本上来说,spring容器也就是做了这么两件事。现在有两个问题,
一是spring容器怎么知道要创建哪些对象?
二是spring容器是怎么创建并装配这些对象的?

要解决这两个问题,得从java的运行机制说起。我们知道,java程序都是在java虚拟机(jvm)中运行的,每个java类都会被编译成一个字节码文件(即.class文件),java虚拟机通过类加载器(ClassLoader)将字节码文件加载到内存中并运行。jvm将字节码文件加载到内存中并解析后,显然就掌握了一个类的所有细节,包括类的名称,类有哪些成员变量和成员方法等等。既然jvm掌握了一个类的所有细节,那么显然,无需你手动new,jvm自己就能创建一个类的对象,并为它设置好各个成员变量的值,这就是java的反射机制。第一个问题, 我们可以通过某种方式(比如配置文件)来告诉spring容器我们希望它为创建哪些类对象,spring容器就会按照配置文件的指示,去扫描并加载相关的类;第二个问题, spring就是通过java的反射机制来创建并装配好相关的对象的。这就是spring的运行原理。我们把存放在spring容器的对象称之为bean(豌豆)。

spring容器的配置方式
有3种方式来配置spring容器:
1.基于xml文件的配置方式;
2.基于注解的配置方式;
3.基于java类的配置方式。
即spring容器可以通过这三种方式来获知需要创建哪些类对象。

基于xml文件的配置方式
通过xml文件来给spring容器提供需要创建对象的类信息。
先创建一个空的maven工程,工程名为spring-demo-xml,空的工程如下:
在pom.xml文件中添加如下依赖:


    4.0.0

    com.mystudy
    spring-demo-xml
    1.0-SNAPSHOT

    
        1.8
    

    
        
            org.springframework
            spring-context
            4.3.10.RELEASE
        
    

    
        
            
                org.apache.maven.plugins
                maven-compiler-plugin
                
                    ${java.version}
                    ${java.version}
                
            
        
    
再编写UserController、UserService和UserServiceImpl三个类(接口),内容与上面一样:
再在resources目录下添加名为spring.xml的配置文件:
在spring.xml中添加内容如下:



    
    
        
            
        
    
spring.xml中先定义了一个名为userService的bean(即对象),class指出了这个类的全限定名。再定义了一个名为userController的bean,它的成员变量userService引用userService这个bean。通过这个配置文件,spring容器会分别创建一个UserServiceImpl对象和UserController对象,并将UserServiceImpl对象注入到UserController对象中。接下来添加main函数:
main函数的代码如下:
package com.mystudy;

import com.mystudy.controller.UserController;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class MyTest {
    public static void main(String[] args) {
        ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("classpath:spring.xml"); // 1

        UserController userController = (UserController) context.getBean("userController"); //2
        System.out.println(userController.sayHello("tom")); //3
    }
}
语句1通过 ClassPathXmlApplicationContext这个类创建了一个spring容器,顾名思义,这个类是通过xml配置文件来配置spring容器的,传入的参数就是配置文件的地址。spring容器会根据配置文件的bean定义,创建好对象并将对象装配好。语句2直接从spring容器中获取想要的对象。语句3直接使用从spring容器中获取到的对象。执行main函数,输出为:
hello tom

基于注解的配置
用xml配置文件的方式,每个要创建的对象我们都要定义一个bean,这样还是比较麻烦的,并且如果要创建的对象比较多的话,这个工作量还是很大的。有没有更简单的方式呢? 最好是能够让spring容器自动去扫描某个包下的类文件,自动去发现哪些类是需要创建对象的,并且能够自动完成对象的装配。幸好,spring提供的基于注解的配置方式实现了这一功能。我们只要将类标记上相关的注解,spring就知道需要为其创建对象,并能自动完成对象的装配工作。下面我们将上面的例子改成基于注解的配置方式。

首先修改UserController类,添加@Controller注解,在成员变量userService上添加@Autowired注解。@Controller注解表示希望spring创建这个类的对象,@Autowired注解表示希望spring将一个实现了UserService接口的对象注入进来:
package com.mystudy.controller;

import com.mystudy.service.UserService;
import org.springframework.stereotype.Controller;

@Controller
public class UserController {
    @Autowired
    private UserService userService;

    public  void setUserService(UserService userService) {
        this.userService = userService;
    }

    public String sayHello(String to) {
        return userService.sayHello(to);
    }
}
接下来修改UserServiceImpl类,添加@Service注解,@Service注解也表示希望spring创建这个类的对象:
package com.mystudy.service.impl;

import com.mystudy.service.UserService;
import org.springframework.stereotype.Service;

@Service
public class UserServiceImpl implements UserService{
    @Override
    public String sayHello(String to) {
        return "hello " + to;
    }
}
除了@Controller、@Service注解外,还有@Bean、@Component等注解,这些注解都表示希望spring创建对象,之所以名称不同,只是为了区分一个工程中不同类型的组件。

接下来修改spring.xml文件,将文件改成:



    
可以看出,配置文件里只有一句话,那就是component-scan,component-scan告诉spring容器去扫描哪些包。这里我们让spring容器去扫描"com.mystudy"这个包下的所有类,spring会为该包下所有标记了@Controller、@Service、@Component、@Bean等注解的类创建对象,并为所有标注了@Autowired的成员注入合适的对象。

main函数不用修改,执行main函数,输出为:
hello tom

基于java类的配置
使用基于注解的配置加上自动扫描,已经非常方便了。不过spring还提供了基于java类的配置方式,这种方式的优点一是可以用代码控制对象的创建逻辑,二是提供了类型安全。下面我们将上面的例子改成基于Java类的配置方式。

首先将UserController和UserServiceImpl上的所有注解全部删除,然后将spring.xml文件删除。

然后在com.mystudy下创建一个名为configure的包,在该包下创建一个用来配置spring容器的类SpringConfig:
SpringConfig类的内容如下:
package com.mystudy.configure;

import com.mystudy.controller.UserController;
import com.mystudy.service.UserService;
import com.mystudy.service.impl.UserServiceImpl;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class SpringConfig {
    @Bean
    public UserController userController() {
        UserController userController = new UserController();
        userController.setUserService(userService());
        return userController;
    }
    @Bean
    public UserService userService() {
        return new UserServiceImpl();
    }
}
SpringConfig类上添加了@Configuration注解,表示这是一个配置类。这个类里有两个方法,一个是userContoller()方法,返回类型为UserController,该方法被标记了@Bean注解,这告诉Spring,用这个方法来创建UserController类的对象。另一个userSerice方法同理。

接下来修改main函数为:
package com.mystudy;

import com.mystudy.configure.SpringConfig;
import com.mystudy.controller.UserController;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

public class MyTest {
    public static void main(String[] args) {
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(SpringConfig.class); // 1

        UserController userController = (UserController) context.getBean("userController");
        System.out.println(userController.sayHello("tom"));
    }
}
注意语句1,这里和前面不同,前面是用 ClassPathXmlApplicationContext类来创建spring容器的,而这里用的是 AnnotationConfigApplicationContext类来创建spring容器,顾名思义,它是通过Java类来配置spring容器的。语句1传入的参数是SpringConfig.class,表示让spring用SpringConfig这个类来配置spring容器。执行main函数,输出:
hello tom

推荐的配置方式
多种配置方式中,spring官方推荐使用java类的配置方式。在上面基于java类的配置方式中,我们把UserController和UserServiceImpl上的注解全都删除了,并且只有在SpringConfig类中定义了的Bean,spring容器才会去创建对象。基于java类的配置方式,再加上自动扫描自动装配,这就完美了。下面我们将工程改造成基于java类配置+自动扫描自动装配的方式。

首先恢复UserController和UserServiceImpl这两个类上删除的注解(即@Controller, @Autowired和@Serivice,然后将SpringConfig类修改为:
package com.mystudy.configure;

import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;

@Configuration
@ComponentScan(value = {"com.mystudy"})
public class SpringConfig {
}
只在SpringConfig类上添加了一个 @ComponentScan注解,告诉spring容器去扫描"com.mystudy"这个包下的类。
接下来执行main函数,仍然输出:
hello tom











你可能感兴趣的:(spring,java)