因为之前系统学习过Spring,所以这里只是做一个简要的了解,Spring详细知识可以看我以前写的Spring专栏。
3. Spring入门
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() {
}
}
演示使用容器创建Bean、销毁Bean
在测试方法中使用一下Spring容器
接下来写一个测试方法,在这个测试方法里面去使用一下这个Spring容器
用到的类:
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
*/
上面演示的是创建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");
}
}
测试类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);
}
}
程序启动过程输出的内容
Spring容器管理对象默认是单例,如果想要修改为多例,可以加上相应注解修改
被Spring容器管理的类默认都是单例的,如果想要多例的话,在类上加上注解@Scope("prototype")
在工厂中注册第三方的类
上面一些情况都是我们在工厂中注册自己写的类的例子,但加入我们想要在工厂中注册第三方的类(jar包里的)该怎么做呢?
我们需要自己去写一个配置类,在配置类当中通过@Bean注解进行声明来解决这个问题
假如说我们现在要在工厂中注册第三方的类SimpleDateFormat ,我们来演示一下
// 配置类
@Configuration // 这个注解表明这个类是一个注解类,而不是普通的类
public class AlphaConfig {
@Bean // 这个Bean的名字就是方法的名字
public SimpleDateFormat simpleDateFormat(){
return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); //返回一个实例并指定日期格式
}
}
测试类
@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()));
}
}
从上面我们可以看到,这种方法其实是有些笨拙的,因为我们要写配置类,配置类中要写方法,方法之上又要加注解,很麻烦。
依赖注入
我们说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()));
}
}
使用这种依赖注入的方式就不需要写上面那种配置类了
关于 dao 层、service层、controller层之间的关系解释
最后补充一点关于 dao 层、service层、controller层之间的关系,并演示一下
controller来处理浏览器的请求,它在处理浏览器的过程中会调用业务组件(service)去处理当前业务,业务组件会调用dao去访问数据库,总结下来就是 controller 调 service,service 调 dao,它们之间的关系是相互依赖的,它们之间依赖的关系就可以使用依赖注入的方式去实现
dao层是用来连接数据库的,service通过使用dao组件并处理业务逻辑,controller是通过使用service来与浏览器进行交互的。
演示一下
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();
}
}
测试:
- 我正在参与掘金技术社区创作者签约计划招募活动,点击链接报名投稿。