Marco's Java【Spring入门(三) 之 重构javaEE三层结构】

前言

上节我们讲到了Spring的控制反转IoC,那么这一节的内容依然是建立在控制反转LoC的基础上做一些巩固和延伸啦。我们来回顾一下上节的IoC,相信大家应该知道什么是IoC了,它的意思就是控制反转,把对象的创建交给spring进行创建,那么我们经常会发现IoC和另外一个名词DI(Dependency Injection)搭配在一起使用,那么这两者的联系和区别到底在哪里呢?

IoC和DI的关系

那IoC之前我们已经定义过,我们再来看看DI的解释

DI—Dependency
Injection,即“依赖注入”:组件之间依赖关系由容器在运行期决定,形象的说,即由容器动态的将某个依赖关系注入到组件之中。依赖注入的目的并非为软件系统带来更多功能,而是为了提升组件重用的频率,并为系统搭建一个灵活、可扩展的平台。通过依赖注入机制,我们只需要通过简单的配置,而无需任何代码就可指定目标需要的资源,完成自身的业务逻辑,而不需要关心具体的资源来自何处,由谁实现。

看了关于DI的解释,我们发现出现频率最高的词是"依赖",那么依赖的是什么呢?为什么需要依赖?谁依赖于谁?谁注入于谁? 注入什么东西? 为何要注入?
在解答这个问题之前我们来举个小栗子~
本杰明从美国纽约来到武汉工作,小伙子想去租房子,但是人生地不熟的,找了快一个星期,终于找到一个价格适宜的房子,然后拎包入住了。恰巧隔壁的邻居是来自美国迈阿密的杰西卡,两个老美老乡见老乡,两眼泪汪汪啊!然后杰西卡就聊到了她的房子是在中介咨询中介帮忙找的,她就提出了她的要求,必须要在西北湖新华路附近,房间一定要干净整洁,直接拎包入住的那种,一天就搞定啦~

很简单的一个租房子的栗子,那么本杰明租的房子就好比我们普通的方式new出来的对象,杰西卡租到的房子就好比通过我们Spring框架的IoC获取到的对象,之前我们有提到过IoC里面的
ApplicationContext就是一个巨大的容器,在这里,中介就好比那个容器,有着各种各样的资源,客户只要发出请求,告诉他,我需要什么样的房子,那么这个请求,就好比id,通过这个id我们就可以在ApplicationContext中搜索相关的符合条件的"房子"(对象),那么中介通过客户需求找到房子,并按照房东的委托将匹配的房源租给租客,这就是DI依赖注入,客户找房子的角色切换成中介找房子这个角色,被称之为IoC控制反转
这么理解大家应该知道两者的关系和区别了吧~

好,那我们接着来解答上面的问题
依赖的是什么呢? 应用程序依赖于IoC/DI容器,依赖IoC/DI容器为它注入所需要的资源。
为什么需要依赖? 因为角色发生了反转,应用程序依赖的资源都是IoC/DI容器里面。
谁依赖于谁? 应用程序依赖于IoC/DI容器
谁注入于谁? IoC/DI容器注入于应用程序
注入什么东西? 注入应用程序需要的外部资源,比如依赖关系
为何要注入? 因为程序要正常运行需要这些外部资源
所以归根结底,从本质上来说IoC就是DI,只不过看问题的角度从"客户"切换成了"中介"而已。这两者是不可分割的存在,凡是程序里面需要使用外部资源的情况,都可以考虑使用IoC/DI容器,IoC/DI容器正诠释了设计理念之一 高内聚、低耦合


Spring的对象属性注入

上节的我们讲到了Spring创建对象的方式,也了解到了我们对象基本的一些属性注入的方式,比如说通过bean的中property子标签进行属性注入,这种注入方式,默认是调用set方法,还有就是我们的constructor-arg子标签的构造器注入,那么如果我们的属性中包含了诸如数组、List、Map,该如何进行属性注入呢?

那么我们还是通过栗子来看看怎么注入吧?(经常把例子打成栗子的我已经习惯用栗子了…)
这里纯粹为了示范,因此栗子不合常理大家也不要见怪啦~
老规矩,还是先建bean包(在Mybatis中通常叫做domain、Spring中叫pojo,都一样啦)
在这里插入图片描述
在这里插入图片描述
接下来,我们来写配置文件

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xmlns:context="http://www.springframework.org/schema/context"
	xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
		http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.3.xsd">
		<!-- 通过构造器方式给对象user1赋值 -->
		<bean id="user1" class="com.marco.domain.User">
		<constructor-arg name="id" value="1"></constructor-arg>
		<constructor-arg name="name" value="marco"></constructor-arg>
		<constructor-arg name="age" value="18"></constructor-arg>
	</bean>
	<!-- 通过调用set方法给对象user2赋值 -->
	<bean id="user2" class="com.marco.domain.User">
	    <constructor-arg name="id" value="1"></constructor-arg>
		<constructor-arg name="name" value="sunnie"></constructor-arg>
		<constructor-arg name="age" value="18"></constructor-arg>
	</bean>
	<!-- 通过调用set方法给对象person赋值 -->
	<bean id="person" class="com.marco.domain.Person"> 
		<property name="users">
			<array>
				<!-- 直接创建user对象 -->
				<bean class="com.marco.domain.User">
					<property name="id" value="1"></property>
					<property name="name" value="marco"></property>
					<property name="age" value="18"></property>
				</bean>
				<!-- 外部引用user对象 -->
				<ref bean="user2"/>	
			</array>
		</property>
		<property name="userList">
			<list>
				<map value-type="com.marco.domain.User">
					<!-- 外部引用user对象 -->
					<entry key="marco zheng" value-ref="user1"></entry>
					<entry key="sunnie zeng" value-ref="user2"></entry>
				</map>
			</list>	
		</property>
		<property name="properties">
			<props>
				<prop key="UserDao">com.marco.dao.impl.UserDaoimpl</prop>
				<prop key="UserService">com.marco.service.impl.UserServiceimpl</prop>
			</props>
		</property>
	</bean>
</beans>	

其实学习了Mybatis的基本配置,再来看Spring的基本配置,发现其实都差不多,只不过标签换了而已,因此大家也可以总结以下,一般碰到<property>标签就是属性,属性里name就是属性名,value就是属性值,class就是完全限定名,id在Spring的IoC中一般都是对象的名称,至于array、list、map标签,相信我不说,大家应该就懂了吧~
为了方便调用ApplicationContext对象,我这里做了简单的封装

public class ContextGenerator {
	private static ApplicationContext context;

	static {
		context = new ClassPathXmlApplicationContext("applicationContext.xml");
	}

	public static ApplicationContext getContext() {
		return context;
	}
}

接下来我们测试一下,通过上述方式,是否能够实现JavaBean对象的注入

public class Test {
	public static void main(String[] args) {
		ApplicationContext context = ContextGenerator.getContext();
		Person person = (Person) context.getBean("person");
		System.out.println(person);
	}
}

在这里插入图片描述
结果实在是太长啦,截取不了,我代大家看了结果,没有问题,请放心哦~


重构javaEE三层结构

一不小心,又讲了这么多关于IoC的内容,终于到了本的重点啦 【重构javaEE三层结构】
相信大家对MVC模型已经不陌生了吧,毕竟我已经提过挺多次了,你们不嫌烦,我自己都嫌烦啦,哈哈
每次学习了新的东西,反观之前学习的东西,总能发现问题或者说"落后的地方"。如果能发现问题,证明我们都在成长,这是一件好事,那大家可以思考一下,我们之前写的MVC的格局有什么地方可以改进下呢?(这里不谈我代码的逻辑问题哈)。

硬着头皮想,可能不一定想得出来,我就不打哑谜啦。我就把关键的部分给列举出来
在这里插入图片描述
在这里插入图片描述
大家有没有发现,我们之前在一个类中调用另外一个类的对象的方法的时候,都是我们自己直接new对象的,那么有没有办法不用我们自己new对象,或者说是有没有一套统一的方法,让我传什么"口令"就拿到什么对象呢?
结合我们之前学过的反射和IoC思想,我们试着完成一下,这里的dao包和service包我就省略掉了,直接上dao.impl和service.impl的内容,各位应该都能明白啦。

BeanFactory+Properties获取对象

为了简化代码突出重心,我这里就省略JDBC操作,模拟一下获取数据库值的操作了。

public class UserDaoimpl implements UserDao{
	@Override
	public User getUserById(Integer id) {
		switch (id) {
		case 1:
			return new User(1,"marco", 18);
		case 2:
			return new User(2,"jack", 20);
		case 3:
			return new User(3,"rose", 18);
		default:
			return null;
		}
	}
}

这里我先停一下,既然之前的对象都是我们new出来的,那么通过反射和配置文件,是不是能建一个Bean工厂呢?

public class BeanFactory {
	static Properties prop = new Properties();
	
	static {
		try {
			prop.load(BeanFactory.class.getClassLoader().getResourceAsStream("config.properties"));
		} catch (IOException e) {
			e.printStackTrace();
		}
	}	
	
	@SuppressWarnings("unchecked")
	public static <T> T newInstance(String key) {
		String className = prop.getProperty(key);
		Class<?> clz;
		try {
			clz = Class.forName(className);
			return (T) clz.newInstance();
		} catch (Exception e) {
			e.printStackTrace();
		} 
		return null;
	}
}

配置文件、Service和Servlet我们就这么写

UserDao=com.marco.dao.impl.UserDaoimpl
UserService=com.marco.service.impl.UserServiceImpl
public class UserServiceImpl implements UserService{
	@Override
	public User getUserById(Integer id) {
		UserDao userDao = (UserDao)BeanFactory.newInstance("UserDao");
		return userDao.getUserById(id);
	}
}
public class UserServlet {
	
	public User getUserById(Integer id) {
		UserService userService = (UserService)BeanFactory.newInstance("UserService");
		User user = null;
		if(null != id) {
			user = userService.getUserById(id);
		}
		return user;
	}
}
public class Test {
	public static void main(String[] args) {
		UserServlet userServlet = new UserServlet();
		User user = userServlet.getUserById(1);
		System.out.println(user);		
	}
}

测试结果如下,没有问题,通过这种方式,我们发现类与类之间解除了耦合关系,为什么这么说呢?
大家想象,通过之前的方式,如果我们改变了之中一个类的名字,那么类中引用的这个类的名字是不是全得换,现在我们得类还很少体会不到,如果有100个类都有当前类得引用呢?虽然很夸张,但是如果真的这样改起代码可是相当痛苦得一件事情,那通过现在这种方式,我们只用改配置文件中得类得完全限定名,其他得代码部分就根本不用修改了。
在这里插入图片描述

Spring+XML获取对象

方式一:通过beanId获取对象
那么既然通过properties文件结合反射可以获取对象,那么使用Spring+XML获取对象也就不在话下啦

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xmlns:context="http://www.springframework.org/schema/context"
	xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
		http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.3.xsd">
			
	<bean id="userDao" class="com.marco.dao.impl.UserDaoimpl"></bean>
	<bean id="userService" class="com.marco.service.impl.UserServiceImpl"></bean>
</beans>

我们只需要将之前获取UserDao对象和UserService对象得方法改成下面这种形式就可以啦

ApplicationContext context = ContextGenerator.getContext();
UserDao userDao = (UserDao)context.getBean("userDao")
ApplicationContext context = ContextGenerator.getContext();
UserService userService = (UserService)context.getBean("userService");

方式二:property注入
是不是很简单?如果你以为这样就结束就大错特错啦
我们把Service和Servlet修改一下

public class UserServiceImpl implements UserService{
	UserDao userDao;

	public void setUserDao(UserDao userDao) {
		this.userDao = userDao;
	}

	@Override
	public User getUserById(Integer id) {
		return userDao.getUserById(id);
	}
}
public class UserServlet {
	UserService userService;
	
	public void setUserService(UserService userService) {
		this.userService = userService;
	}
	
	public User getUserById(Integer id) {
		User user = null;
		if(null != id) {
			user = userService.getUserById(id);
		}
		return user;
	}
}

写成这样是要闹哪样?不急接着看
在这里插入图片描述

public class Test {
	public static void main(String[] args) {
		ApplicationContext context = ContextGenerator.getContext();
		UserServlet userServlet = context.getBean(UserServlet.class);
		User user = userServlet.getUserById(1);
		System.out.println(user);		
	}
}

在这里插入图片描述
很神奇对不?其实明白原理,发现也就那么回事儿,大家也看到了我用红线标注得地方,它实现得原理实际上就是使用我提到的DI依赖注入,之前我们得写法是将依赖注入关系体现在Java代码里面,而现在这种方式将这种依赖得关系体现在了我们得XML文件中,这样我们修改关系也显得更加灵活多变。

方式三:autowire自动装配注入
还没结束哦,我们接着上面得玩,好戏还在后面!
我们将userService和userServlet的bean标签的属性加上autowire=“byType”,autowire翻译过来就是自动组装,
autowire除了default还有byType和byName两个值,从字面上理解就是根据类型根据名称,那么根据这两个来做什么呢?

<bean id="userDao" class="com.marco.dao.impl.UserDaoimpl" ></bean>
	
<bean id="userService" class="com.marco.service.impl.UserServiceImpl" autowire="byType">
</bean>

<bean id="userServlet" class="com.marco.servlet.UserServlet" autowire="byType">
</bean>

还是使用之前的测试代码不变,我们发现结果和之前的一模一样,其实autowire自动装配的就是我们的set方法,byType的意思就是通过set方法里面的要装配值得类型(比如说在UserServlet中得装备对象类型UserService),byName得意思就是装配值得名称(比如说在UserServlet中得装备对象名称userService),我们来做个测试

<bean id="userDao" class="com.marco.dao.impl.UserDaoimpl" ></bean>
	
<bean id="userService" class="com.marco.service.impl.UserServiceImpl" autowire="byType">
</bean>
<bean id="userService1" class="com.marco.service.impl.UserServiceImpl" autowire="byType">
</bean>

<bean id="userServlet" class="com.marco.servlet.UserServlet" autowire="byType">
</bean>	

我们发现运行是有问题得,因为当userServlet根据Type来找UserServiceImpl的时候发现找到了两个一模一样的双胞胎对象,userServlet就懵了,到底谁是谁呢?这就是所谓的代码中的"真假美猴王"。
当我们把userServlet的autowire改成byName,程序又能正常运转,相信其中的道理大家应该都能明白了吧!

方式四:注解注入
玩累了,来介绍最后一种方法,使用注解注入!使用这种方式真的算的上是释放双手了,我们修改下之前的代码,将之前的set方法全部去掉。

@Component
public class UserDaoimpl implements UserDao{
	
	@Override
	public User getUserById(Integer id) {
		switch (id) {
		case 1:
			return new User(1,"marco", 18);
		case 2:
			return new User(2,"jack", 20);
		case 3:
			return new User(3,"rose", 18);

		default:
			return null;
		}
	}
}
@Component
public class UserServiceImpl implements UserService{
	@Autowired
	UserDao userDao;

	@Override
	public User getUserById(Integer id) {
		return userDao.getUserById(id);
	}
}
@Component
public class UserServlet {
	@Autowired
	UserService userService;

	public User getUserById(Integer id) {
		User user = null;
		if(null != id) {
			user = userService.getUserById(id);
		}
		return user;
	}
}

大家应该发现了,我在所有的类上加上注解@Component,可以把这个注解当作是将这些类放入到Spring的容器中, @Autowired就是自动装配的意思了,然后我们将xml结构分成以下4份
在这里插入图片描述
application-dao,application-service和application-servelt三分天下,将之前的属于自己type的标签归为己有,
三分天下之时,依然还是需要有人一统天下,否则添加大乱,民(程序猿)不聊生!
在applicationContext.xml中只用写上一句
<context:component-scan base-package="com.marco"></context:component-scan>

还是用之前的代码测试
在这里插入图片描述
需要注意,我们使用context:component-scan必须开启context,怎么开启呢?
点击xml文件下方的namespace中有个context就可以了,是不是很贴心!讲的这么详细都被我自己给的感动了…
在这里插入图片描述
讲了这么久还没有说component-scan是什么意思呢。component-scan实质上就是扫描的意思,他里面的属性base-package="com.marco"就表示在哪个范围里扫描,我们之前不是给每一个关联的类加上了@Component标签么?说白了只有加上这个标签,我们才能扫描的到啦~

注解附录:
@Component 代表一个spring的组件
@Controller 被Component 注解了。主要作用于接收用户请求的类
@Service 被Component 注解了 主要作用于服务层
@Repository 被Component 注解了 主要作用于数据访问层
@Autowired 作用于类里面的属性或方法 ,用于自动从IOC容器里面根据类型去装配


接下来给大家补充一个小知识点啦,非常简单

关于外部配置引入的问题

上面我们接触了<context:component-scan></context:component-scan>这个标签,不难知道我们xml文件是可以引入外部资源的,context这个属性正是为外部资源导入赋能。
之前我们不是使用properties来导入JDBC的配置信息么,那么我们尝试再结合XML来完成一下这个操作

<context:property-placeholder location="classpath:jdbc.properties" system-properties-mode="FALLBACK" />
<bean id="jdbc" class="com.marco.domain.JDBCConifg">
	<property name="driverClass" value="${driverClass}"></property>
	<property name="url" value="${url}"></property>
	<property name="username" value="${username}"></property>
	<property name="password" value="${password}"></property>
</bean>

context:property-placeholder中的location就是我们properties文件的名称,注意了properties要在src根目录下,那么为什么要配置system-properties-mode="FALLBACK"呢?
因为当我们使用${xxx}查找属性value的时候,会从系统路径开始扫描,而系统路径中可能也会有叫username的,比如说你的笔记本的用户名,那么为了避免这种事情发生,我们会把username改成别的名字比如说userName,或者加上这句话就可以啦,测试结果如下
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值