`
xlcai
  • 浏览: 19806 次
  • 性别: Icon_minigender_1
  • 来自: 北京
社区版块
存档分类
最新评论

SpringMvc(二)配置SpringMvc

 
阅读更多

最近在学习SpringMvc,自己搭建一次,也算做做笔记了,分享一下,用的Eclipse jee版,Eclipse新建web项目就不说了,在前面有篇文章有介绍,直接说配置Spring吧,肯定要先在web.xml配下拦截器

 

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://java.sun.com/xml/ns/javaee" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd" id="WebApp_ID" version="3.0">
  <display-name>springMVC</display-name>
  <welcome-file-list>
    <welcome-file>index.html</welcome-file>
    <welcome-file>index.htm</welcome-file>
    <welcome-file>index.jsp</welcome-file>
    <welcome-file>default.html</welcome-file>
    <welcome-file>default.htm</welcome-file>
    <welcome-file>default.jsp</welcome-file>
  </welcome-file-list>
<!-- 配置开始 -->
<!-- a1加载Spring容器配置 -->
<listener>
    <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<!-- a2设置Spring容器加载配置文件路径 -->
<context-param>
    <param-name>contextConfigLocation</param-name>
    <param-value>classpath:applicationContext.xml</param-value>
</context-param>

<!-- b1配置Spring核心控制器 DispatcherServlet -->
<servlet>
    <servlet-name>dispatcher</servlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
    <!-- b2设置控制器加载配置文件路径 -->
    <init-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>classpath:servlet-context.xml</param-value>
    </init-param>
    <!-- b3启动顺序,让这个Servlet随Servlet容器一起启动。 -->
    <load-on-startup>1</load-on-startup>
</servlet>
<!-- b3拦截所有.action请求 -->
<servlet-mapping>
    <servlet-name>dispatcher</servlet-name>
    <url-pattern>*.action</url-pattern>
</servlet-mapping>
<!-- c1设定编码格式 -->
<filter>
    <filter-name>characterEncoding</filter-name>
    <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
    <init-param>
        <param-name>encoding</param-name>
        <param-value>UTF-8</param-value>
    </init-param>
</filter>
<!-- c2设定编码格式范围 -->
<filter-mapping>
    <filter-name>characterEncoding</filter-name>
    <url-pattern>/*</url-pattern>
</filter-mapping>
  
</web-app>

这里配了一个servlet一个application容器,配置文件的位置都放在根路径下,我们就可以放在resource下。顾名思义
先看servlet.xml

 

 

<?xml version="1.0" encoding="UTF-8"?>
<beans:beans xmlns="http://www.springframework.org/schema/mvc"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xmlns:mvc="http://www.springframework.org/schema/mvc"
	xmlns:util="http://www.springframework.org/schema/util"
	xmlns:beans="http://www.springframework.org/schema/beans"
	xmlns:context="http://www.springframework.org/schema/context"
	xsi:schemaLocation="
        http://www.springframework.org/schema/mvc 
        http://www.springframework.org/schema/mvc/spring-mvc-3.0.xsd
        http://www.springframework.org/schema/util 
        http://www.springframework.org/schema/util/spring-util-2.0.xsd
        http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
        http://www.springframework.org/schema/context
        http://www.springframework.org/schema/context/spring-context-3.0.xsd">
	<!-- 返回数据解析。prefix:前缀, suffix:后缀 也就是配置views的目录-->
	<beans:bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
		<beans:property name="prefix" value="/WEB-INF/views/" />
		<beans:property name="suffix" value=".jsp" />
	</beans:bean>
	<!-- 相当于注册了DefaultAnnotationHandlerMapping和AnnotationMethodHandlerAdapter两个bean -->
	<annotation-driven />
	<!-- 注解controller所在的包,  扫描路径 -->
	<context:component-scan base-package="com.springMvc.controller"/>
</beans:beans>

 

 

关于HandlerMapping和handlerAdapter这里就直接转下别人博客里的解释了:如下

 

SpringMVC中,满足条件的请求进入到负责请求分发的DispatcherServlet,DispatcherServlet根据请求url到控制器的映射(HandlerMapping中保存),HandlerMapping最终返回HandlerExecutionChain,其中包含了具体的处理对象handler(也即我们编程时写的controller)以及一系列的拦截器interceptors,此时DispatcherServlet会根据返回的HandlerExecutionChain中的handler找到支持这一处理器类型的适配器(handlerAdapter),在处理器适配器中最终会去调用控制器的请求响应方法并返回结果视图(ModelAndView)

然后是application.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-3.0.xsd
		http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd">
	<!-- 注解所在的包,  扫描dao路径 -->
	<context:component-scan base-package="com.springMvc.dao"/>
	<!-- 注解所在的包,  扫描service路径 -->
	<context:component-scan base-package="com.springMvc.service"/>
	<!-- 引入jdbc.properties -->
 	<context:property-placeholder location="classpath:jdbc.properties" ignore-unresolvable="true"/>
	<!-- 配置一个数据源,用下dbcp连接池 -->
	<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
		<!-- 用的mysql数据库,连接到数据库mydb(自己新建) -->
		<property name="driverClassName" value="${driver}" />
		<property name="url" value="${url}" />
		<!-- 用户名密码,根据本地数据库自定义 -->
		<property name="username" value="${username}" />
		<property name="password" value="${password}" />
	</bean>
	
	<!-- 定义jdbc模板 -->
	<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
		<property name="dataSource" ref="dataSource" />
	</bean>
</beans>

 

这里就是配下要扫描注解的路径,然后配个数据库连接,基本上配置文件就写好了,我们可以简单写点小程序了,在建好的web项目里继续划分出dao,service,control(Eclipse搭建web项目那篇文章的例子)

结构很清晰的说。好像忘了还有log4j,先配好吧,方便调试啥的

 

log4j.rootCategory=INFO,A1
log4j.appender.A1=org.apache.log4j.ConsoleAppender
log4j.appender.A1.layout=org.apache.log4j.PatternLayout

同样放在resource下,log4j.properties。简单配置下只输出info到控制台。然后终于可以开始写代码了,写什么好呢,想了想就做个简单的记账系统吧,手机上有很多这种应用,记录每天花的钱,收入的钱,然后有个汇总啥的,功能不多,好实现。看来代码还是不能先写,先搞定数据库,如下设计

 


当然我也没设计过这种软件,只是凭空想象的,简单弄这么两个表,应该一看就明白了,数据库id都设自增长,账目时间,金额,出入方向,账目类型就是可以自定义类型(吃饭,娱乐,工资)然后类型细分的话又做了个类型表,可以用父类ID来规划子父类关系,比如吃饭再分为早饭,午饭,晚饭等,都是扯淡,没啥好说的。至于金额是int,这里数据库按最小单位的分来记录,所以不带小数点的。然后又了数据库就可以对应写两个entity实体类了

 

package com.springMvc.dao.entity;
import java.sql.Date;
public class Account {
	private String id;//数据库ID
	private Date acc_time;//时间
	private Integer acc_jine;//金额
	private boolean acc_shouzhi;//出入
	private Integer acc_type;//分类
	private String acc_beizhu;//备注
	//getters and setters

 

package com.springMvc.dao.entity;

public class Type {
	private Integer type_id;//数据库ID
	private String type_name;//类型描述
	private Integer type_parent;//父类ID getters and setters略

然后是Manager这里先简单写个账目的Manager,写两个

 

 

package com.springMvc.dao.manager;

import org.apache.log4j.Logger;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Repository;

import com.springMvc.dao.entity.Account;
/*通过Spring注解定义一个Dao,这里简单写下略去了Dao直接写在Manager里了*/
@Repository
public class AccountManager {
	/*自动注入jdbcTemplate的bean*/
	@Autowired
	private JdbcTemplate jdbcTemplate;
	//logger
	private final static Logger logger = Logger.getLogger(AccountManager.class);
	
	public int insertAccount(Account ac){
		logger.info("----------------账目插入数据库,dao层--!");
		/*Sql*/
		String sql = "insert into account_info (acc_time,acc_jine,acc_shouzhi,acc_type,acc_beizhu)"
				+ "values(?,?,?,?,?)";
		/*入参*/
		Object[] args = {ac.getAcc_time(),ac.getAcc_jine(),ac.isAcc_shouzhi(),ac.getAcc_type(),ac.getAcc_beizhu()};
		return jdbcTemplate.update(sql,args);//返回成功失败
	}
}
package com.springMvc.dao.manager;

import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;

import org.apache.log4j.Logger;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.RowCallbackHandler;
import org.springframework.stereotype.Repository;

import com.springMvc.model.TypeInfo;

@Repository
public class TypeManager {

	/*自动注入jdbcTemplate的bean*/
	@Autowired
	private JdbcTemplate jdbcTemplate;
	//logger
	private final static Logger logger = Logger.getLogger(TypeManager.class);
	
	/**
	 * 提供一个查询所有最底层类的方法,供给前台插入数据时选择
	 */
	public List<TypeInfo> selectAllTypes(){
		logger.info("----------------查询可用类型,dao层--!");
		/*这里我设想的是id不是别人的父类id肯定就是最子类了,sql不太擅长,有更好的方法欢迎提出*/
		String sql = "select type_id,type_name from account_type where type_id not in"
				+ "(select distinct type_parent from account_type)";
		final List<TypeInfo> resultList = new ArrayList<TypeInfo>();
		
		jdbcTemplate.query(sql, 
				new RowCallbackHandler() {
					@Override
					public void processRow(ResultSet rs) throws SQLException {
						TypeInfo ty = new TypeInfo();
						ty.setType_id(rs.getInt("type_id"));
						ty.setType_name(rs.getString("type_name"));
						resultList.add(ty);
					}
				});
		return resultList;
	}
}


一个account manager一个type manager,分别提供插入方法,和提供插入时选择账目类型列表。基本先够用了吧,这里返回类型列表用到了一个类型的model,如下

 

 

package com.springMvc.model;

public class TypeInfo {
	private Integer type_id;//id
	private String type_name;//描述
	public Integer getType_id() {
		return type_id;
	}
	public void setType_id(Integer type_id) {
		this.type_id = type_id;
	}
	public String getType_name() {
		return type_name;
	}
	public void setType_name(String type_name) {
		this.type_name = type_name;
	}
	
}

 

然后写service吧

 

package com.springMvc.service;

import com.springMvc.dao.entity.Account;

public interface AccountService {
	/**
	 * 插入账目
	 */
	public void insertAccout(Date acc_time,Integer acc_jine,Integer acc_shouzhi,Integer acc_type,Integer acc_beizhu);
}

 

package com.springMvc.service;

import java.util.List;

import com.springMvc.model.TypeInfo;

public interface TypeService {

	/**
	 * 调取可用类型
	 */
	public List<TypeInfo> selectAllTypes();
}
package com.springMvc.service.Impl;

import java.sql.Date;

import org.apache.log4j.Logger;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import com.springMvc.dao.entity.Account;
import com.springMvc.dao.manager.AccountManager;
import com.springMvc.service.AccountService;
@Service  //标注为服务层的一个service
public class AccountServiceImpl implements AccountService{
	
	//logger
	private final static Logger logger = Logger.getLogger(AccountServiceImpl.class);
	@Autowired//自动装配dao bean
	private AccountManager accountManager;
	@Override
	public void insertAccout(Date acc_time,Integer acc_jine,Integer acc_shouzhi,Integer acc_type,Integer acc_beizhu) {
		logger.info("----------------插入账目进入Service层--!");
		Account ac = new Account();
		ac.setAcc_time(acc_time);
		ac.setAcc_jine(acc_jine);
		ac.setAcc_shouzhi(acc_shouzhi==1?true:false);
		ac.setAcc_type(acc_type);
		accountManager.insertAccount(ac);
	}
}



package com.springMvc.service.Impl;

import java.util.List;

import org.apache.log4j.Logger;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import com.springMvc.dao.manager.TypeManager;
import com.springMvc.model.TypeInfo;
import com.springMvc.service.TypeService;
@Service
public class TypeServiceImpl implements TypeService{

	//logger
	private final static Logger logger = Logger.getLogger(TypeServiceImpl.class);
	@Autowired//自动装配dao bean
	private TypeManager typeManager;
	
	@Override
	public List<TypeInfo> selectAllTypes() {
		logger.info("----------------调取可用类型,Service层--!");
		return typeManager.selectAllTypes();
	}

}

 

两个service接口,两个实现类,没什么好解释的,然后写两个controller,一个是进入记账页面时调取可选类型的action,一个是保存记账的action如下

 

package com.springMvc.controller;

import java.sql.Date;

import org.apache.log4j.Logger;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.servlet.ModelAndView;

import com.springMvc.service.AccountService;

@Controller
public class AccountController {
	//logger
	private final static Logger logger = Logger.getLogger(AccountController.class);
	@Autowired//自动装配Service bean
	private AccountService accountService;
	
	@RequestMapping(value = "/insert.action")
	public ModelAndView insertAccount(@RequestParam("acc_time") Date acc_time,
			@RequestParam("acc_jine") Integer acc_jine,
			@RequestParam("acc_shouzhi") Integer acc_shouzhi,
			@RequestParam("acc_type") Integer acc_type,
			@RequestParam("acc_beizhu") String acc_beizhu){
		logger.info("----------------插入账目进入Controller--!");
		accountService.insertAccout(acc_time, acc_jine, acc_shouzhi, acc_type, acc_beizhu);
		/*返回主页由于我们的index.jsp放在了view外的web-inf外,所以写了两个../取上上层目录,
		 * 正常应该把index.jsp直接放到view文件夹下*/
		return new ModelAndView("../../index");
	}
}



 

package com.springMvc.controller;

import java.util.List;

import org.apache.log4j.Logger;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.servlet.ModelAndView;

import com.springMvc.model.TypeInfo;
import com.springMvc.service.TypeService;
@Controller
public class TypeController {
	//logger
	private final static Logger logger = Logger.getLogger(TypeController.class);
	@Autowired//自动装配Service bean
	private TypeService typeService;
	
	@RequestMapping(value = "/toInsertPage.action")
	public ModelAndView listAllType(){
		logger.info("----------------调取可用type进入Controller--!");
		List<TypeInfo> types = typeService.selectAllTypes();
		//分别是视图名称,(通过前后缀的配置就是view下的insert.jsp,)后面是模型对象
		return new ModelAndView("insert", "types", types);
	}
}

controller写好了,做前端的也写好就Ok了,这里没有前端的同事,只好自己写了,我们把helloworld页面加个跳转按钮,连接到toInsertPage.action,这样请求可用账目类型,返回到insert.jsp.然后insert.jsp填写账目表单验证数据后提交到insert.action返回到index.jsp。其实就一个insert.jsp如下

 

 

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Insert title here</title>
<script type="text/javascript">
function checkData(){
	var acc_time = document.getElementById("acc_time").value;
	var acc_jine = document.getElementById("acc_jine").value;
	var acc_beizhu = document.getElementById("acc_beizhu").value;
	
	var regDate=/^(\d{4})-(\d{2})-(\d{2})$/;
	var regJine=/^[0-9]*[1-9][0-9]*$/;
	if(!regDate.test(acc_time)){
		alert("输入正确日期");
		return false;
	}
	if(!regJine.test(acc_jine)){
		alert("输入正确金额");
		return false;
	}
	if(acc_beizhu.length>100){
		alert("备注过长");
		return false;
	}
}
</script>
</head>
<body>
<form action="insert.action" method="post" onsubmit="return checkData()">
	<table cellpadding="0" cellspacing="0">
		<tr>
			<th>时间:</th><td><input type="text" name="acc_time" id="acc_time"/>
			格式为2010-02-01这里简单实现下功能,为学框架嘛,就没搞什么日期控件
			</td>
		</tr>
		<tr>
			<th>出入:</th>
			<td>
			<input type="radio" checked="checked" value="1" id="churu1" name="acc_shouzhi"/><label for="churu1">出</label>
			<input type="radio" value="0" id="churu0" name="acc_shouzhi"/><label for="churu2">入</label>
			</td>
		</tr>
		<tr>
			<th>金额:</th>
			<td><input type="text" name="acc_jine" id="acc_jine"/>
			单位为分,同上,简单实现下功能,输入正整数</td>
		</tr>
		<tr>
			<th>类型:</th>
			<td><select name="acc_type">
			<c:forEach items="${types }" var="type" varStatus="status">
			<option value="${type.type_id}">${type.type_name}</option>
			</c:forEach>
			</select></td>
		</tr>
		<tr>
			<th>备注:</th>
			<td><input type="text" name="acc_beizhu" id="acc_beizhu"></td>
		</tr>
		<tr>
		<th colspan="2"><input type="submit"/></th>
		</tr>
	</table>
</form>
</body>
</html>

tomcat run一下,进入hello world,点添加账目,跳转填写表单,填写内容后提交,跳回hello world页,logger info如下:流程很清晰了,就这样吧

 

分享到:
评论

相关推荐

    SpringMVC-配置文档

    SpringMVC-配置文档以及其中的说明。 包含使用注解、扫描带注解的包 、使用注解方式配置springMVC的映射器和适配器

    springmvc maven配置

    springmvc maven配置,里面有步骤,按照步骤搭建。

    SpringMVC一些配置文件的demo

    在"SpringMVC一些配置文件的demo"中,我们可以深入理解SpringMVC的核心配置和工作流程。 1. **核心配置文件**:SpringMVC的配置通常在`dispatcher-servlet.xml`中进行,这是SpringMVC的前端控制器DispatcherServlet...

    SpringMVC4.3.6配置json所需要的jar包

    SpringMVC4.3.6配置json所需要的jar包,不是使用最新最高的版本可以的,我尝试了,有错误:严重: Servlet.service() for servlet [springMVC] in context with path [/30-returnVoid-ajax] threw exception [Handler...

    SpringMVC基于代码的配置方式(零配置,无web.xml)

    传统的SpringMVC配置涉及web.xml文件,但现在我们可以完全通过Java配置来替代。 二、基于代码的配置 1. 引入依赖:首先,我们需要在项目中引入SpringMVC和Spring Boot的相关依赖,通常这些依赖会包含在Spring Boot...

    SpringMVC纯注解配置

    "SpringMVC纯注解配置"是SpringMVC框架的一种高级用法,旨在减少XML配置文件的使用,提高开发效率和代码可读性。在这个主题中,我们将深入探讨如何利用注解实现SpringMVC的配置以及jQuery如何处理后台返回的JSON对象...

    SpringMVC4零配置

    **SpringMVC4零配置详解** SpringMVC作为Spring框架的一部分,是用于构建Web应用程序的控制器层。在SpringMVC4版本中,引入了“零配置”概念,旨在简化开发流程,提高开发效率。本文将深入探讨SpringMVC4的零配置...

    springmvc的配置

    springmvc的配置 使用spring管理所有的avabean 通过依赖注入来实现类的初始化 maven命令:将仓库jar包复制到lib目录下 mvn dependency:copy-dependencies -DoutputDirectory=war/WEB-INF/lib -DincludeScope=...

    配置文件:SpringMVC核心配置文件示例

    java 配置文件:SpringMVC核心配置文件示例

    ssm框架整合的第一个阶段:完成了springmvc框架的配置和spring框架的配置

    在本文中,我们将深入探讨SSM整合的第一阶段,即完成SpringMVC和Spring框架的配置。 首先,Spring框架是Java企业级应用的核心,它提供了依赖注入(Dependency Injection,DI)和面向切面编程(Aspect-Oriented ...

    SpringMVC最小配置所需jar

    在开发SpringMVC应用时,正确配置所需的jar包至关重要,因为它们包含了运行SpringMVC所需的核心库和其他依赖。以下是关于SpringMVC最小配置所需jar包的详细解释: 1. **spring-webmvc.jar**:这是Spring MVC的主要...

    SpringMVC配置多数据源实战

    在SpringMVC框架中配置多数据源是一项常见的需求,尤其在大型企业级应用中,由于业务的复杂性,往往需要连接不同的数据库以满足不同模块的需求。以下将详细讲解如何实现这一功能。 首先,理解数据源(DataSource)...

    2019-SpringMVC配置

    这是SpringMVC的2019配置,最近需要做一个项目配置的原始环境,可以运行,简单配置的修改,大家应该自己能懂,里面需要的jar包,都存在了,写了一个简单的首页,调用bootstrap,jquery风格,需要的同学,自己下载吧。

    SpringMVC所有jar包和配置文件

    SpringMVC所有jar包和配置文件可以快速配置springmvc项目

    SpringMVC配置

    3. **配置SpringMVC上下文**:在指定的初始化参数中(如servlet-context.xml),配置SpringMVC的行为,包括视图解析器、模型视图、拦截器、转换器和格式化器等。 ```xml xmlns:xsi=...

    最简单的SpringMVC Maven配置.docx

    在本文中,我们将探讨如何进行最简单的SpringMVC Maven配置,这是一个常见的Java Web应用程序开发步骤。SpringMVC是Spring框架的一部分,它简化了处理HTTP请求和响应的方式。在这个配置中,我们将关注`web.xml`文件...

    springMVC整合ibatis 配置详细

    2. **配置SpringMVC**:配置SpringMVC的DispatcherServlet,设置拦截器、视图解析器等,同时开启注解驱动,如`@EnableWebMvc`。 3. **配置iBatis**:创建SqlSessionFactoryBean,配置数据源、MyBatis的配置文件路径...

    springMVC二级缓存配置

    Spring 3.1 引入了激动人心的基于注释(annotation)的缓存(cache)技术,它本质上不是一个具体的缓存实现方案(例如 EHCache 或者 OSCache),而是一个对缓存使用的抽象,通过在既有代码中添加少量它定义的各种 ...

    springMVC 多视图配置(Jsp Freemarket Json)实例

    在这个"springMVC 多视图配置(Jsp Freemarket Json)实例"中,我们将探讨如何在 SpringMVC 中设置多种视图解析器来支持 Jsp、FreeMarker 和 Json 格式的响应。 **1. 视图解析器 (View Resolver)** 在 SpringMVC ...

Global site tag (gtag.js) - Google Analytics