说明:
本篇博客介绍了Servlet、业务层(通过创建业务层,是的以前的Controller在调用的时候可以通过Service来间接调用DAO,目的是优化项目中的业务,使代码更简洁),以及IOC(控制反转和依赖注入)的详细介绍,其中3.3实现控制反转是一个重点也是一个难点,需要好好理解。
JavaWeb笔记目录
1. Servlet的初始化设置以及相关配置
1.1 Servlet的初始化设置方式
Servlet生命周期:实例化、初始化、服务、销毁四个阶段
Servlet的两种初始化方式:
①init(config)
其中带参数的方法代码如下:
public void init(ServletConfig config) throws ServletException {
this.config = config ;
init();
}
②init()
public void init() throws ServletException{
}
1.2 Servlet的初始化作用和获取初始化设置的数据
如果我们想要在Servlet初始化时做一些准备工作,那么我们可以重写init方法
①获取config对象:ServletConfig config = getServletConfig();
②获取初始化参数值: config.getInitParameter(key);
实现代码:
@Override
public void init() throws ServletException{
ServletConfig config = getServletConfig();
String configInitParameter = config.getInitParameter("hello");//初始化servlet config在xml文件中
System.out.println("configInitParameter = " + configInitParameter);
}
1.3 Servlet的两种配置方式
①在web.xml文件中配置Servlet
<servlet>
<servlet-name>Demo01Servlet</servlet-name>
<servlet-class>com.lei.servlet.Demo01Servlet</servlet-class>
<init-param>
<param-name>hello</param-name>
<param-value>world</param-value>
</init-param>
</servlet>
<servlet-mapping>
<servlet-name>Demo01Servlet</servlet-name>
<url-pattern>/demo01</url-pattern>
</servlet-mapping>
②通过注解的方式进行配置
@WebServlet(urlPatterns = {"/demo01"} ,
initParams = {
@WebInitParam(name="hello",value="world"),
})
补充:
Servlet中的ServletContext和<context -param>
ServletContext作用:
通过ServletContext获取配置的上下文参数
<context -param>
配置文件:
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath.applicationContext.xml</param-value>
</context-param>
(1)获取ServletContext
①在初始化方法中: ServletContext servletContext = getServletContext();
②在服务方法中也可以通过request对象获取request.getServletContext();
③可以通过session获取:session.getServletContext()
(2)获取初始化值
servletContext.getInitParameter();
实现代码:
@Override
public void init() throws ServletException{
//获取ServletContext
ServletContext servletContext = getServletContext();
String contextConfigLocation = servletContext.getInitParameter("contextConfigLocation");
System.out.println("contextConfigLocation = " + contextConfigLocation);
}
2. 业务层介绍
业务层包含了系统所需要的所有功能上的算法和计算过程,并与数据访问层和控制层交互。(服务端返回 数据的处理以及相应页面的变化)
2.1 MVC详细说明
MVC : Model(模型)、View(视图)、Controller(控制器)
视图层:用于做数据展示以及和用户交互的的一个界面
控制层:能够接受客户端的请求,具体的业务功能还是需要借助于模型组件来完成
模型层:模型分为很多种:有比较简单的pojo/vo(value object),有业务模型组件,有数据访问层组件
①pojo/vo : 值对象
②DAO : 数据访问对象
③ BO : 业务对象
2.2 区分业务对象和数据访问对象
①DAO中的方法都是单精度方法或者称之为细粒度方法。什么叫单精度?一个方法只考虑一个操作,比如添加,那就是insert操作、查询那就是select操作…
②BO中的方法属于业务方法,也实际的业务是比较复杂,因此业务方法的粒度是比较粗的
注册这个功能属于业务功能,也就是说注册这个方法属于业务方法。
那么这个业务方法中包含了多个DAO方法。也就是说注册这个业务功能需要通过多个DAO方法的组合调用,从而完成注册功能的开发。
注册
①检查用户名是否已经被注册 - DAO中的select操作
②向用户表新增一条新用户记录 - DAO中的insert操作
③向用户积分表新增一条记录(新用户默认初始化积分100分) - DAO中的insert操作
④向系统消息表新增一条记录(某某某新用户注册了,需要根据通讯录信息向他的联系人推送消息) - DAO中的insert操作
⑤向系统日志表新增一条记录(某用户在某IP在某年某月某日某时某分某秒某毫秒注册) - DAO中的insert操作…
2.3 业务层实现实例
这里仍然使用的是水果库存管理系统
Service层
FruitServive接口:
public interface FruitService {
//获取指定页面的库存列表信息
List<Fruit> getFruitList(String keyword,Integer pageNo);
//添加库存列表信息
void addFruit(Fruit fruit);
//根据id查看指定库存记录
Fruit getFruitByFid(Integer fid);
//获取总页数
Integer getPageCount(String keyword);
//修改特定的库存记录
void updateFruit(Fruit fruit);
//根据id删除指定的库存记录
void deleteFruit(Integer fid);
}
FruitServive实现类:
FruitServiveImpl:
public class FruitServiceImpl implements FruitService {
private FruitDAO fruitDAO = null;
@Override
public List<Fruit> getFruitList(String keyword, Integer pageNo) {
return fruitDAO.getFruitList(keyword,pageNo);
}
@Override
public void addFruit(Fruit fruit) {
fruitDAO.addFruit(fruit);
}
@Override
public Fruit getFruitByFid(Integer fid) {
return fruitDAO.getFruitByFid(fid);
}
@Override
public Integer getPageCount(String keyword) {
int count = fruitDAO.getFruitCount(keyword);
int pageCount = (count+5-1)/5;
return pageCount;
}
@Override
public void updateFruit(Fruit fruit) {
fruitDAO.updateFruit(fruit);
}
@Override
public void deleteFruit(Integer fid) {
fruitDAO.deleteFruit(fid);
}
}
在以前的实现过程中FruitController直接调用FruitDAO实现类的方法,通过业务层的设计转换为:
Controller调用Service--------->Service调用FruitDAO实现类
3. IOC介绍
IOC全称Inversion of Control,翻译为“控制反转”,实现的是降低了代码的耦合度,所以在这里首先介绍耦合:
3.1 耦合/依赖
依赖指的是某某某离不开某某某,在软件系统中,层与层之间是存在依赖的。我们也称之为耦合。
在我们系统架构或者是设计的一个原则是: 高内聚低耦合。
层内部的组成应该是高度聚合的,而层与层之间的关系应该是低耦合的,最理想的情况0耦合(就是没有耦合),也就是说在一个类的内部不牵扯其他的类,在项目初始化的时候将它们之间的依赖关系生成(也可理解是删除一个类对另外一个类没有影响)
3.2 IOC - 控制反转和DI - 依赖注入
控制反转
①之前在Servlet中,我们创建service对象 , FruitService fruitService = new FruitServiceImpl(); 这句话如果出现在servlet中的某个方法内部,那么这个fruitService的作用域(生命周期)应该就是这个方法级别; 如果这句话出现在servlet的类中,也就是说fruitService是一个成员变量,那么这个fruitService的作用域(生命周期)应该就是这个servlet实例级别②之后我们在applicationContext.xml中定义了这个fruitService。然后通过解析XML,产生fruitService实例,存放在beanMap中,这个beanMap在一个BeanFactory中
因此,我们转移(改变)了之前的service实例、dao实例等等他们的生命周期。控制权从程序员转移到BeanFactory。这个现象我们称之为控制反转
依赖注入:
①之前我们在控制层出现代码:FruitService fruitService = new FruitServiceImpl();那么,控制层和service层存在耦合。
②之后,我们将代码修改成FruitService fruitService = null ;
然后,在配置文件中配置:
<bean id="fruit" class="FruitController">
<property name="fruitService" ref="fruitService"/>
</bean>
3.3 实现控制反转
实现控制反转的通俗点说明就是:在定义的时候修改为FruitService fruitService = null ,之后在applicationContext.xml文件中加入配置,然后在DispatcherServlet中创建BeanFactory的实例,最终在ClassPathXmlApplicationContext中组装bean之间的依赖关系
实现步骤
①:修改定义的格式(见2.3的两个图)
②:applicationContext.xml文件中加入配置
在bean节点内部加入一个标签property,name表示属性名(对应bean标签里面的class中对应的属性FruitDAO)ref:表示引用其他bean的id值(也是FruitDAO,之后通过)
<beans>
<bean id="fruitDAO" class="com.lei.fruit.dao.impl.FruitDAOImpl"/>
<bean id="fruitService" class="com.lei.fruit.service.impl.FruitServiceImpl">
<!--bean实现-->
<!--property标签用来表示属性:name表示属性名(对应bean标签里面的class中对应的属性)ref:表示引用其他bean的id值-->
<property name = "fruitDAO" ref="fruitDAO"/>
</bean>
<!--bean的作用是:servletpath中设计的名字对应的是fruit,那么就是FruitController来处理-->
<bean id="fruit" class="com.lei.fruit.controllers.FruitController">
<property name="fruitService" ref="fruitService"/>
</bean>
</beans>
③创建BeanFactory以及在Dispatcher(中央处理器)中创建它的实例
public interface BeanFactory {
//根据id获取对应的实现类
Object getBean(String id);
}
④最后一步在ClassPathXmlApplicationContext实现功能
详细说明:
首先获取bean节点,存放到beanMap集合中,这在Java基础篇中央处理器的使用中已经实现,接下来需要做的就是将bean节点内部的property配置进行实现以其中的一个为例。
<bean id="fruit" class="com.lei.fruit.controllers.FruitController">
<property name="fruitService" ref="fruitService"/>
需要实现的是:
(1)通过一个循环获取到当前的节点,使用beanNodeList.item(i),
(2)判断是否是元素节点,是的话就进行强制转化变为Element类型,
(3)通过beanElement.getChildNodes();获得当前节点的所有孩子节点(property即使bean节点的其中一个孩子节点),利用"property".equals(beanChildNode.getNodeName()判断当前的孩子节点是否就是我们要找的
(4)获得ref和当前类【name是通过反射调用当前Class的getDeclaredField(propertyName);来获取到它的属性的】,得到的ref是fruitService
(5)调用beanMap集合得到他的Vale值(也就是实现类)
(6)通过id得到Fruit再利用beanMap的get得到目标类【在这里面完成控制反转】FruitController,
(7)getClass得到Class对象调用getDeclaredField(propertyName);获得相应的Field(属性)
(8)完成设置
【这几个步骤比较关键,具体的代码实现看下面,注释也很清楚足够理解控制反转的完整过程】
public ClassPathXmlApplicationContext(){
try {
InputStream inputStream = getClass().getClassLoader().getResourceAsStream("applicationContext.xml");
//1.创建DocumentBuilderFactory
DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance();
//2.创建DocumentBuilder对象
DocumentBuilder documentBuilder = documentBuilderFactory.newDocumentBuilder();
//3.创建Document对象
Document document = documentBuilder.parse(inputStream);
//4.获取所有的bean节点
NodeList beanNodeList = document.getElementsByTagName("bean");
for (int i = 0; i < beanNodeList.getLength(); i++) {
Node beanNode = beanNodeList.item(i);
if (beanNode.getNodeType() == Node.ELEMENT_NODE) {
Element beanElement = (Element) beanNode;
String beanId = beanElement.getAttribute("id");
String className = beanElement.getAttribute("class");
//有多个bean节点,所以对名称做了改动
Class beanClass = Class.forName(className);
//创建bean实例对象
Object beanObj = beanClass.newInstance();
//将bean实例对象保存到容器中
beanMap.put(beanId, beanObj);
//到目前为止,需要主义的是bean和bean之间的依赖冠希还没有加入
}
}
//5.组装bean之间的依赖关系
for (int i = 0; i < beanNodeList.getLength(); i++) {
//获取到每一个bean节点
Node beanNode = beanNodeList.item(i);
//判断每一个节点是不是元素节点
if (beanNode.getNodeType() == Node.ELEMENT_NODE) {
//进行强转
Element beanElement = (Element) beanNode;
String beanId = beanElement.getAttribute("id");
//获取当前节点的所有孩子节点
NodeList beanChildNodeList = beanElement.getChildNodes();
//进行遍历
for (int j = 0; j < beanChildNodeList.getLength(); j++) {
Node beanChildNode = beanChildNodeList.item(j);
if (beanChildNode.getNodeType()==Node.ELEMENT_NODE && "property".equals(beanChildNode.getNodeName())){
Element propertyElement = (Element) beanChildNode;
//<bean id="fruit" class="com.lei.fruit.controllers.FruitController">
//<property name="fruitService" ref="fruitService"/>
String propertyName = propertyElement.getAttribute("name");//fruitService
String propertyRef = propertyElement.getAttribute("ref");
//找到propertyRef对应的实例
//beanMap存储的是FruitService----FruitServiceImpl
Object refObj = beanMap.get(propertyRef);//加入上面的例子获取到的是FruitServiceImpl
//将refObj设置到当前bean对应的实力的property属性上去
Object beanObj = beanMap.get(beanId);//beanId是Fruit所以beanMap获取的是:beanIbj:FruitController
//找到当前的类(先找到他的类,然后再找对应的属性)
Class<?> beanClazz = beanObj.getClass();
//找到propertyName对应的属性
Field propertyField = beanClazz.getDeclaredField(propertyName);//找到存在FruitController中对应的属性
propertyField.setAccessible(true);
propertyField.set(beanObj,refObj);
}
}
}
}
} catch (ParserConfigurationException e) {
e.printStackTrace();
} catch (SAXException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (NoSuchFieldException e) {
e.printStackTrace();
}
}