第29讲 Android+SSH综合项目实践
本例采用mysql数据库,数据库名为News,采用MySql Manager建立表达字段和约束。
首先修改系统上mysql的编码为utf8
建立数据库的时候采用utf8
建立表的时候用utf8
设置外键约束
数据库中字段的详细
二、搭建ssh框架环境
2.1 struts框架的搭建
(1)在Myeclipse中新建一个Web工程,使用JavaEE 5.0,添加struts框架进去
复制struts所需要的jar包进入工程(参考示例工程)
(2)在web.xml中加入启动struts的过滤器
<filter>
<filter-name>struts2</filter-name>
<filter-class>org.apache.struts2.dispatcher.ng.filter.StrutsPrepareAndExecuteFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>struts2</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
(3)创建struts.xml配置文件(参考blank示例工程中复制过来)
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE struts PUBLIC
"-//Apache Software Foundation//DTD Struts Configuration 2.0//EN"
"http://struts.apache.org/dtds/struts-2.0.dtd">
<struts>
<package name="" namespace="/" extends="struts-default">
<action name=" " class=" ">
</action>
</package>
</struts>
(4)创建整个工程的包目录
dao:持久层
service:业务层代码,将来可以作为切面,实现业务拦截,代码增强
action:控制层代码
orm:数据模型,实体类,以及映射文件
interceptor:Struts的拦截器
util:其他的工具类
2.2为工程添加spring框架
(注意:不要把所有的spring的包都复制进去,因此,在添加框架的时候,不要选择复制jar文件)
(1)选中工程,右键——add Spring …..
(2)选择创建一个新的applicationContext.xml配置文件:
(3)复制Spring的基本包进入工程:
spring-framework-2.5.5-with-dependencies\spring-framework-2.5.5\dist\ spring.jar
spring-framework-2.5.5-with-dependencies\spring-framework-2.5.5\lib\ jakarta-commons\commons-logging.jar
复制AspectJ和Annotation所需要用到的包:
spring-framework-2.5.5\lib\aspectj\ aspectjrt.jar
spring-framework-2.5.5\lib\aspectj\ aspectjweaver.jar
(4)复制Spring的struts插件包进去:(在struts2和spring结合的时候,必须使用这个包,后面有讲到,在后面添加也可以)
struts-2.3.1.2\lib\struts2-spring-plugin-2.3.1.2.jar
2.3 配置struts和spring的结合
(1)先配置好Struts2,然后将Spring2.5配置进来。(先确保Struts2配置正常,这个前面已经完成,三个部分:添加Struts相关包、添加struts.xml文件、在web.xml中添加struts的配置)
(2)将struts2-spring-plugin-2.3.1.2.jar复制到工程中,注意这个是在struts的包下(前面2.4节已经复制进去了)
在我们集成struts2+spring+hibernate,也就是所谓的S2SH,不可避免的要引入struts2-spring- plugin.jar插件。当引入这个插件后,原先所struts创建的action类,交给了spring创建。在struts2-spring- plugin.jar中有一个struts-plugin.xml,里面声明了action类由spring工厂创建。在struts2插件文档里,这样写着“The Spring Plugin works by overriding the Struts ObjectFactory to enhance the creation of core framework objects。”这个插件重写了struts的对象工厂,当创建一个action类时,它会根据struts的配置文件的class属性的值与 spring配置文件中的id属性的值相匹配。如果没有与之相匹配,将会像没有使用这个插件前一样创建,然后由spring自动装配。
在web.xml中编写自动启动Spring框架的监听器,并且制定spring配置文件applicationContext.xml的位置,指定给参数contextConfigLocation。(见下面代码方框中的内容)
<?xml version="1.0" encoding="UTF-8"?>
<web-app version="2.5"
xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"><!--
<!-- 指定Spring的配置文件位置 -->
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/classes/applicationContext.xml</param-value>
</context-param>
<!-- 指定以Listener方式启动Spring容器 -->
<listener> <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<!-- 指定以Listener方式启动Log4j -->
<listener>
<listener-class>org.springframework.web.util.Log4jConfigListener</listener-class>
</listener>
<!-- 定义Struts2的核心控制器,前面已经配置好了-->
<filter>
<filter-name>struts2</filter-name>
<filter-class>org.apache.struts2.dispatcher.ng.filter.StrutsPrepareAndExecuteFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>struts2</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
</web-app>
(3)在struts.xml中添加一个常量struts.objcetFactory,表示action对象都由spring框架来创建,如下红色部分代码:
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE struts PUBLIC
"-//Apache Software Foundation//DTD Struts Configuration 2.0//EN"
"http://struts.apache.org/dtds/struts-2.0.dtd">
<struts>
<constant name="struts.objcetFactory" value="spring"></constant>
<package name="" namespace="/" extends="struts-default">
<action name=" " class=" ">
</action>
</package>
</struts>
(4)加入sping框架后,编写一个Action的配置方法,就不再是只限于在struts.xml中配置了。
需要分为两步:首先在Spring配置文件applicationContext.xml中装配Struts的Action实例,即将Action配置为一个bean,交由Spring来统一管理。然后将这个bean的id,作为action的class属性,配置到struts.xml中去。
注意:一旦配置了spring框架,struts.xml中的action的class属性,就不再是Action的类全名,而是Spring中定义的bean的id名。
2.4 为工程添加Hibernate框架
(1)在此之前,需要在MyEclipse中添加一个数据库的驱动
(2)工程上点击右键,将Hibernate框架添加进去
选择将jar包复制到工程中来
选择不创建SessionFactory,因为我们在Spring中来创建
点击finish,出现自动生成的applicationContext.xml配置文件,里面会找不到BasicDataSource
<bean id="dataSource"
class="org.apache.commons.dbcp.BasicDataSource">
需要将…\spring-framework-2.5.5-with-dependencies\spring-framework-2.5.5\lib\jakarta-commons\common-dbcp.jar,common-pool.jar添加进去
DBCP(DataBase connection pool),数据库连接池。是 apache 上的一个 java 连接池项目,也是 tomcat 使用的连接池组件。单独使用dbcp需要3个包:common-dbcp.jar,common-pool.jar,common-collections.jar由于建立数据库连接是一个非常耗时耗资源的行为,所以通过连接池预先同数据库建立一些连接,放在内存中,应用程序需要建立数据库连接时直接到连接池中申请一个就行,用完后再放回去。参考书上使用的是c3p0,也是连接池组件。
c3p0与dbcp区别
dbcp没有自动的去回收空闲连接的功能
c3p0有自动回收空闲连接功能
(2)使用逆向工程将数据库生成相应的pojo和orm映射文件
先添加数据驱动,切换到Database视图,将数据库加到MyEclipse中来。
Update hibernate configuration with mapping 这项打勾是自动更新applicationContext.xml中的映射配置文件列表,下一步,选择主键生成器为native——完成。
逆向工程完成后,可以查看到,在orm包中有自动生成的实体类和hibernate映射配置文件。
还可以查看到,applicationContext.xml中有自动生成的相关代码
(3)在applicationContext.xml中添加如下三句代码
<prop key = "hibernate.show_sql">true</prop>
<prop key="hibernate.format_sql">true</prop>
<!-- 设置Hibernate是否在控制台输出SQL语句,开发调试阶段通常设为true -->
<!-- 设置Hibernate一个提交批次中的最大SQL语句数 -->
<prop key="hibernate.jdbc.batch_size">50</prop>
添加后,在执行hibernate的数据库操作时,会在控制台打印出sql语句:
3.1 实现步骤概述
(1)编写数据库访问层dao、业务层service
编写好 XxxDao. java,XxxDaoImpl. java以及XxxService. java,XxxServiceImpl. java。设置dao和service层次的依赖注入关系,在applicationContext.xml中进行配置,并测试dao和service的各个方法的可用性,再进行下一步操作。
(2)编写控制层代码action
编写XxxAction.java,设置action和service的依赖关系,分别在applicationContext.xml和struts.xml中配置相关的Action,在浏览器上测试action的有效性,再用Android客户端来访问。
http://localhost:8080/Demo1/RegisterAction?username=xiaoming&password=456&realname=xiaoming
(3)编写Android端程序,设置Android的HttpClient访问的url为上述的Action的地址。
3.2 以user表的操作为例,演示整个ssh框架的编写过程。
1、编写UserDao和UserDaoImpl
package com.demo.dao;
import com.demo.orm.User;
public interface UserDao {
public void saveUser(User u);
}
public class UserDaoImpl extends HibernateDaoSupport implements UserDao {
public void saveUser(User u) {
this.getHibernateTemplate().save(u);
}
}
在applicationContext.xml中配置UserDaoImpl
<bean id="UserDaoImpl" class="com.demo.dao.impl.UserDaoImpl">
<property name="sessionFactory" ref="sessionFactory"></property>
</bean>
用main函数测试
public static void main(String [] args){
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
UserDaoImpl userDaoImpl =(UserDaoImpl) context.getBean("UserDaoImpl");
User u = new User();
u.setRealname("汤姆");
u.setUsername("tom");
u.setPassword("123");
userDaoImpl.saveUser(u);
}
2、编写业务层UserService、UserServiceImpl,注意和UserDao的依赖注入关系。
public interface UserService {
public void register(User u);
}
public class UserServiceImpl implements UserService{
UserDao dao;
public UserDao getDao() {
return dao;
}
public void setDao(UserDao dao) {
this.dao = dao;
}
public void register(User u) {
dao.saveUser(u);
}
}
<bean id="UserServiceImpl" class="com.demo.service.impl.UserServiceImpl">
<property name="dao" ref="UserDaoImpl"></property>
</bean>
在main中测试UserService
public static void main(String [] args){
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
UserServiceImpl us =(UserServiceImpl) context.getBean("UserServiceImpl");
User u = new User();
u.setRealname("李磊");
u.setUsername("lilei");
u.setPassword("123");
us.register(u);
}
3、编写控制层代码RegisterAction
public class RegisterAction extends ActionSupport implements ServletRequestAware,ServletResponseAware{
//获取到request response对象
HttpServletRequest request;
HttpServletResponse response;
public void setServletRequest(HttpServletRequest request) {
this.request=request;
}
public void setServletResponse(HttpServletResponse response) {
this.response=response;
}
UserService userService;
public void setUserService(UserService userService) {
this.userService = userService;
}
public String execute() throws Exception {
String username = request.getParameter("username");
String password = request.getParameter("password");
String realname = request.getParameter("realname");
User u = new User();
u.setPassword(password);
u.setRealname(realname);
u.setUsername(username);
userService.register(u);
response.getWriter().write("success");
return null;
}
}
在applicationContext.xml中对RegisterAction进行配置,注意依赖UserService
<bean id="RegisterAction" class="com.demo.action.RegisterAction">
<property name="userService" ref="UserServiceImpl"></property>
</bean>
在struts.xml中对RegisterAction进行配置
<struts>
<constant name="struts.objcetFactory" value="spring"></constant>
<package name="default" namespace="/" extends="struts-default">
<action name="RegisterAction" class="RegisterAction">
</action>
</package>
</struts>
4、在浏览器中测试LoginAction
http://localhost:8080/Demo1/RegisterAction?username=xiaoming&password=456&realname=xiaoming
5、编写Android客户端的用户注册界面,用户注册的业务类、用户注册的Activity。
(1)编写注册的业务类,将数据发送到服务器,得到返回的结果,是注册成功、失败、还是连接网络失败。
(2)编写Android的注册界面及Activity
注意:向服务器提交数据的中文编码问题(见22讲)
3.3 用户登录部分
1、服务器端:持久层UserDao、UserDaoImpl添加一个业务方法
public User findUserByNameAndPw(String username, String pw) {
List<User> list = (List<User>)this.getHibernateTemplate().find(
"from User as u where u.username=? and u.password = ?",
new Object[] { username, pw });
if(list.size()>0){
return list.get(0);
}
return null;
}
2、业务层、控制层依次调用
3、页面上测试登录的url
4、在Android端编写登录的相关代码。
3.4 对已经注册的用户进行管理(服务器端完成)
3.5 对新闻的管理(服务器端完成)
3.6 客户端显示新闻列表(使用JSON封装客户端和服务器端交换的数据)
3.6.1 服务器端部分
1、编写NewsDaoImpl NewsServiceImpl
2、NewsAction,将List封装的新闻列表以json格式发送到客户端
需要用到JSONArray工具类,需要将其jar包导入到工程中
public class NewsAction extends ActionSupport implements ServletRequestAware,ServletResponseAware{
NewsService service;
public NewsService getService() {
return service;
}
public void setService(NewsService service) {
this.service = service;
}
HttpServletRequest request;
HttpServletResponse response;
public void setServletRequest(HttpServletRequest request) {
this.request = request;
}
public void setServletResponse(HttpServletResponse response) {
this.response=response;
}
@Override
public String execute() throws Exception {
List<News> list = service.listAllNews();
//将所有的List中的信息以json的形式写入到客户端
JSONArray a = new JSONArray(list);
System.out.println(a.toString());
response.setContentType("text/html;charset=utf-8");
response.getWriter().print(a.toString());
return null;
}
}
在浏览器中测试Action:
http://192.168.101.62:8080/Demo1/NewsAction
单条JSON数据格式如下:
"id":1,
"newsTilte":"我院举办2013年\u201c廉洁·诚信\u201d校园辩论赛",
"newsContent":"本次辩论赛是学院根据《省委教育工委 省教育厅关于举办全省高校首届大学生\u201c廉洁·诚信\u201d主题辩论赛的通知》文件精神,结合我院思政课教学实践活动开展的,旨在深入学习贯彻党的十八大精神,扎实推进廉洁文化进校园活动,引导大学生增强理性思辨能力、诚实守法意识和廉洁自律意识,提高思想道德水平,培养合格的社会主义建设者和接班人。学院非常重视本次比赛,专门成立了辩论赛领导小组,由思政部、党办、学工处、纪委监查审计室和各系联合承办,演讲与口才协会协办。",
"stauts":1,
"publishDate":"2013-06-26 00:00:00.0"
},
"picture":{"id":1,"hibernateLazyInitializer":{"unwrap":false,"persistentClass":"class com.demo.orm.Picture","identifier":1,"uninitialized":true,"entityName":"com.demo.orm.Picture"}},
"newstype":{"id":1,"hibernateLazyInitializer":{"unwrap":false,"persistentClass":"class com.demo.orm.Newstype","identifier":1,"uninitialized":true,"entityName":"com.demo.orm.Newstype"}},
3.6.2 客户端部分
1、编写客户端访问新闻列表数据的业务类NewsService
将收到得客户端的json格式的新闻数据,通过工具类JSONArray,再次重组成List<News>对象。
public class NewsService {
public List<News> getAllNewsList(){
String basePath = "http://192.168.101.62:8080/Demo1/NewsAction";
try {
URL url = new URL(basePath);
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setConnectTimeout(5000);
conn.setRequestMethod("GET");
if(conn.getResponseCode()==200){
Log.i("test", "网络连接成功,采用Get方法提交数据");
InputStream is = conn.getInputStream();
BufferedReader br = new BufferedReader(new InputStreamReader(is));
String result ="";
String line="";
while((line=br.readLine())!=null){
result+=line;
}
Log.i("test", result);
List <News> list = new ArrayList<News>();
JSONArray jSONArray = new JSONArray(result);
for(int i = 0;i<jSONArray.length();i++){
JSONObject object = jSONArray.optJSONObject(i);
String newsTilte = (String) object.get("newsTilte");
String newsContent = (String) object.get("newsContent");
String publishDate = (String) object.get("publishDate");
JSONObject newspic = object.getJSONObject("picture");
int newspicId = (Integer) newspic.get("id");
JSONObject newstype = object.getJSONObject("newstype");
int newstypeId = (Integer) newstype.get("id");
News news = new News(newstypeId, newspicId, newsTilte,
newsContent, publishDate);
list.add(news);
}
return list;
}
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return null;
}
}
2、编写新闻标题列表的Activity及界面
界面略
public class ListNewsActivity extends Activity {
ListView lv;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_list_news);
lv = (ListView) this.findViewById(R.id.lv_news);
NewsService service = new NewsService();
List<News> newslist = service.getAllNewsList();
List<HashMap<String, Object>> data = new ArrayList<HashMap<String, Object>>();
for(News news:newslist){
HashMap<String, Object> map= new HashMap<String, Object>();
map.put("newstilet", news.getNewsTilte());
map.put("newscontent", news.getNewsContent());
map.put("publishdate", news.getPublishDate());
map.put("newstpye_id", news.getNewstypeId());
map.put("newspic_id", news.getPictureId());
data.add(map);
}
SimpleAdapter adapter = new SimpleAdapter(this,data,
R.layout.item,new String[]{"newstilet","publishdate"}, new int[]{R.id.tv_newstitle,R.id.tv_newstime});
lv.setAdapter(adapter);
}
}
3.7 单个新闻显示页面
1、编写单个新闻显示的Activity,编写单个新闻显示的界面(线性布局)
2、在新闻列表Activity中编写单个列表项的选项点击事件:
lv.setOnItemClickListener(new OnItemClickListener(){
@Override
public void onItemClick(AdapterView<?> parent, View view,
int position, long id) {
String news_title= newslist.get(position).getNewsTilte();
String news_content = newslist.get(position).getNewsContent();
String news_time = newslist.get(position).getPublishDate();
int newspic_id = newslist.get(position).getPictureId();
//跳转到单个新闻显示页面,用Intent将数据传输过去(代码略)
}});
3.8 图片的显示
在客户端只能得到新闻的图片id,希望通过图片id能显示到该张图片
1、在服务器端编写一个程序,得到客户端提交的图片id,查询到图片的具体地址,编写一个专用于显示图片的Action
public class ImageAction extends ActionSupport implements ServletRequestAware,ServletResponseAware{
HttpServletRequest request;
HttpServletResponse response;
public void setServletRequest(HttpServletRequest request) {
this.request = request;
}
public void setServletResponse(HttpServletResponse response) {
this.response=response;
}
//客户端就通过:http://localhost:8080/Demo1/ImageAction?pic_id=3 来访问图片
public String execute() throws Exception {
int pic_id = Integer.parseInt(request.getParameter("pic_id"));
//做一个查询,通过pic_id得到图片的具体的地址(代码略)
//假设这个地址已经获得,是
String pic_add="aa.jpg";
//http://localhost:8080/Demo1/pic/aa.jpg
response.setContentType("image/jpeg");
OutputStream out = response.getOutputStream();
FileInputStream in = new FileInputStream(request.getRealPath("pic")+"/"+pic_add); //得到图片的服务器的文件的输入流
//将图片的输入流,写入客户端的输出流
if(in!=null){
byte [] b= new byte[1024];
int len=0;
while((len=in.read(b))!=-1){
out.write(b, 0, len);
}
}
out.flush();
return null;
}
}
2、在浏览器中测试,看是否能
通过:http://localhost:8080/Demo1/ImageAction?pic_id=3 来访问图片
3、编写客户端访问图片的程序,将图片显示到客户端的ImageView上,详细代码见第22讲网络图片的查看