1.2 社区开发前置知识:Spring入门

301 阅读9分钟

因为之前系统学习过Spring,所以这里只是做一个简要的了解,Spring详细知识可以看我以前写的Spring专栏。

3. Spring入门

image-20220701152936348.png

spring.io

image-20220701154339003.png

image-20220701171324421.png

Spring 中关于在工厂中注册的四个注解

Spring中的四个关于在工厂中注册的注解

Spring主要有四种注解可以注册bean,每种注解可以任意使用,只是语义上有所差异:

@Component:可以用于注册所有bean

@Repository:主要用于注册dao层(数据库访问)的bean

@Controller:主要用于注册controller层(处理请求)的bean

@Service:主要用于注册service层(业务)的bean

当我们在创建对应类的时候需要加上对应的注解,只有这样,才会在工厂中注册这些类。

关于测试类

# 关于测试类上加的 @RunWith(SpringRunner.class) 注解问题

@RunWith注解作用:
--让测试在Spring容器环境下执行。如测试类中无此注解,将导致service,dao等自动注入失败。
--@RunWith就是一个运行器
--@RunWith(JUnit4.class)就是指用JUnit4来运行
--@RunWith(SpringJUnit4ClassRunner.class),让测试运行于Spring测试环 境,以便在测试开始的时候自动创建Spring的应用上下文
--@RunWith(Suite.class)的话就是一套测试集合
--@RunWith(SpringRunner.class),让测试运行于Spring测试环境,以便在测试开始的时候自动创建Spring的应用上下文

因为SpringRunner.class继承了SpringJUnit4ClassRunner.class且没有进行任何修改
所以@RunWith(SpringRunner.class)基本等同于@RunWith(SpringJUnit4ClassRunner.class)

# 网上问题
问题:
查了好多文章说@RunWith(SpringRunner.class)注解是一个测试启动器,可以加载Springboot测试注解。
本人好奇@RunWith(SpringRunner.class)的作用,于是在IDEA中把这个注解去掉后发现Bean也可以通过@Autowired注解进行注入。于是比较怀疑@RunWith注解的作用

解释:
在正常情况下测试类是需要@RunWith的,作用是告诉java你这个类通过用什么运行环境运行,例如启动和创建spring的应用上下文。否则你需要为此在启动时写一堆的环境配置代码。你在IDEA里去掉@RunWith仍然能跑是因为在IDEA里识别为一个JUNIT的运行环境,相当于就是一个自识别的RUNWITH环境配置。但在其他IDE里并没有。


所以为了让自己的代码不仅可以在 IDEA 中运行,也可以在其他编译软件中运行,还是建议加上
另外,为了可以使用 @RunWith(SpringRunner.class),我们需要引入下面的依赖
<dependency>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter-test</artifactId>
	<scope>test</scope>
</dependency>
<dependency>
	<groupId>junit</groupId>
	<artifactId>junit</artifactId>
	<scope>test</scope>
</dependency>
# 关于测试类上加的@ContextConfiguration(classes = CommunityApplication.class)注解的问题

我们正式运行程序运行的是正式环境中的入口类(配置类),而不是测试环境中的入口类(配置类)执行程序,
我们测试的时候肯定也希望使用正式环境中的配置类.希望和正式环境中的配置类是一样的.所以我们要在测试类上加上
@ContextConfiguration(classes = CommunityApplication.class)注解。

注意:CommunityApplication.class要写成自己项目中正式环境中的入口类的class类型
# 关于测试类实现ApplicationContextAware接口的问题

IOC核心是Spring容器,而容器又是被自动创建的,那我们怎么去得到这个容器呢?其实很简单,哪个类
想得到Spring容器,让它实现


注意:实现ApplicationContextAware接口需要去实现setApplicationContext方法

	需要重写的方法初始模样
	
	@Override
	public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {

	}
	
如果一个类实现了ApplicationContextAware接口的setApplicationContext方法,Spring容器会检测到,
Spring容器在扫描组件的时候会调用setApplicationContext方法把自身(容器)传进来,我们只需要把
这个容器暂存下来,后面就能够使用它了,所以在测试类中加一个成员变量(记录这个容器),然后在
setApplicationContext方法中将容器赋给它,当程序运行的时候容器自动被传进来,然后赋值给
成员变量applicationContext,然后在其他的地方就可以使用这个Spring容器了。

所以测试方法一般结构


@SpringBootTest
@RunWith(SpringRunner.class)
@ContextConfiguration(classes = CommunityApplication.class)
class CommunityApplicationTests implements ApplicationContextAware {
	private ApplicationContext applicationContext;

	@Override
	public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
		this.applicationContext = applicationContext;
	}

	// 测试方法,测试方法的名字可以随便写,但是记得加上@Test注解
	@Test
	void contextLoads() {

	}
}

image-20220701204102661.png

演示使用容器创建Bean、销毁Bean

在测试方法中使用一下Spring容器

接下来写一个测试方法,在这个测试方法里面去使用一下这个Spring容器

用到的类:

image-20220701213710770.png

AlphaDao:

public interface AlphaDao {

    public String select();
}

AlphaDaoHibernateImpl:

@Repository // 访问数据库的话需要加上这个注解,加上这个注解使得这个类在工厂之中进行注册
						// 这个类在工厂中的默认名字是类名首字母小写
@Primary		// 加上这个类是如果还有其它的类与其实现了相同的接口,在工厂中获取接口的class时获取
						// 的是这个实现类(解耦,不用修改逻辑代码),当然在工厂中获取的时候可以直接获取实现类的class
public class AlphaDaoHibernateImpl implements AlphaDao{
    @Override
    public String select() {
        return "Hibernate";
    }
}

AlphaDaoMyBatisImpl:

@Repository		// 加上这个类使得这个类在工厂中进行注册:
public class AlphaDaoMyBatisImpl implements AlphaDao{
    @Override
    public String select() {
        return "MyBatis";
    }
}

测试类CommunityApplicationTests:

@SpringBootTest
@RunWith(SpringRunner.class)
@ContextConfiguration(classes = CommunityApplication.class)
class CommunityApplicationTests implements ApplicationContextAware {
   // 在接口的实现方法中将工厂赋给这个成员变量
   private ApplicationContext applicationContext;

  // 参数是Spring工厂
   @Override
   public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
      this.applicationContext = applicationContext;
   }

   // 测试方法
   @Test
   public void testApplicationContext() {
      System.out.println(applicationContext);
      // 从容器中获取Alpha类(通过类型获取)
      AlphaDao alphaDao = applicationContext.getBean(AlphaDao.class);
      System.out.println(alphaDao.select());  // 这里通过接口获取的是Hibernate

      // 从容器中获取Alpha类(通过名字获取),方法返回类型是Object,所以要强制类型转换
      alphaDao = (AlphaDao) applicationContext.getBean("alphaDaoMyBatisImpl");
      System.out.println(alphaDao.select());  // 这里通过实现类在工厂中的Id获取的是
   }
}

/*
结果:
Hibernate
MyBatis
*/

image-20220701215914671.png

上面演示的是创建Bean,接下来我们来演示一下初始化Bean、销毁Bean方法

AlphaService类:

@Service
public class AlphaService {

    public AlphaService(){
        System.out.println("实例化AlphaService");
    }

    @PostConstruct
    // 让容器管理这个方法,也就是让容器在合适的时候自动的调用这个方法
    // @PostConstruct注解的意思是这个方法会在构造器之后调用,初始化方法一般都是在构造之后调用的
    public void init(){
        System.out.println("初始化AlphaService");
    }

    @PreDestroy
    // 加上这个方法之后会在对象销毁之前调用它可以去释放一些资源
    public void destory(){
        System.out.println("销毁AlphaService");

    }
}

image-20220701230113579.png

测试类CommunityApplicationTests:

@SpringBootTest
@RunWith(SpringRunner.class)
@ContextConfiguration(classes = CommunityApplication.class)
class CommunityApplicationTests implements ApplicationContextAware {
   private ApplicationContext applicationContext;

   @Override
   public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
      this.applicationContext = applicationContext;
   }

   // 测试方法
   @Test
   public void testBeanManagement(){
      AlphaService alphaService = applicationContext.getBean(AlphaService.class);
      System.out.println(alphaService);
   }
}

程序启动过程输出的内容

image-20220701225954258.png

image-20220701230624945.png

Spring容器管理对象默认是单例,如果想要修改为多例,可以加上相应注解修改

被Spring容器管理的类默认都是单例的,如果想要多例的话,在类上加上注解@Scope("prototype")

image-20220701230342233.png

image-20220701230454341.png

在工厂中注册第三方的类

上面一些情况都是我们在工厂中注册自己写的类的例子,但加入我们想要在工厂中注册第三方的类(jar包里的)该怎么做呢?

我们需要自己去写一个配置类,在配置类当中通过@Bean注解进行声明来解决这个问题

假如说我们现在要在工厂中注册第三方的类SimpleDateFormat ,我们来演示一下

// 配置类
@Configuration   // 这个注解表明这个类是一个注解类,而不是普通的类
public class AlphaConfig {

    @Bean          // 这个Bean的名字就是方法的名字
    public SimpleDateFormat simpleDateFormat(){
        return  new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");  //返回一个实例并指定日期格式
    }
}

image-20220701233451954.png

测试类

@SpringBootTest
@RunWith(SpringRunner.class)
@ContextConfiguration(classes = CommunityApplication.class)
class CommunityApplicationTests implements ApplicationContextAware {
   private ApplicationContext applicationContext;

   @Override
   public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
      this.applicationContext = applicationContext;
   }

   // 测试方法
   @Test
   public void testApplicationContext() {
      System.out.println(applicationContext);
      // 从容器中获取Alpha类(通过类型获取)
      AlphaDao alphaDao = applicationContext.getBean(AlphaDao.class);
      System.out.println(alphaDao.select());

      // 通过名字进行获取,方法返回类型是Object,所以要强制类型转换
      alphaDao = (AlphaDao) applicationContext.getBean("alphaDaoMyBatisImpl");
      System.out.println(alphaDao.select());
   }

   @Test
   public void testBeanConfig(){
      SimpleDateFormat simpleDateFormat = applicationContext.getBean(SimpleDateFormat.class);
      System.out.println(simpleDateFormat.format(new Date()));
   }

}

image-20220701233614722.png

从上面我们可以看到,这种方法其实是有些笨拙的,因为我们要写配置类,配置类中要写方法,方法之上又要加注解,很麻烦。

依赖注入

我们说Spring另一个特性是依赖注入,依赖注入我们只需要在成员变量上加上 @Autowired 工厂就会自动将工厂中的类赋值给成员变量

@SpringBootTest
@RunWith(SpringRunner.class)
@ContextConfiguration(classes = CommunityApplication.class)
class CommunityApplicationTests implements ApplicationContextAware {
   private ApplicationContext applicationContext;

   @Override
   public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
      this.applicationContext = applicationContext;
   }

   // 测试方法
   @Test
   public void testApplicationContext() {
      System.out.println(applicationContext);
      // 从容器中获取Alpha类(通过类型获取)
      AlphaDao alphaDao = applicationContext.getBean(AlphaDao.class);
      System.out.println(alphaDao.select());

      // 通过名字进行获取,方法返回类型是Object,所以要强制类型转换
      alphaDao = (AlphaDao) applicationContext.getBean("alphaDaoMyBatisImpl");
      System.out.println(alphaDao.select());
   }

   @Autowired             // 依赖注入
   @Qualifier("alphaDaoMyBatisImpl")  // 指明使用这个接口的哪个实现类(写实现类在工厂中的名字)
   private AlphaDao alphaDao;

   @Autowired
   private AlphaService alphaService;

   @Autowired
   private SimpleDateFormat simpleDateFormat;
   @Test
   public void testDI(){
      System.out.println(alphaDao.select());
      System.out.println(alphaService);
      System.out.println(simpleDateFormat.format(new Date()));
   }
}

image-20220701234031463.png

使用这种依赖注入的方式就不需要写上面那种配置类了

关于 dao 层、service层、controller层之间的关系解释

最后补充一点关于 dao 层、service层、controller层之间的关系,并演示一下

controller来处理浏览器的请求,它在处理浏览器的过程中会调用业务组件(service)去处理当前业务,业务组件会调用dao去访问数据库,总结下来就是 controller 调 service,service 调 dao,它们之间的关系是相互依赖的,它们之间依赖的关系就可以使用依赖注入的方式去实现

dao层是用来连接数据库的,service通过使用dao组件并处理业务逻辑,controller是通过使用service来与浏览器进行交互的。

演示一下

image-20220701235128404.png

AlphaDao:

public interface AlphaDao {

    public String select();
}

AlphaDaoHibernateImpl:

@Repository   // 访问数据库的话需要加上这个注解,加上这个注解使得这个类在工厂之中进行注册
@Primary
public class AlphaDaoHibernateImpl implements AlphaDao{
    @Override
    public String select() {
        return "Hibernate";
    }
}

AlphaService:

@Service
public class AlphaService {
    @Autowired
    private AlphaDao alphaDao;
  
    public String find(){
        return alphaDao.select();
    }
}

AlphaController:

@Controller
@RequestMapping("/alpha")
public class AlphaController {

    @Autowired
    private AlphaService alphaService;

    @RequestMapping("/data")
    @ResponseBody									// 将数据转换为json数据
    public String getData(){
        return alphaService.find();
    }
}

测试:

image-20220701235309142.png