SSM 学习笔记

六、SSm框架

1.1 Spring Framwork框架

Day03

1.1.1 何为框架

Mvc 结构

1.1.1.1 何为软件?

软件:是程序和文档的一个集合,是用来解决问题.

例如:

为了方便购物:电子商务系统(京东,天猫)

为了解决打车难的问题(滴滴)

为了解决最后一公里问题(OFO,摩拜,…)

……..

扫描二维码关注公众号,回复: 1717949 查看本文章

从软件编程角度分析,软件具体解决了那些问题?

解决数据输入问题(键盘输入,语音输入)

解决数据处理问题(日志记录,数据的计算,数据推送问题)

解决数据输出问题(数据展现,……)

站在软件设计的角度,采用哪种方式解决问题会更好?

面向过程?(着眼点解决问题的基本算法及步骤)

面向对象?(着眼点使用哪些对象解决问题)

在这些过程中,对象如何设计,对象依赖管理,内存管理如何实现?

……

接口:标准

抽象类:标准的非完全实现(半成品)

具体类:接口或抽象类的完美实现(成品)

1.1.1.2 何为框架(Framework)?

        框架是软件编程中对特定问题的封装和抽取.是一个为了降低软件编程难度,提高软件运行性能,提高软件可维护性,可扩展性而推出的半成品.

例如spring,mybatis,…..

生活中的框架:简历的模板就构建一份完成简历的框架.

说明:框架应用及设计时应用三个维度进行考虑.

面向对象 (从静态角度进行分析,一个软件有哪些对象,对象有哪些属性与行为,对象与对象之间有什么关系,如何通过这些对象解决对应问题)

面向切面 (从动态角度进行分析,在对象运行时如何为对象扩展新的业务功能)

面向服务 (从服务角度进行细粒度控制,然后进行RPC调用)

分析现阶段做过的项目?

项目中采用的什么架构?

项目中分了几层,每一层有什么职责,层与层之间如何关联的?

项目中对象创建以及对象的管理如何实现的?

项目中的线程安全如何考虑的?

………

1.1.2 Spring 框架

1.1.2.1 Spring 概述

1.1.2.1.1 Spring 是什么?

Spring 是一个开源控制反转面向切片容器工厂框架(Framwork),主要是要从三个维度(面向对象,面向切面,面向服务)通过整合一定的资源解决软件中的一些相关问题.

从细节上spring核心主要解决如下几个问题:

对象的创建(通过工厂创建)      -- 底层通过反射实现

对象的管理(重点管理对象依赖关系)    -- 底层通过内省实现

回顾我们需要一个对象时,是如何创建对象的?

直接创建(例如 new Point())

通过静态工厂(例如 Calendar.getInstance(),JdbcUtil.getConnection())

通过动态工厂(基于配置与反射实现对象的创建,

例如ApplicationContext 本质上就是一个动态工厂,具体可参考课堂

项目案例)

动态工厂涉及到的知识点:

配置文件(xml)

流对象应用(IO)

xml文件的解析(dom4j,扩展了解sax,pull)

反射技术(Class)

1.1.2.1.2 Spring 应用动机?

Spring 在JavaEE体系中最主要的动机是整理企业可整合资源然后提供一套完整的解决方案,例如.

整合web技术,提供一套MVC架构应用

整合JDBC功能,提供一套数据持久化方案

整合安全框架(例如shiro),提供一套权限控制方案

整合定时任务调度框架,提供一套任务调度方案

整合消息中间件JMS,提供一些高并发处理方案

……………………..

回顾:Java中的定时任务如何实现?(Timer, ScheduledExecutorService)

1.1.2.1.3 Spring 基本架构?

架构图中先了解如下几个部分

IOC (控制反转)

AOP (面向切面)

WEB (WEB-MVC)

ORM (对象关系映射)

…….

1.1.3 Spring IOC 基础应用

1.1.3.1 Spring底层实现案例

1.)准备工作:

2.)中心代码实现ApplicationContext.java文件:

3.)applicationContext.xml配置文件

4.)测试类TestFactoryDemo01.java文件

1.1.3.2 Spring IOC 概述

Spring  IOC 是实现了控制反转功能的一个容器对象,它要通过这个对象,实现对象之间依赖关系的管理.
        目的:主要是实现对象之间解耦合.以提高程序的可维护及可扩展性.

       Spring中的IOC(控制反转)功能如何实现?(借助DI:依赖注入)

   说明:  对象关系

 对象关系(java中对象的关系)
     Is  a  (继承或实现)类继承

Has a  (关联关系)  属性

Use a  (依赖关系)  方法使用

   Class Student  extends   Person{  //is  a 关系

 Computer  c;   //has  a 关系

Public  void  set(){Math.Random();    //use  a 关系 }

}

1.1.3.3 Spring Bean 容器

         Spring Bean容器负责创建Bean对象,并管理bean对象. (底层根据元数据的描述与反射技术进行对象的创建-----底层是根据反射技术实现对象的创建和通过内省技术实现对象的管理)

         Spring 中容器对象父类类型ApplicationContext类型,基于此类型Spring 容器还提供了很多具体类型,例如 ClassPathXmlApplicationContext类型.

   说明:

1)何为元数据?(描述数据的数据)

       2)Java中常用的元数据表示形式?(xml,注解)

1.1.3.3.1 Spring Bean 容器元数据配置

本小节基于xml方式,配置spring相关信息

<?xml version="1.0" encoding="UTF-8"?>

<beans 

    xmlns="http://www.springframework.org/schema/beans" 

    xmlns:p="http://www.springframework.org/schema/p"

    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 

    xmlns:context="http://www.springframework.org/schema/context"

    xmlns:tx="http://www.springframework.org/schema/tx"

    xmlns:aop="http://www.springframework.org/schema/aop"

    xmlns:mvc="http://www.springframework.org/schema/mvc"

    xmlns:util="http://www.springframework.org/schema/util"

    xmlns:jpa="http://www.springframework.org/schema/data/jpa"

    xsi:schemaLocation="  

       http://www.springframework.org/schema/beans   

       http://www.springframework.org/schema/beans/spring-beans-4.3.xsd  

       http://www.springframework.org/schema/mvc   

       http://www.springframework.org/schema/mvc/spring-mvc-4.3.xsd   

       http://www.springframework.org/schema/tx   

       http://www.springframework.org/schema/tx/spring-tx-4.3.xsd   

       http://www.springframework.org/schema/aop

       http://www.springframework.org/schema/aop/spring-aop-4.3.xsd

       http://www.springframework.org/schema/util

       http://www.springframework.org/schema/util/spring-util-4.3.xsd

       http://www.springframework.org/schema/data/jpa

       http://www.springframework.org/schema/data/jpa/spring-jpa-1.3.xsd

       http://www.springframework.org/schema/context

       http://www.springframework.org/schema/context/spring-context-4.3.xsd"> 

      <bean id="date1" 

             class="java.util.Date"/>

</beans>

1.1.3.3.2 Spring Bean 容器对象的初始化

Spring 中ApplicationContext为容器类型的父类类型,在初始化时可以初始化具体的子类类型.

ClassPathXmlApplicationContext ctx=

new ClassPathXmlApplicationContext(

 "beans.xml");

1.1.3.3.3 Spring Bean 容器对象的使用

Date date1=(Date)ctx.getBean("date1");

Date date2=ctx.getBean("date1",Date.class);

1.1.3.4 Spring Bean 对象

底层构建bean对象管理bean对象的思维图

1.1.3.4.1 Spring Bean 对象命名及初始化

对象命名一般是在配置文件中通过id或name属性指定其名称.

对象的初始化一般指的是对象的构建,一般有三种方式

直接通过类的构造方法

直接通过类的静态方法(静态工厂)

直接通过类的实例方法

通过spring的工厂创建(需要实现FactoryBean接口)

1.1.3.4.2 Spring Bean 对象的作用域

Spring中Bean对象的作用域一般是通过Bean元素的score属性进行配置,

它的值最重要的有两个:

singleton (单例):每次从容器获取返回的都是一个对象(

内存中此类的实例只有一份),Spring中bean的默认作用域

prototype (多例):每次从容器获取都会创建一个新的类的实例

说明:当spring中的对象是单例设计时,这个对象的内部实例变量操作

必须是一个原子操作,否则可能会出现线程不安全的问题.

Spring提供的生命周期有:singleton整个应用只有一个(单例)、prototype每次调用都产生一个新的对象(非单例),request每个HTTP请求对应一个bean对象、session每个HTTP回话对应一个beanglobal-session每个portlet上下文session对应一个beanSpring中默认的作用域是singleton

FAQ?

何为单例,单例对象一般如何设计?

1.1.3.4.3 Spring Bean 对象的生命周期

当Spring中bean元素的作用域为singleton时,此对象的生命周期由spring管理,

假如作用域为prototype时,bean对象的创建可以由spring负责,但对象的管理及销毁Spring不在参与.

假如对象在初始化,销毁时需要执行一些动作可以在bean元素中通过属性

init-method,destroy-method属性指定要调用的具体方法.

      <bean id="helloService"

            class="beans.HelloService"

            scope="singleton"

            init-method="doInit"

            destroy-method="doDestroy"/>

 初始化和销毁:
1)方式一:自定义初始化和销毁    ==========可以自己在方法中添加一些操作。如上案列。
xml中的bean标签增加init-methoddestroy-method属性来指定初始化和销毁方法。 

2)方式二:在beanjava类中implements   InitializingBean或者DisposableBean接口实现对象的初始化和销毁。

3)方式三:在xml中的beans标签中增加default-init-methoddefault-destroy-method属性。来使用默认的初始化和销毁。

1.1.3.4.4 Spring Bean 对象的延迟加载

对象的延迟加载一般指的是何时需要何时加载,这是提高系统运行时性能的一种手段.

<bean 

            id="helloService"

            class="beans.HelloService"

            scope="singleton"

            init-method="doInit"

            destroy-method="doDestroy"

            lazy-init="true"/>

说明:spring在启动时默认会初始化所有的bean对象,假如有一些bean对象

可能长时间用不到却创建了此对象,那么一些系统资源就会被浪费,解决方案

就是将此对象的加载机制设置延迟加载.假如希望所有对象都采用延迟加载

策略可以在配置文件的根元素beans中添加一个属性default-lazy-init="true"

设置即可.

这种延迟加载只对singliton对用域的时候管用。

1.1.3.5 Spring Bean依赖

1.1.3.5.1 依赖基础

FAQ

当spring在构建对象时如何为对象的属性赋值?(set,构造方法)

当spring初始化一个bean对时假如还需要依赖其它对象如何实现?

1)就需要用set注入的property标签 ,其中 name属性为类的get方法的名字(底层自动转换首字母大小写),ref属性为spring容器中别的bean对象的id值(也就是你需要的其他对象)

        

  2)就需要用constructor(构造器)注入的constructor-arg标签 ,其中 ref属性为spring容器中别的bean对象的id值(也就是你需要的其他对象)。

         

Spring 中对象属性及以关系的注入方式有两种:

Set 注入 (通过对象的set方法为参数赋值)

构造注入 (通过对象的构造方法为参数赋值)

Spring 容器中的Bean对象通常会存在一定的依赖关系,而这种依赖关系的实现在Spring 框架中要借助于DI机制。其中DI就是借助对象管理对象依赖关系的一个过程。

Spring中提供的依赖注入方式有构造注入和设置注入,其中构造注入就是借助构造方法的参数实现对类中属性值的注入,set注入就是借助set方法的参数实现其属性值的注入。

Spring 依赖注入时,可以实现基本值的注入,Bean对象的注入,集合的注入,spring表达式方式的注入等等。

set注入:(重点掌握)

<bean id="dataSource1"

class="com.company.spring.util.DataSource">

         <property name="driverClassName"

value="com.mysql.jdbc.Driver"/>

         <property name="url" value="jdbc:mysql:///test"/>

         <property name="username" value="root"/>

         <property name="password" value="root"/>

</bean>

<bean id="jdbcTemplate1"

class="com.company.spring.injection.JdbcTemplate">

         <property name="dataSource" ref="dataSource1"/>

</bean>

constructor注入:(了解)

<bean id="dataSource2" class="com.company.spring.util.DataSource">

         <constructor-arg value="com.mysql.jdbc.Driver"/>

         <constructor-arg value="jdbc:mysql:///test"/>

         <constructor-arg value="root"/>

         <constructor-arg value="root"/>

</bean>

<bean id="jdbcTemplate2"

class="com.company.spring.injection.JdbcTemplate">

         <constructor-arg ref="dataSource2"/>

</bean>

1.1.3.5.2 依赖高级

依赖高级就是给map集合,list集合,数组,还有配置文件等高级元素注入值。案例如下:

Spring表达式:通过spring表达式读取配置文件信息

                              #{} 为spring表达式

                              #{cfg.port}表示获取cfg指定的配置文件中key为port的值

               

1.)首先配置complex.xml文件

2.)配置config.properties文件

    Properties文件一般为配置文件,里面储存内容的格式为K=V(键值对)。

3.)  定义配置类ComplexObject.java文件

4.)测试类TestComplex.java 文件

实际项目中对象的属性可能会有数组类型,集合等类型,这些类型的注入的方式可参考如下案例实现。

定义一个相对复杂的对象

public class ComplexObject {

private String[] hobby;

private List<String> address;

private Map<String,String> phones;

private Properties configs;

//set,get,…

}

对象在配置文件中的实现:

<bean  id="complexObject"

class="com.company.spring.injection.ComplexObject">

          <property name="hobby">

             <list>

                <value>足球</value>

                <value>篮球</value>

             </list>

          </property>

          <property name="address">

             <list>

                <value>北京</value>

                <value>上海</value>

             </list>

          </property>

          <property name="phones">

             <map>

                <entry key="pKey1" value="123"/>

                <entry key="pKey2" value="456"/>

             </map>

          </property>

          <property name="configs">

             <props>

                <prop key="cKey1">#{cfg.port}</prop>

                <prop key="cKey2">#{cfg.host}</prop>

             </props>

          </property>

      </bean>

      <util:properties id="cfg"

location="classpath:config.properties"/>

提示:

其中util:properties元素用于引入配置文件,并可以通过spring表达式获取对应key的值,然后注入给其它对象的属性。

1.1.3.5.3 自动装配

Spring 会根据Bean中的相关方法对Bean中的相关元素进行值的注入,这种方式的实现需要借助spring bean元素中的autowire属性实现,此属性的值常用的有四个:

     

1)no:不进行自动装配

2)byName:由spring获取bean对象中的set方法,取方法名set单词后面的内容,第一字母小写 ,

然后根据这个名字在容器中进行查找(比对bean元素的id),假如找到对应名字,则将这个名字对应的对象注入给对应set方法的参数. 也有可能找到了名字相同但类型不同的对象,此时就会抛出异常.

3)byType:由spring获取bean对象中的set方法,取方法中参数的类型, 然后根据这个类型在容器中

进行查找(比对bean元素的类型),假如找到对应类型,则将这个类型对应的 对象注入给对应set方法的参数.也有可能找到了类型相同的多个对象,此时就会抛出异常.

4)constructor:由spring获取bean对象中所有的构造函数,依据构造函数中参数的类型在spring容器查找相匹配的对象,找到则注入,没找到则不执行.假如找到多个则先进行参数名匹配,名字也不匹配则不进行注入

案例:

Spring IoC容器可以自动装配(autowire)相互协作bean之间的关联关系,autowire可以针对单个bean进行设置,autowire的方便之处在于减少xml的注入配置

Spring 配置文件中通过bean元素的autowire属性指定自动装配规则,一共有四种类型值

 

属性值

描述

 

NO

禁用自动配置(默认)

 

ByName

按名字自动装配(重点掌握)

 

ByType

按类型自动状态(重点掌握),但有多个类型时会出错

 

Constructor

byType类似,不同之处在于它应用于构造器参数。

例如:

  <bean id="jdbcTemplate3"

             class="com.company.spring.injection.JdbcTemplate"

             autowire="constructor">

  </bean>

  <bean id="jdbcTemplate3"

             class="com.company.spring.injection.JdbcTemplate"

             autowire="byName">

  </bean>

1.1.4 Spring IOC注解应用

1.1.4.1 实现spring注解加载bean案例:

1.1.4.2 常用注解标记

1.1.4.2.1 注解标记4种的解释

Spring运行时会扫描此包(project),包括子包中的.class文件,

            然 后将有@Component,@Controller,@Service,@Repository等 注解描述的类构建成对象,然后存储到map,key默认为类名,

            且 类名的首字母小写.,如果没有spring规定的注解描述。此类的对象就不会构建在spring的容器中,spring也不会管理这些类的对象。

       1)@Controller   一般用于描述控制层对象(controller)     C

       2)@Service         一般用于描述业务层对象(model)          M

       3)@Repository  一般用于描述数据层对象                     数据持久层对象

       4)@Component 一般用于修饰其它组件对象

        自动扫描设置:                设置扫描的包:

<context:component-scan   base-package="project"/>

1.1.4.2.2 Spring注解的使用

1.把需要spring管理的类上面加上spring规定的注解描述。如:

1.1.4.3 注解标记命名

1.1.4.3.1 注解定义-自定义注解:

 * 1)是java中的一种元数据,用于描述java对象

 * 2)本质上也是一个类,

 * @Retention  表示自己定义的注解何时有效

 * @Target   表示自己定义的注解应用在哪里

@Retention(RetentionPolicy.RUNTIME)    //应用场景  运行时

@Target(value=ElementType.TYPE)         //应用地方  类

1.1.4.3.2 1.创建注解.class文件。@Annotation
1.1.4.3.3 2.使用自定义注解

1.1.4.4 Bean组件扫描

1.1.4.4.1 组件扫描定义

Spring中通过指定一个包路径,由系统自动扫描该包及其子包所有组件类,当发现组件类定义前有特定的注解标记时,就将该组件纳入到Spring容器。

这种方式的配置等价于原有XML配置中的<bean>定义功能组件扫描,可以替代大量XML配置中的<bean>元素的定义

1.1.4.4.2 扫描方式配置

使用组件扫描,首先需要在XML配置中指定扫描父级package路径,例如

<context:component-scan base-package=”com.company.spring/>”

在这个配置中,容器会自动扫描org.example包及其子包下所有组件,并实例化bean对象。

1.1.4.5 系统组件注解

1.1.4.5.1 组件注解标记

指定扫描类路径后,并不是该路径下所有组件类对象都由Spring容器创建并管理,只有在组件类定义前面有以下注解标记时,才会扫描到Spring容器。

 

注解名

说明

 

@Component

通用注解

 

@Repository

持久层组件应用注解

 

@Service

业务层组件应用注解

 

@Controller

控制层组件应用注解

提示:实际项目中某个业务的实现,是要进行分层处理的,每一层的对象都应该有自己的职责,目的是便于维护与扩展。

例如:

数据层对象

@Repository

public class SysUserDaoImpl implements SysUserDao{

public void saveUser(Object obj) {

System.out.println("dao.save.user");

}

}

业务层对象

@Service

public class SysUserServiceImpl implements SysUserService{

}

控制层对象

@Controller

public class UserController {

 

}

 

1.1.4.5.2 系统组件命名

当一个组件在扫描过程中被检测到时,会生成一个默认id值,默认id为小写开头的类名。也可以在注解标记中自定义id,例如

@Component

public class IdGenerator {

public IdGenerator() {

System.out.println("IdGenerator");

}

}

1.1.4.5.3 系统组件作用域

通常受Spring管理的组件,默认的作用域是"singleton"。如果需要其他的作用域可以使用@Scope注解,只要在注解中提供作用域的名称即可。

@Scope("singleton")

@Component

public class IdGenerator {

public IdGenerator() {

System.out.println("IdGenerator");

}

}

在Bean组件中还可以借助@PostConstruct和@PreDestroy注解标记指定

初始化和销毁回调方法。这个知识点了解即可。

@Scope("singleton")

@Component

public class IdGenerator {

public IdGenerator() {

System.out.println("IdGenerator");

}

@PostConstruct

public void init() {

System.out.println("init()");

}

@PreDestroy

public void destory() {

System.out.println("destory()");

}

}

1.1.4.5.4 Spring 注解标记延迟加载

在注解中,希望延迟加载对象。可以利用四种组件命名+@lazy(true),且默认作用域为singletor。

1.1.4.5.5 组件依赖关系注解

实际项目中对象与对象之间通常会具有一定的依赖关系,这种关系假如基于spring注解的方式进行注入,可使用@Autowired/@Qualifier,@Resource

注解在对象的属性或set方法上进行声明。

   

@Autowired注解应用

@Service

public class SysUserServiceImpl implements SysUserService{

@Autowired

private SysUserDao userDao;

public void saveUser(Object obj) {

    userDao.saveUser(obj);

}

}

  

   其中@Autowire注解默认按类型进行注入,假如希望按名字进行注入,可以再结合@Qualifier一起使用,例如

@Service

public class SysUserServiceImpl implements SysUserService{

@Autowired

@Qualifier("userDaoImpl")

private SysUserDao userDao;

public void saveUser(Object obj) {

    userDao.saveUser(obj);

}

}

  

@Resource注解应用

@Controller

public class SysUserController {

@Resource

private SysUserService userService;

public void doSaveUser(Object obj) {

userService.saveUser(obj);

}

}

  其中@Resource属于JavaEE中的一个注解,默认按对应的属性名进行装配注入,假如希望指定的名称进行注入,可通过注解中的name属性进行配置,例如

@Controller

public class SysUserController {

@Resource(name="userServiceImpl")

private SysUserService userService;

public void doSaveUser(Object obj) {

userService.saveUser(obj);

}

}

1.1.4.5.6 组件注入表达式应用

Spring应用中可借助@Value注解通过Spring表达式获取对应properties文件中的值,并将其值注入到对应对象的属性上

首先在XML配置中指定要注入的properties文件,例如

config.properties文件内容

prefix=CGB

suffix=JT

spring中配置文件中添加如下语句。

<util:properties

id="cfg" location="classpath:config.properties"/>

然后在属性或Setter方法前使用@Value注解

@Scope("singleton")

@Component

public class IdGenerator {

@Value("#{cfg.prefix}")

    private String prefix;

@Value("#{cfg.suffix}")

private String suffix;

}

 

1.1.4.5.7 案例一:

1.1数据层接口。用于类之间的依赖关系的实现。

1.2数据层的实现类,用来持久化数据(也就是把数据写到数据库或者硬盘上面)

2.1业务层接口。也就是model

2.2业务层的实现类。

3测试类

1.1.4.5.8 案例二:

1.1数据访问对象接口

1.2数据访问对象的实现类

2.1用户服务接口

2.2用户服务实现类

3.测试类

1.1.4.5.9 案例三:

测试

1.1.5 Spring IOC注解应用

1.1.5.1 Spring 配置组件扫描

Spring中对Bean元素的配置提供了两种方式,一种是基于xml方式,一种是基于注解方式.基于注解方式取实现Bean对象的管理时,首先需要配置对bean的扫描,就是告诉spring哪些对象需要交给spring管理.告诉的方式也有两种,第一种方式是直接在spring的配置文件中借助如下标签进行配置:

  <context:component-scan 

      base-package="cn.tedu.project"/>

spring容器在初始化,会解析此xml文件,然后获取包信息然后基于包信息从对应目录查找对应class文件.这些class文件并不一定都是由spring管理,spring只负责管理有特定

注解标记标识的class.例如@Service

1.1.5.2 Spring 系统组件应用

1.1.5.2.1 Spring 常用注解标记

Spring 中为了基于注解式配置和管理bean组件,推出了几个

用于描述bean组件的特定注解.常用的有:

 

注解名

说明

 

@Component

通用注解

 

@Repository

持久层组件应用注解

 

@Service

业务层组件应用注解

 

@Controller

控制层组件应用注解

以上注解的功能是一样的,只是描述的对象不同而已.在实际项目中通常会采用

分层架构的设计思想,最常用的分层架构就是MVC架构.而用于描述我们的bean组件的这些注解,通常会应用在MVC个组件对象中.例如web阶段最简单MVC架构图下:

此图中的Servlet一般充当MVC中的控制层对象,图中的Model可以

包含业务层对象,数据层对象.图中的JSP封装的视图逻辑.

例如:

数据层对象(封装数据逻辑,例如JDBC操作)

@Repository

public class SysUserDaoImpl implements SysUserDao{

public void saveUser(Object obj) {

System.out.println("dao.save.user");

}

}

业务层对象(封装业务逻辑,例如对用户权限进行检测,对操作进行日志记录)

@Service

public class SysUserServiceImpl implements SysUserService{

}

控制层对象(封装控制逻辑,例如请求处理,请求的响应)

@Controller

public class UserController {

 

}

视图层对象(一般封装视图逻辑,例如数据的呈现)

1.1.5.2.2 Spring 注解标记命名

在spring应用中,使用Spring注解描述的类,在构建对象进行存储时,都会默认给定

一个名字,这个名字就是所描述类的类名,并且类名的第一个单词的首字母小写.

例如:现有一个controller对象

@Controller

public class UserController {

 

}

使用@Controller注解修饰的UserController类,底层创建对象时,给对象起的名字为userController.假如不想使用这个名字,可以在使用注解描述类时,指定其名称.

例如:

@Controller(“userc”)

public class UserController {

 

}

此时这个controller对象在存储时,它的名字为userc,但实际应用中我并建议这么做,建议按默认名字.

1.1.5.2.3 Spring 注解标记作用域

使用Spring 注解标记描述的类,通常是要交给spring管理的,spring管理这些bean组件时,会按指定作用域进行管理.假如基于注解方式定义bean组件的作用域

需要使用@Scope注解,例如

@Scope(“singleton”)

@Controller

public class UserController {

 

}

对于这个UserController对象现在默认的作用域时单例,且生命周期由Spring管理.假如没有指定作用域默认就是singleton.

对一个Bean对象,假如希望这个对象是多例的我们可以使用@Scope(“prototype”)进行修饰.

1.1.5.2.4 Spring 注解标记延迟加载

延迟加载是很多框架中都会采用的一种技术,是从资源使用角度延迟对对象的创建.对象假如很长时间不使用,而且创建出来了,可能会占用系统中的很多资源.尤其是一些大对象(占用资源比较多的对象),一般都会采用延迟记载.具体基于注解

如何定义这个组件要采用延迟加载策略,可采用@Lazy(value=true)这种方案,当然

我们可以定义全局的延迟加载策略(在xml的beans元素内部进行定义).

@Lazy(value=true)

@Scope(“singleton”)

@Controller

public class UserController {

 

}

回顾:xml中全局延迟加载策略的定义

1.1.5.2.5 Spring 注解实现依赖注入

实际项目中我们通常会采用分层的设计思想,每一层对象都有它自己的一个

职责,然后这些对象相互关联,共同完成一个具体的业务,例如购物车业务,订单

业务.我们在基于注解方式配置对象依赖时,spring中可使用

@Autowired/@Qualifier,@Resource注解在对象的属性,构造方法

或set方法上进行声明。

位置使用说明

1)@Autowired/@Qualifier 应用在属性或构造方法上

2)@Resource一般应用在属性火set方法上.

应用规则说明:

通过@Autowired实现对象属性值的注入默认是按属性类型进行值的注入,假如

类中提供了与此属性有对应关的系构造函数则执行这个构造函数直接对属性

初始化,如果没有,底层通过反射获得属性类型以后,然后从容器中查找与此类型匹配的对象为其注入值.当按类型进行注入时,假如容器中存在多

个类型相同的对象时可能会注入失败,此时还可以借助@Qualifier这个注解指定

按名字进行注入(提示:在使用@Qualifier注解时前提必须已经使用了@Autowired注解),假如没有使用@Qualifier注解,此按默认属性名进行查找.

@Resource 註解一般用在屬性或set方法上用于为对象参数赋值,规则是假如这个注解中指定了名称,则只按注解中的name属性对应值

查找对象,然后进行值得注入.假如注解中没有指定名称,先按变量名进行查找,假如没找到,则按类型查找.假如Spring发现某个set方法上有此注解,则会

直接调用set方法为属性赋值.

@Autowired注解应用

@Service

public class SysUserServiceImpl implements SysUserService{

@Autowired

private SysUserDao userDao;

public void saveUser(Object obj) {

    userDao.saveUser(obj);

}

}

  

   其中@Autowire注解默认按类型进行注入,假如希望按名字进行注入,可以再结合@Qualifier一起使用,例如

@Service

public class SysUserServiceImpl implements SysUserService{

@Autowired

@Qualifier("userDaoImpl")

private SysUserDao userDao;

public void saveUser(Object obj) {

    userDao.saveUser(obj);

}

}

  

@Resource注解应用

@Controller

public class SysUserController {

@Resource

private SysUserService userService;

public void doSaveUser(Object obj) {

userService.saveUser(obj);

}

}

  其中@Resource属于JavaEE中的一个注解,默认按对应的属性名进行装配注入,假如希望指定的名称进行注入,可通过注解中的name属性进行配置,例如

@Controller

public class SysUserController {

@Resource(name="userServiceImpl")

private SysUserService userService;

public void doSaveUser(Object obj) {

userService.saveUser(obj);

}

}

1.1.5.2.6 Spring 组件注入表达式应用

Spring应用中可借助@Value注解通过Spring表达式获取对应properties文件中的值,并将其值注入到对应对象的属性上。

首先在XML配置中指定要注入的properties文件,例如

config.properties文件内容

prefix=CGB

suffix=JT

spring中配置文件中添加如下语句。

<util:properties

id="cfg" location="classpath:config.properties"/>

然后在属性或Setter方法前使用@Value注解

@Scope("singleton")

@Component

public class IdGenerator {

@Value("#{cfg.prefix}")

    private String prefix;

@Value("#{cfg.suffix}")

private String suffix;

}

1.1.6 总结

1.1.6.1 课后作业

1.1.6.1.1 Day01 作业

自己完成写一个Spring工厂(基于xml方式)

掌握课堂编写的Spring基本案例

尝试基于注解的形式写一个Spring工厂

给定一个包,包中几个类

定义一个工厂,扫描指定包中的类

基于反射构建所有类的对象

将对象存储于工厂对应的容器中(Map类型)

1.1.6.1.2 Day02作业

总结课堂知识点

完成课堂案例

尝试实现依赖高级部分的依赖注入

1.1.6.1.3 Day03作业

掌握基于xml自动装配机制

总结集合注入的几种方式

理解基于包的扫描机制构建类的对象

完成课堂基于注解的对象构建

预习基于注解的对象依赖关系设置

1.1.6.1.4 Day04 作用

总结整理课堂内容到文档中

完成课堂案例

尝试基于反射给对象中的一个私有属性赋值

课后了解代理模式

1.1.6.2 重点和难点分析

1.1.6.2.1 Day01 重点难点

Spring 是什么(框架:IOC,AOP,…..)

Spring 应用动机及应用场景(完整的解决方案)

Spring 容器工厂的初始化及bean对象的获取?

Spring 容器的关闭(销毁资源)

理解并能够自己写一个简易的spring工厂

1.1.6.2.2 Day02 重点难点

Spring Bean对象的命名及初始化

Spring Bean对象的作用域及生命周期

Spring Bean对象的延迟加载

Spring Bean对象的依赖注入(set注入,构造注入)

Spring Bean 对象的自动装配

1.1.6.2.3 Day03 重点难点

Spring Bean中集合注入如何实现

Spring Bean 中表达式的基本应用

Spring Bean 中修饰bean注解标记常用的有哪些?

Spring Bean 中注解修饰的Bean是如何命名的?

1.1.6.2.4 Day04 重点难点

Spring Bean 中基于注解方式如何定义bean的作用域

Spring Bean 中基于注解方式如何实现延迟加载

Spring Bean 中基于注解方式如何实现依赖注入

Spring Bean 中@Value注解如何应用(了解)

1.1.6.3 常见FAQ

1.1.6.3.1 Day01 FAQ

Spring 工厂构建Bean对象时,对Bean有什么要求?(无参的构造函数)

底层构建对象Class.forName(“包名.类名”).newInstance()

Spring 工厂底层构建Bean对象的机制?(借助反射)

Spring 中如何获取容器中的Bean对象,以及有哪些常用的重载方法?
getBean方法直接获取。getBean(“id”,类名.calss);

当对象不使用了要释放资源,目的是什么?(防止内存泄漏)

何为内存泄漏?
内存泄漏就是:对象不使用了,但是还存在引用指向这个对象,这样导致gc也回收不了这个对象。最终将会导致,内存溢出。内存泄漏是内存溢出的导火线。

-对象已经不使用了但占用着内存,这种现象称之为内存泄漏,内存泄漏

不是内存溢出,但是它是内存溢出的一个导火索.内存溢出直接就导致

系统崩溃了.

1.1.6.3.2 Day02 FAQ

Spring Bean对象默认的作用域是什么? ---Singleton

Spring Bean 的作用域为singleton时,Bean对象有什么特点?—单例。不管加载几次,都是同一个对象。

Spring Bean 元素配置的作用域为prototype时,

Bean对象有什么特点?  
  多例。就是每次获取这个对象和请求注入都会创建一个新的对象,且这个对象的创建由spring创建,但是创建之后的管理就不是spring管理了。

Spring Bean 元素中如何配置bean的生命周期方法?
init-method  创建之后初始化  和destroy-method  对象销毁方法指定

Spring Bean 为单例时,是否可能会存在线程安全问题?
是,存在

Spring Bean 中延迟加载机制有什么好处,如何配置?
可以合理的管理对象,节省内存的开支。可以在xml文件中 加default-lazy-init=“true”

Spring 中依赖注入的方式有哪些?

Set注入  构造注入    还有自动装配

Spring 中的Set注入有什么特点?

Set注入时按set方法的名字首字母小写在bean容器中查找id或者name属性值相同的对象。

Spring 中的构造注入有什么特点?

构造注入

Spring 中的自动装配如何理解?

       

1.1.6.3.3 Day03 FAQ

何为Spring Bean容器?

用于创建bean对象,管理bean对象的那个容器

Spring Bean容器与Spring IOC 容器有什么不同吗?

Spring IOC 容器本质上指的的就是Spring Bean容器,

Spring Bean容器中最核心一个机制是IOC机制(

控制反转),所以有时候又将springbean容器称之为

Spring IOC 容器.

Spring IOC 如何理解?

IOC 是Spring中提供一种控制反转机制,目的是将我们

项目中对象的依赖管理交给Spring实现,这样可以更好

实现对象关系的解耦,提高程序的可扩展性.

Spring DI 如何理解?

DI 是Spring中的依赖注入机制,IOC的实现需要借助

这种机制.我们通常会这样理解,Spring Bean容器中的

IOC思想一种目标,DI是实现这种思想的目标的手段.

Spring 中配置bean的方式有几种?

两种,基于xml和基于注解方式

Spring基于xml方式和基于注解方式配置bean对象时

有什么优缺点吗?

Xml:优点代码侵入性小,缺点是灵活不太好.

注解:优点灵活性比较好,缺点是存在一定的代码侵入性.

提示:基于注解方式虽然具备一定的代码侵入性,但是这种

侵入性输入声明式侵入性,这种侵入性在程序中是允许的.

它属于弱侵入性.

尝试自己写过SpringBean容器吗?写过

基于xml方式(xml解析+反射)

基于注解方式(反射)

Spring中集合的注入的方式?(map,list,set,数组)

Spring 中依赖注入表达式的应用? #{key.fileKey}

Spring 中修饰类的注解常用的有哪些?

  

1.1.6.3.4 Day04 FAQ

Spring 中基于注解如何配置对象作用域?

@Scope(“singleton”)@Scope(” prototype”)

Spring 中基于注解如何配置延迟加载机制?

Lazy(value=true)

Spring 中对象依赖关系的注入有哪些方式?

Autowired     Qualifier     resource    

Set 注入,构造注入

  xml注入和注解注入

Spring 中基于注解方式实现依赖关系注入时,有哪些常用注解?

@service    @controller     @repository    @ Component

Spring 中@Resource与@Autowire注解应用时有什么异同点

Spring 中@Value注解有什么作用.

       

1.1.6.3.5 留言管理(MVC)

-----------------------------------------

1.1.6.4 1.MVC 概述

一种架构模式(采用分层设计思想)

M:Model (业务层对象)

C:Controller(控制层对象)

V:View (视图层对象)

生活中的MVC:菜单+服务员+厨师

程序中的MVC:JSP+Servlet+JavaBean

1.1.6.5 2.基于MVC架构实现留言管理模块

1)数据库中表的设计(数据库使用test)

create table msg(

   id int primary key auto_increment,

   content varchar(300) not null

)engine=InnoDB charset utf8;

2)创建数据库表对应的实体类型

public class Message{

   private int id;

   private String content;

   //set,get方法

}

3)构建model中的数据持久层逻辑对象

3.1)构建一个MessageDao接口

3.2)构建MessageDao接口实现类

4)构建Model中业务层逻辑对象

4.1)构建一个MessageService接口

4.2)构建一个MessageService接口的实现类

5)构建一个控制层逻辑对象

a)构建MessageController继承HttpServlet

b)配置MessageController.

6)构建一个msg.jsp创建一个表单,

提交表单时访问httpservlet.

-----------------------------------------

1.1.6.6 3.案例:

1.1.6.6.1  3.1数据库准备
1.1.6.6.2 3.2创建数据库表对应的实体类型
1.1.6.6.3 3.3构建model中的数据持久层逻辑对象
1.1.6.6.3.1 3.3.1构建一个MessageDao接口
1.1.6.6.3.2 3.3.2构建MessageDao接口实现类
1.1.6.6.4 3.4构建Model中业务层逻辑对象
1.1.6.6.4.1 3.4.1构建一个MessageService接口
1.1.6.6.4.2 3.4.2构建一个MessageService接口的实现类
1.1.6.6.5 3.5构建一个控制层逻辑对象
1.1.6.6.5.1 3.5.1构建MessageController继承HttpServlet
1.1.6.6.5.2 3.5.2配置MessageController.  Web.Xml文件
1.1.6.6.6 3.6构建一个msg.jsp创建一个表单, 提交表单时访问httpservlet.

1.1.7 Spring AOP 简介

1.1.7.1 Spring AOP 概述

1.1.7.1.1 Spring AOP 是什么

         AOP 是Aspect-Oriented Programming的缩写,被译成面向切面编程。面向切面编程是通过预编译的方式或运行时动态代理方法实现业务中的一个扩展功能。可以说是OOP(Object-Oriented Programing,面向对象编程)的补充和完善。实际项目中我们通常会将OOP与AOP结合使用。Spring中依托于@AspectJ框架,底层通过动态代理方式实现了面向切面编程,我们称之为Spring的AOP。 

Spring  框架核心功能模块之一

Spring  中的面向切面编程(Aspect Oriented Programming)

Spring  中业务对象的横切面

提示:AOP把软件系统分为两个部分:核心关注点和横切关注点。

业务处理的主要流程是核心关注点,非核心流程(也就是扩展业务)是横切关注点。

1.1.7.1.2 Spring AOP 应用特点

Spring AOP基于OCP原则,在不改变原有功能代码的基础上扩展新的功能,这样可以很好的实现业务代码解耦,提高代码的可重用性,以简化代码编写,提高开发效率。

1.1.7.1.3 Spring AOP 应用场景

作为面向对象编程的一种补充,广泛应用于处理一些具有横切性质的系统级服务,如

系统日志处理,系统事务处理,系统安全验证,系统数据缓存等。

例如:

1.1.7.2 Spring AOP 核心概念及原理

1.1.7.2.1 Spring AOP 核心概念

切面(aspect): 横切面对象(扩展业务类对象),一般为一个具体类对象(可以借助@Aspect声明)

连接点(joinpoint):程序执行过程中某个特定的点,一般指被拦截到的方法

切入点(pointcut):对连接点拦截内容(核心业务的方法)的一种定义

通知(Advice):在切面的某个特定连接点上执行的动作,例如before,after等。

很晦涩难懂,多做例子,做完就会清晰。先可以按白话去理解。

1.1.7.2.2 Spring AOP 应用原理

Spring AOP 底层依赖于代理机制,在对象运行时可以动态织入新的扩展功能。

Spring创建代理的规则:

1、默认使用Java动态代理(JDK动态代理)来创建AOP代理,这样就可以为任何接口实例创建代理了

2、当需要代理的类不是代理接口的时候,Spring会切换为使用CGLIB库来创建代理对象(spring框架,自带CGLIB夹包)。

1.1.8 Spring AOP 编程实现

1.1.8.1 Spring AOP 编程基本关注点

纵观SpringAOP编程,程序员只需要参与三个部分:

定义普通业务切面(Aspect)

定义切入点(Pointcut),一个切入点可能横切多个业务组件

定义增强处理(Advice),增强处理就是在AOP框架为普通业务组件织入的处理动作(从属于上讲就是通知) 

对于以上AOP编程的三个部分最关键就是定义切入点和定义增强处理,一旦定义了合适的切入点和增强处理

1.1.8.2 Spring AOP 编程基本步骤

创建maven项目并添加依赖

添加spring配置文件启用注解配置

创建核心业务接口及实现类

创建业务切面类,定义切入点及增强处理

编写测试类进行测试

               

1.1.8.3 Spring AOP 项目创建及依赖

创建maven的java或web项目,并添加AOP依赖在pom.xml文件中注入。

<dependencies>

<dependency>

<groupId>org.springframework</groupId>

<artifactId>spring-context</artifactId>

<version>4.3.9.RELEASE</version>

</dependency>

<!-- 1.8.5有问题 -->

<dependency>

<groupId>org.aspectj</groupId>

<artifactId>aspectjrt</artifactId>

<version>1.8.9</version>

</dependency>

<dependency>

<groupId>org.aspectj</groupId>

<artifactId>aspectjweaver</artifactId>

<version>1.8.9</version>

</dependency>

</dependencies>

添加单元测试依赖

<dependency>

<groupId>Junit</groupId>

<artifactId>junit</artifactId>

<version>4.7</version>

</dependency>

1.1.8.4 Spring AOP 基于XML方式实现

1.1.8.4.1 Spring AOP 核心业务组件定义

定义HelloService接口

public interface HelloService {

 void sayHello(String msg);

}

定义SayHelloServiceImpl实现类实现SayHelloService接口

@Service

public class HelloServiceImpl implements HelloService {

public void sayHello(String msg) {

System.out.println("hello "+msg);

}

}

1.1.8.4.2 Spring AOP 切面业务组件定义

@Component

public class LoggingAspect {

public void beforeMethod() {

System.out.println("beforeMethod");

}

public void afterMethod() {

System.out.println("afterMethod");

}

}

1.1.8.4.3 Spring AOP 基于xml方式配置

<?xml version="1.0" encoding="UTF-8"?>

<beans 

    xmlns="http://www.springframework.org/schema/beans" 

    xmlns:p="http://www.springframework.org/schema/p"

    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 

    xmlns:context="http://www.springframework.org/schema/context"

    xmlns:tx="http://www.springframework.org/schema/tx"

    xmlns:aop="http://www.springframework.org/schema/aop" 

    xmlns:mvc="http://www.springframework.org/schema/mvc"

    xmlns:util="http://www.springframework.org/schema/util"

    xmlns:jpa="http://www.springframework.org/schema/data/jpa"

    xsi:schemaLocation="  

       http://www.springframework.org/schema/beans   

       http://www.springframework.org/schema/beans/spring-beans-4.3.xsd  

       http://www.springframework.org/schema/mvc   

       http://www.springframework.org/schema/mvc/spring-mvc-4.3.xsd   

       http://www.springframework.org/schema/tx   

       http://www.springframework.org/schema/tx/spring-tx-4.3.xsd   

       http://www.springframework.org/schema/aop

       http://www.springframework.org/schema/aop/spring-aop-4.3.xsd

       http://www.springframework.org/schema/util

       http://www.springframework.org/schema/util/spring-util-4.3.xsd

       http://www.springframework.org/schema/data/jpa

       http://www.springframework.org/schema/data/jpa/spring-jpa-1.3.xsd

       http://www.springframework.org/schema/context

       http://www.springframework.org/schema/context/spring-context-4.3.xsd">

    <!-- 自动扫描该包   -->

    <context:component-scan base-package="com.company.spring" />

    <!-- AOP配置 -->

<aop:config>

<!-- 切点表达式进行匹配判断,within对哪个类进行处理 -->

<aop:pointcut 

     expression="within(com.company.spring.service.HelloServiceImpl)"

     id="pc"/>

<!-- 切面触发的执行类 -->

<aop:aspect ref="loggingAspect">

<!-- 切面触发的执行类的before方法 -->

<aop:before method="beforeMethod" pointcut-ref="pc"/>

<aop:after method="afterMethod" pointcut-ref="pc"/>

</aop:aspect>

</aop:config>

</beans>

基于xml方式实现AOP的配置,代码的侵入比较小,但灵活性不足。

1.1.8.4.4 Spring AOP 测试

ClassPathXmlApplicationContext ctx=

new ClassPathXmlApplicationContext("aop.xml");

HelloService helloService=

ctx.getBean("helloServiceImpl",HelloService.class);

helloService.sayHello("hello");

ctx.close();

1.1.8.5 Spring AOP 基于注解方式实现

1.1.8.5.1 Spring AOP 项目创建

创建maven java 项目

 

添加AOP依赖

  <dependencies>

  <dependency>

<groupId>org.springframework</groupId>

<artifactId>spring-context</artifactId>

<version>4.3.9.RELEASE</version>

</dependency>

    <dependency>

<groupId>org.aspectj</groupId>

<artifactId>aspectjrt</artifactId>

<version>1.8.9</version>

</dependency>

    <dependency>

<groupId>org.aspectj</groupId>

<artifactId>aspectjweaver</artifactId>

           <version>1.8.9</version>

</dependency>

    <dependency>

<groupId>Junit</groupId>

<artifactId>junit</artifactId>

<version>4.7</version>

</dependency>

  </dependencies>

编写核心业务组件(MessageService接口与MessageServiceImpl实现类)

package com.project;

public interface MessageService {

    void sendMsg(String msg);

}

package com.project.impl;

import org.springframework.stereotype.Service;

import com.project.MessageService;

@Service

public class MessageServiceImpl implements MessageService {

public void sendMsg(String msg) {

      System.out.println("**********"+msg+"***********");

}

}

编写扩展业务组件(TxManager)

package com.aspect;

import org.aspectj.lang.annotation.After;

import org.aspectj.lang.annotation.Aspect;

import org.aspectj.lang.annotation.Before;

import org.aspectj.lang.annotation.Pointcut;

import org.springframework.stereotype.Component;

@Component

@Aspect

public class TxManager {

/**定义切入点(可以简化对表达式的引用,可以在通知上直接用@Pointcut注解修饰的方法签名)*/

@Pointcut("within(com.project.impl.MessageServiceImpl)")

public void pointCut(){}

    @Before("pointCut()")

private void beginTx() {

      System.out.println("start transaction*************");

}

    @After("pointCut()")

private void endTx() {

      System.out.println("end transaction***************");

}

}

配置核心业务组件以及扩展业务组件(组件扫描及AOP注解应用)

<context:component-scan base-package="com"/>

    <!-- 启用AOPbean创建代理对象 -->

    <aop:aspectj-autoproxy/>

编写单元测试(junit)

package cn.project;

import org.junit.After;

import org.junit.Before;

import org.junit.Test;

import org.springframework.context.support.ClassPathXmlApplicationContext;

import com.project.MessageService;

public class TestTxService {

private ClassPathXmlApplicationContext ctx;

@Before

    public void init(){ctx=new ClassPathXmlApplicationContext("beans.xml");}

@After

    public void  destroy(){ctx.close();}

@Test

public void testSaveUserName(){

MessageService  proxy=ctx.getBean("messageServiceImpl",MessageService.class);

proxy.sendMsg("小谢童鞋");

}

}

 

1.1.8.5.2 Spring AOP 核心业务组件定义

定义MessageService接口

public interface MessageService {

 void sendMsg(String msg);

}

定义MessageServiceImpl实现类实现SayHelloService接口

@Service

public class MessageServiceImpl implements MessageService{

public void sendMsg(String msg) {

System.out.println(msg);

}

}

1.1.8.5.3 Spring AOP 切面业务组件定义

@Component

@Aspect

public class TxManager {

@Pointcut("within(com.company.spring.service.MessageServiceImpl)")

public void pointCut() {}

@Before("pointCut()")

public void beginTx() {

System.out.println("begin transaction");

}

@After("pointCut()")

public void endTx() {

System.out.println("end transaction");

}

}

其中:

@Component 表示此组件由Spring对象管理

@Aspect 用于定义切面(对切入点和通知的封装)

@Pointcut 用于定义切入点(用于织入扩展功能的点)

@Before 用于定义前置通知(业务方法之前执行)

@After 用于定义最终通知(业务方法执行完成以后执行)

1.1.8.5.4 Spring AOP 配置文件启用AOP功能

<?xml version="1.0" encoding="UTF-8"?>

<beans default-lazy-init="true"

    xmlns="http://www.springframework.org/schema/beans" 

    xmlns:p="http://www.springframework.org/schema/p"

    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 

    xmlns:context="http://www.springframework.org/schema/context"

    xmlns:tx="http://www.springframework.org/schema/tx"

    xmlns:aop="http://www.springframework.org/schema/aop"

    xmlns:mvc="http://www.springframework.org/schema/mvc"

    xmlns:util="http://www.springframework.org/schema/util"

    xmlns:jpa="http://www.springframework.org/schema/data/jpa"

    xsi:schemaLocation="  

       http://www.springframework.org/schema/beans   

       http://www.springframework.org/schema/beans/spring-beans-4.3.xsd  

       http://www.springframework.org/schema/mvc   

       http://www.springframework.org/schema/mvc/spring-mvc-4.3.xsd   

       http://www.springframework.org/schema/tx   

       http://www.springframework.org/schema/tx/spring-tx-4.3.xsd   

       http://www.springframework.org/schema/aop

       http://www.springframework.org/schema/aop/spring-aop-4.3.xsd

       http://www.springframework.org/schema/util

       http://www.springframework.org/schema/util/spring-util-4.3.xsd

       http://www.springframework.org/schema/data/jpa

       http://www.springframework.org/schema/data/jpa/spring-jpa-1.3.xsd

       http://www.springframework.org/schema/context

       http://www.springframework.org/schema/context/spring-context-4.3.xsd">

    <!-- 自动扫描该包 -->

    <context:component-scan base-package="cn.tedu.aop" />

    <!-- 使aspectj注解生效,自动为目标对象生成代理对象 -->

    <aop:aspectj-autoproxy/>

    

</beans>

 

说明:基于AOP注解的方式配置AOP时,因为注解要应用在类或方法的上面,所以会由一定的侵入性。但这种侵入性属于声明式侵入性,所以范畴同样也是属于弱耦合范围之内(强耦合一般是在业务代码内部的直接耦合)。

1.1.8.5.5 Spring AOP 测试

ClassPathXmlApplicationContext ctx=

new ClassPathXmlApplicationContext("aop.xml");

MessageService msgService=

ctx.getBean("messageServiceImpl",MessageService.class);

msgService.sendMsg("hello");

ctx.close();

1.1.9 Spring AOP 编程增强

1.1.9.1 Spring AOP 切入点表达式增强

Spring中通过切入点表达式定义具体切入点,其常用AOP切入点表达式定义及说明:

 

指示符

作用

 

bean

用于匹配指定类型内的方法

 

within

用于匹配指定的一个或多个类型内的方法

 

execution

用于匹配表达式指定的所有方法执行的连接点

 切入点:织入增强业务的那个点(某个或多个核心业务方法的集合)

1.1.9.1.1 bean 表达式

bean应用于类级别,实现粗粒度的控制:

bean(userServiceImpl))

指定一个类

bean(*Service)

指定所有的后缀为service的类

bean的时候需要指定一个类或者多个相同的类的后缀字母加*,如上。不需要写类的全名(也就是包名加类名。Bean注意是根据bean容器的ID属性或者name属性查找对象。)

例如:

@Aspect

@Component

public class LoggingAspect {

@Before("bean(xxxService)")  //@Before("bean(*Service)")

public void beforeMethod() {

System.out.println("beforeMethod");

}

@After("bean(xxxService)")

public void afterMethod() {

System.out.println("afterMethod");

}

}

说明:使用bean表达式,通常是要进行粗粒度的切入点进行定义。

1.1.9.1.2 within 表达式

within应用于类级别,实现粗粒度的控制,在指定相关类时,需要指定到具体包名+类名。

within(aop.service.UserServiceImpl))

指定类,只能指定一个类

within(aop.service.*))

只包括当前目录下的类

within(aop.service..*))

指定当前目录包含所有子目录中的类

 

@Aspect

@Component

public class LoggingAspect {

@Before("within(aop.XxxService.*)")

public void beforeMethod() {

System.out.println("beforeMethod");

}

@After("within(aop.XxxService.*)")

public void afterMethod() {

System.out.println("afterMethod");

}

}

说明:通过within表达式进行切入点定义时,也是类级别的定义(粒度比较粗),针对的是类中的所有方法进行控制。

1.1.9.1.3 execution 表达式

execution方法级别,细粒度的控制:

语法:execution(返回值类型 包名.类名.方法名(参数列表))

execution(void aop.service.UserServiceImpl.addUser())

匹配方法

execution(void aop.service.PersonServiceImpl.addUser(String))

方法参数必须为字符串

execution(* aop.service..*.*(..))

万能配置

例如:

@Aspect

@Component

public class TimingAspect {

@Before("execution(* cn.tedu..*Service.*(..))")

public void timeBeforeMethod(JoinPoint point) {

String method=point.getSignature().getName();

System.out.println("timeBeforeMethod:"+method);

}

/**方法切入点 (execution: 执行)*/

@After("execution(* cn.tedu..*Service.update(..))")

public void timeAfterMethod(JoinPoint point) {

String method=point.getSignature().getName();//获得方法的签名Signature(方法名+参数列表),再由方法签名获得方法的名字。

Object arg=point.getArgs()[0];  //获得方法的参数列表Args中的第一个元素[0]

 

System.out.println("timeAfterMethod:"+method+"("+arg+")");

}

}

说明:以上切面中定义的通知方法中的joinPoint表示一个连接点。而这个连接点一般封装了核心业务中具体的某个业务方法信息,简单点就是代表了核心业务类中的具体的某个业务方法。

   切入点是符合条件的方法的集合。

   连接点是符合条件的一个方法。

1.1.9.2 Spring AOP 配置增强

1.1.9.2.1 Spring AOP 概念增强

切面(Aspect):对应扩展业务对象(横切关注点对象)

通知(Advice):切面中定义的扩展业务操作(一般为一个方法)

前置通知(@Before):

目标业务方法执行之前(也就是连接点之前)要添加的业务操作,前置通知不会影响核心业务(连接点)的执行,除非此处抛出异常。相等于try里面的第一条代码。

后置通知(@AfterReturning):

目标业务方法正常执行之后要添加的业务操作,此通知只会在目标业务正常执行完,没有异常的时候才会执行,如果连接点抛出异常,则不会执行,所有也称之为正常返回通知。相等于try里面的return。

异常通知(@AfterThrowing):

目标业务方法出现异常之后要执行的操作,核心业务正常执行,没有出现异常的时候,此通知是不会执行的,只有核心业务出现异常的时候才会执行。相等于catch里面的内容。

最终操作(@After):

目标方法最终要执行的业务,无论是否有异常都要执行的通知,也就是说不管连接点(核心业务方法)是正常执行完,还是出现异常没有执行,此通知都会执行。相等于finally里面的内容。

环绕通知(@Around):

目标方法执行之前和执行之后都要添加业务时可采用的一种通知,这是最强大的通知类型,需要负责决定是否继续执行核心业务方法,如果需要继续执行核心业务方法,就需要在参数类列中添加(ProceedingJoinPoint  joinPoint),然后在方法里面用joinPoint变量的proceed()方法调用核心业务方法。

切入点:切入扩展业务的那些具体业务方法集合,也就是多个核心业务的集合。

连接点:一般对应某个具体的目标业务方法,程序中用JointPoint类型对象表示.

说明:以上术语需要在案例中进行强化理解会更好.也可通过如下结构加强理解

Try{

   前置通知(before)

   目标业务(连接点)

   后置通知(afterReturn)

}catch(Exception e){

   异常通知(afterThrowing)

}finally{

   最终通知(after)

}

1.1.9.2.2 Spring AOP xml 方式配置增强

切入点及前置通知,后置通知,返回通知,异常通知,环绕通知的配置

 

<aop:config>

<aop:pointcut id="pc"expression="execution(*

com.company.spring.service..*.*(..))" >

<aop:aspect ref="loggingAspect">

<aop:before method="beforeMethod" pointcut-ref="pc"/>//前置通知

<aop:after method="afterMethod" pointcut-ref="pc"/>//最终通知

<aop:after-returning method="returnMethod"   //后置通知

 pointcut-ref="pc"/>

<aop:after-throwing method="throwMethod"   //异常通知

pointcut-ref="pc"/>

</aop:aspect>

</aop:config>

后置通知after-returning为方法执行之后要执行的一种通知.

异常通知 after-throwing为方法出现异常以后要执行的一种通知

切入点及环绕通知的配置

<aop:config>

<aop:pointcut id="pc"expression="execution(*

com.company.spring.service..*.*(..))" >

<aop:aspect ref="loggingAspect">

<aop:around method="aroundMethod" pointcut-ref="pc"/>  //环绕通知

</aop:aspect>

</aop:config>

说明:一般环绕通知会独自使用,是在目标方法执行前后都要执行的一种通知类型.

1.1.9.2.3 Spring AOP 注解方式配置增强

切入点及前置通知,最终通知,返回通知,异常通知,环绕通知的配置

@Aspect

@Component

public class LoggingAspect {

@Before("bean(xxxService)")  //前置通知    1

public void beforeMethod() {

System.out.println("beforeMethod");

}

@After("bean(xxxService)") //最终通知     2

public void afterMethod() {

System.out.println("afterMethod");

}

@AfterReturning(pointcut="bean(xxxService)",returning="result") //后置(返回)通知     3

public void afterReturningMethod(Object result) {

System.out.println("afterReturnMethod.result="+result);

}

@AfterThrowing("bean(xxxService)")// 异常通知  3

public void afterThrowingMethod() {

System.out.println("afterThrowingMethod");

}

}

切入点及环绕通知的配置

@Component

@Aspect

public class TxManager {

@Pointcut("execution(com.company.spring.service..*.*(..))")

public void pointCut() {}

@Around("pointCut()")  //环绕通知

public Object around(ProceedingJoinPoint joinPoint)

throws Throwable{

System.out.println("环绕通知aroundAdvice");

try {

           //前置通知,可以写在此处。

System.out.println("方法开始时间start.time:"+System.nanoTime());

//执行目标方法(核心业务方法),连接点。如果此处不执行核心业务方法,执行的时候就只会有扩展的业务,没有核心的业务。

Object result=joinPoint.proceed();

//返回目标方法执行结果,后置通知可以写在此处。

System.out.println("方法结束时间end.time:"+System.nanoTime());

return result; //返回结果的时候,必须先执行最终通知才会执行返回通知。

} catch (Throwable e) {

e.printStackTrace();

           //异常通知可以写在此处

System.out.println("异常通知时间EXP.time:"+System.nanoTime());

throw e;

}finally{

           //最终通知可以写在此处。

System.out.println("最后通知时间finally.time:"+System.nanoTime());

}}

}

提示:可以将proceedingJointPoint理解为一个特殊的连接点,通过连接点访问Proceed方法时会自动执行目标业务对象方法.

1.1.9.3 Spring AOP 切面顺序配置

当业务系统中存在多个切面时,哪个切面优先执行,哪个切面要延后执行,我们可以通过配置切面 执行顺序实现,序号小的会优先执行。

1.1.9.3.1 基于xml方式配置

 

  <!-- 自动扫描该包 -->

    <context:component-scan base-package="com.company.spring" />

    <!-- AOP配置 -->

<aop:config>

<aop:pointcut id="pc"

     expression="execution(*

com.company.spring.service..*.*(..))"/>

<aop:aspect ref="loggingAspect" order="1">

<aop:around method="aroundMethod" pointcut-ref="pc"/>

</aop:aspect>

<aop:aspect ref="txManager" order="2">

    <aop:around method="aroundMethod" pointcut-ref="pc"/>

</aop:aspect>

</aop:config>

1.1.9.3.2 基于注解方式配置

基于注解方式配置时,需要借助@Order注解描述横切关注点(就是那个横切面),通过

@Order注解中的序号声明切面顺序.

例如:事务切面

@Order(1)

@Aspect

@Component

public class TxManager {

@Pointcut("execution(* com.company.spring.service..*.*(..))")

public void pointCut() {}

@Around("pointCut()")

public Object around(ProceedingJoinPoint joinPoint)

throws Throwable{

System.out.println("事务开始");

Object result = joinPoint.proceed();

System.out.println("事务结束");

return result;

}

}

例如:日志切面及顺序定义

@Order(2)

@Aspect

@Component

public class LoggingAspect {

@Pointcut("execution(* com.company.spring.service..*.(..))")

public void pointCut() {}

@Before("pointCut()")

public void beforeMethod() {

System.out.println("beforeMethod");

}

@Before("pointCut()")

public void afterMethod() {

System.out.println("afterMethod");

}

}

1.1.9.4 Spring AOP 应用场景实践

权限检测、日志处理、事物处理。等扩展业务。

1.1.9.4.1 Pom.xml依赖设置

1.创建maven的Java项目,设置pom.xml文件的依赖属性

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">

  <modelVersion>4.0.0</modelVersion>

  <groupId>cn.tedu</groupId>

  <artifactId>CGB-SPRING-DAY08-AOP-01</artifactId>

  <version>0.0.1-SNAPSHOT</version>

  <dependencies>

    <dependency>

     <groupId>org.springframework</groupId>

     <artifactId>spring-context</artifactId>

     <version>4.3.9.RELEASE</version>

     </dependency>

     <!-- spring底层AOP的实现,需要依赖

      -->

     <dependency>

     <groupId>org.aspectj</groupId>

     <artifactId>aspectjrt</artifactId>

     <version>1.8.9</version>

     </dependency>

     

      <dependency>

      <groupId>org.aspectj</groupId>

      <artifactId>aspectjweaver</artifactId>

      <version>1.8.9</version>

      </dependency>

       <!-- 引入单元测试 -->

       <dependency>

   <groupId>junit</groupId>

   <artifactId>junit</artifactId>

   <version>4.7</version>

     </dependency>

  </dependencies>

</project>

1.1.9.4.2 设置资源配置文件applicationCongif.xml文件

如果测试需要用ClassPathXmlApplicationContext类来初始化spring容器则需要用到xml文件配置,如果用AnnotationConfigApplicationContext类来设置spring容器的初始化,就需要定义一个AppConfig.java来配置组件扫描。

<?xml version="1.0" encoding="UTF-8"?>

<beans 

    default-lazy-init="true"

    xmlns="http://www.springframework.org/schema/beans" 

    xmlns:p="http://www.springframework.org/schema/p"

    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 

    xmlns:context="http://www.springframework.org/schema/context"

    xmlns:tx="http://www.springframework.org/schema/tx"

    xmlns:aop="http://www.springframework.org/schema/aop" 

    xmlns:mvc="http://www.springframework.org/schema/mvc"

    xmlns:util="http://www.springframework.org/schema/util"

    xmlns:jpa="http://www.springframework.org/schema/data/jpa"

    xsi:schemaLocation="  

       http://www.springframework.org/schema/beans   

       http://www.springframework.org/schema/beans/spring-beans-4.3.xsd  

       http://www.springframework.org/schema/mvc   

       http://www.springframework.org/schema/mvc/spring-mvc-4.3.xsd   

       http://www.springframework.org/schema/tx   

       http://www.springframework.org/schema/tx/spring-tx-4.3.xsd   

       http://www.springframework.org/schema/aop

       http://www.springframework.org/schema/aop/spring-aop-4.3.xsd

       http://www.springframework.org/schema/util

       http://www.springframework.org/schema/util/spring-util-4.3.xsd

       http://www.springframework.org/schema/data/jpa

       http://www.springframework.org/schema/data/jpa/spring-jpa-1.3.xsd

       http://www.springframework.org/schema/context

       http://www.springframework.org/schema/context/spring-context-4.3.xsd"> 

       <!-- spring

            @Service     一般用于描述业务层对象(model

            @Component   一般用于修饰其他组件

            @Controller  一般用于描述控制层对象(controller

            @Repository  一般用于修饰其他组件对象

             -->

       <!-- 配置组件扫描(将对象包中指定的类交个spring来管理) -->

    <context:component-scan base-package="com.project"/>  

      <!-- 基于xml配置aop 自动代理 -->

      <aop:aspectj-autoproxy/>

        

</beans>

1.1.9.4.3 规则接口

package com.project.service;

/**

 * 核心业务需要实现的规则接口

 */

public interface UserService {

  void  saveUserName(String name);

  String updateName(String name);

  Object findUserById(Integer id);

}

1.1.9.4.4 定义spring基于注解管理对象的类

package com.project;

import org.springframework.context.annotation.ComponentScan;

import org.springframework.context.annotation.EnableAspectJAutoProxy;

/**基于注解方式定义ApplicationContext

 * @ComponentScan 定义组件扫描

 * @EnableAspectJAutoProxy 启用AOP代理

 * */

@ComponentScan("com.project")  //定义组件扫描那个包?

@EnableAspectJAutoProxy   //定义自动切面代码(授权切面自动代理)

public class AppConfig {

}

1.1.9.4.5 核心业务(实现类)

package com.project.service.impl;

import org.springframework.stereotype.Service;

import com.project.annotation.PMT;

import com.project.annotation.Permission;

import com.project.service.UserService;

/**

 * 核心业务类

 */

@Service  //spring注解,把此类的对象交给spring容量来管理

@PMT  //类注解 (权限注解)

public class UserServiceImpl implements UserService {

public void saveUserName(String name) {

if(name==null){

System.out.println("null是什么????????");

return;

}

String userName="账户:"+name;

System.out.println(userName);

}

@Permission

public String updateName(String name) {

if(name!=null){

String userName="修改账户为:"+name;

return userName;

}

return "没有此人*****请重新输入~~~";

}

   public Object findUserById(Integer id) {

   if(id==null){

return "null是什么????????";

}

   if(id!=0){

   return  "账户:小谢童鞋"+"\n"+"密码:******";

   }

return "没有此人********";

}

}

1.1.9.4.6 定义类注解与方法注解

/**类注解,一般应用在类的上面*/

package com.project.annotation;

import java.lang.annotation.ElementType;

import java.lang.annotation.Retention;

import java.lang.annotation.RetentionPolicy;

import java.lang.annotation.Target;

import org.springframework.stereotype.Component;

@Target(ElementType.TYPE)  //作用在类的上面

@Retention(RetentionPolicy.RUNTIME)  //在程序运行的时候启作用

public @interface PMT {

}

/**方法注解,一般应用方法的上面*/

package com.project.annotation;

import java.lang.annotation.ElementType;

import java.lang.annotation.Retention;

import java.lang.annotation.RetentionPolicy;

import java.lang.annotation.Target;

@Target(value=ElementType.METHOD)//作用范围

@Retention(RetentionPolicy.RUNTIME)//作用场景(也就是设么时候顶用)

public @interface Permission {

}

1.1.9.4.7 切面1(权限检测)

package com.project.aspect;

import java.lang.reflect.Method;

import java.security.Provider.Service;

import org.aspectj.lang.JoinPoint;

import org.aspectj.lang.Signature;

import org.aspectj.lang.annotation.Aspect;

import org.aspectj.lang.annotation.Before;

import org.aspectj.lang.annotation.Pointcut;

import org.springframework.core.annotation.Order;

import org.springframework.stereotype.Component;

import com.project.annotation.PMT;

import com.project.annotation.Permission;

/**执行权限检测的切面*/

@Component//组件

@Aspect//切面

@Order(1)  //扩展业务的顺序

public class PermissionAdpect {

   @Pointcut("execution(* com.project..*.*(..))")  //切点

   public void pointCut(){}   //给切点声明一个别名

   @Before("pointCut()")    //前置通知

   public void  checkPermission(JoinPoint jp){// JoinPoint 连接点

   System.out.println("***********准备进行权限检测************");

   //1.获得连接点(核心业务方法)的方法签名(方法名和参数类别)

   Signature nameArgs=jp.getSignature();

   //2.获得核心业务的方法名字

   String methodName=nameArgs.getName();

   //3.获得核心业务的实际参数对应的值以及参数类型数组

   Object[] args=jp.getArgs();

   //4.根据方法名和参数列表获取对应的方法对象

   //4.1获得方法对应的目标对象(核心业务)的类对象

   Class<?> targetCls=jp.getTarget().getClass();

   Object obj=jp.getTarget();//获得核心业务的实例对象

              //System.out.println(obj);

   //4.2获得方法对象(Method)

   //4.2.1先判断获得参数是否为空。

   if(args!=null){

   //4.2.2获得参数列表的类对象

   Class<?>[]  parameterTypes=new Class<?>[args.length];

   for (int i = 0; i < args.length; i++) {

   if(args[i]==null){

   return;

   }

parameterTypes[i]=args[i].getClass();//把参数类型的字节码对象赋值给方法需要的参数数组

}

   Method method=null;  

   try {

   method=targetCls.getDeclaredMethod(methodName, parameterTypes);

} catch (NoSuchMethodException e) {

e.printStackTrace();

} catch (SecurityException e) {

e.printStackTrace();

}

   //5.获取方法上对象上的 注解,判断方法上是否使用了Permission注解

   if(method.isAnnotationPresent(Permission.class)){

   System.out.println("执行"+methodName+"方法需要进行权限检测");

   }else{

   System.out.println("执行"+methodName+"方法不需要进行权限检测");

   }

   if(targetCls.isAnnotationPresent(PMT.class)){

   System.out.println("执行"+methodName+"方法类上面有注解,需要进行权限检测");

   }else{

   System.out.println("执行"+methodName+"方法类没上面有注解,不需要进行权限检测");

   }

   }

   }

   

}

1.1.9.4.8 切面2(日志处理)

package com.project.aspect;

import java.util.logging.Logger;

import java.util.logging.LoggingMXBean;

import org.aspectj.lang.annotation.After;

import org.aspectj.lang.annotation.Around;

import org.aspectj.lang.annotation.Aspect;

import org.aspectj.lang.annotation.Before;

import org.aspectj.lang.annotation.Pointcut;

import org.springframework.core.annotation.Order;

import org.springframework.format.datetime.joda.LocalDateTimeParser;

import org.springframework.stereotype.Component;

/**切面类:包含切入点PointCut和通知advice*/

@Component  //组件,加此注解,此类对象就会被spring容器管理

@Aspect  //切面注解

@Order(2) //切面执行顺序注解

public class LoggingAspect {

Logger log=Logger.getLogger("测试");

@Before("bean(*Impl)")

   public  void   beforeLogging(){

  log.info("************************************************");

   }

@After("bean(*Impl)")

   public void afterLogging(){

   log.info("~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~");

   }

}

1.1.9.4.9 切面3(环绕通知)

package com.project.aspect;

import org.aspectj.lang.ProceedingJoinPoint;

import org.aspectj.lang.annotation.Around;

import org.aspectj.lang.annotation.Aspect;

/**切面:环绕通知*/

import org.springframework.core.annotation.Order;

import org.springframework.stereotype.Component;

@Order(3)

@Component

@Aspect

public class TimeAspect {

  //环绕通知

//@Around("execution(* com.project.service.impl.*.*(..))")//切点

     public Object  aroundAdvice(ProceedingJoinPoint pj){

System.out.println("************测试环绕通知***************");

try {

System.out.println("*********环绕前置通知*********");

Object result=pj.proceed();  //执行核心业务方法

System.out.println("***********环绕后置通知************");

return result;

} catch (Throwable e) {

e.printStackTrace();

System.out.println("**************环绕异常通知****************");

}finally{

System.out.println("*****************环绕最终通知*****************");

}

      return  null;

     }

}

1.1.9.4.10 切面44大通知)

package com.project.aspect;

import org.aspectj.lang.annotation.After;

import org.aspectj.lang.annotation.AfterReturning;

import org.aspectj.lang.annotation.AfterThrowing;

import org.aspectj.lang.annotation.Aspect;

import org.aspectj.lang.annotation.Before;

import org.aspectj.lang.annotation.Pointcut;

import org.springframework.core.annotation.Order;

import org.springframework.stereotype.Component;

@Order(4)

@Component//组件,spring容器管理

@Aspect//切面

public class TxManagerAspect {

@Pointcut("execution(* com.project..*.*(..))")  //切点

public void pointCut(){}  //切点的别名

@Before("pointCut()")  //前置通知

   public void beforeAdvice(){  //1.先执行前置通知

   System.out.println("TxManagerAspect.beforeAdvice前置通知");

   }

@AfterReturning("pointCut()")

public void afterReturning(){//3.正常执行没有异常的时候执行,后置通知(返回通知)

System.out.println("TxManagerAspect.afterReturningAdvice后置通知");

}

@AfterThrowing("pointCut()")

public void afterThrowing(){//4.非正常执行,有异常的时候执行。异常通知。

System.out.println("TxManagerAspect.afterThrowingAdvice异常通知");

}

@After("pointCut()")

public void afterAdvice(){//2.再执行最终通知

System.out.println("TxManagerAspect.afterAdvice最终通知");

}

//1.正常执行: 前置通知>>核心业务>>最终通知>>后置通知(返回通知)>>返回方法执行的结果

//2.异常执行: 前置通知>>核心业务-出现异常>>最终通知>>异常通知-(方法没有结果)

}

1.1.9.4.11 测试(单元测试)

package com.project.test;

import org.junit.After;

import org.junit.Before;

import org.junit.Test;

import org.springframework.context.annotation.AnnotationConfigApplicationContext;

import com.project.AppConfig;

import com.project.service.UserService;

/**测试类,单元测试juint,*/

public class TestUserService {

private AnnotationConfigApplicationContext ctx;

//private ClassPathXmlApplicationContext  ctx; //需要借助xml注解

private UserService us;

@Before

    public void init() {

ctx = new AnnotationConfigApplicationContext(AppConfig.class);

us=ctx.getBean("userServiceImpl",UserService.class);

}

@Test

public void testUpdateName(){

String name=us.updateName(null);

System.out.println(name);

}

@Test

public void testSaveUserName(){

us.saveUserName("宝宝");

}

@Test

public void  testFindUserName(){

System.out.println(us.findUserById(9));

}

@After

    public void destroy() {

ctx.close();

}

}

1.1.9.5 基于AOP环绕通知实现简单权限控制

尝试自己实现

package com.project.aspect;

import java.lang.reflect.Method;

import java.util.Scanner;

import org.aspectj.lang.ProceedingJoinPoint;

import org.aspectj.lang.annotation.Around;

import org.aspectj.lang.annotation.Aspect;

import org.aspectj.lang.annotation.Pointcut;

import org.springframework.core.annotation.Order;

import org.springframework.stereotype.Component;

import com.project.annotation.Permission;

/**基于AOP环绕配置检测权限*/

@Component

@Aspect

@Order(5)

public class TimePermissionAspect {

   private Class<?>[] parameterTypes;

@Pointcut("bean(*Impl)")

   public void pointCut(){}   //切点

   

   @Around("pointCut()")

   public Object aroungAspect(ProceedingJoinPoint  joinPoint){

   Object result=null;

   String methodName=null;

   Object[] args;

   Class<?> cla=null;

   Method method=null;

    

   Scanner sc=new Scanner(System.in);

   try {

   //1.获得方法名

   methodName=joinPoint.getSignature().getName();

   //2.获取实体参数数组

   args=joinPoint.getArgs();

   //3.核心方法的类对象

   cla=joinPoint.getTarget().getClass();

   //4.获取核心方法对象

   //4.1获取形参的class字节对象

   parameterTypes=new Class<?>[args.length] ;

   for (int i = 0; i < args.length; i++) {

 if(args[i]==null){

 return "参数为空";

 }

parameterTypes[i]=args[i].getClass();

   

}

   method=cla.getDeclaredMethod(methodName, parameterTypes);

   if(method.isAnnotationPresent(Permission.class)){//该方法上是否出现注解

   System.out.println("*******环绕权限检测*******");

   System.out.println("********请输入本人身份*********");

   String checking=sc.nextLine();

   if("芝麻开门".equals(checking)){

   System.out.println("验证正确***************欢迎光临");

   }else{

   return "欢迎下次再来";

   }

   }else{

   System.out.println("执行"+methodName+"方法不需要环绕检测****");

   

   }

   result=joinPoint.proceed();

   System.out.println("环绕后置通知已出结果,请注意查收*************");

   return  result;

} catch (Throwable e) {

e.printStackTrace();

return   "核心程序方法运行错误";

}finally{

  System.out.println("请查看环绕通知的结果是否正确!!!!!!!!!!");

}

   }

   

}

1.1.9.6 基于AOP 应用实现日志处理

尝试自己实现

1.2 Spring MVC架构

1.2.1 Spring MVC简介

1.2.1.1 Web开发请求响应模型

在web的世界里都是客户端发起请求,服务端处理请求,并返回响应给客户端。  

以上图形描述的就是典型的B/S架构,是一种特殊的C/S机构,就是客户端

服务端/架构.

回顾:

客户端技术: html/css/javascript,jquery

服务端技术: java/servlet/jsp

数据传输协议:HTTP协议(超文本传输协议)

1.2.1.2 MVC 架构模式概述

1.2.1.2.1 MVC 架构模式定义

MVC全名是Model View Controller,是模型(model)-视图(view)-控制器(controller)的缩写,一种软件设计典范。其中:

M-Model 模型:职责是负责业务逻辑。

V-View 视图 :职责是负责显示界面和用户交互(收集用户信息)。

C-Controller 控制器:是模型层M和视图层V之间的桥梁,用于控制流程。

MVC 应用场景:应用相对复杂的场景.(电商系统,企业ERP系统,…)

MVC 应用优势:

可以更好实现应用解耦,提到代码可扩展性.

提高代码的可重用性,可读性以及可维护性.

1.2.1.2.2 生活中的MVC

分析正规大饭店中菜单(view),服务员(controller),厨师(model)的角色

菜单:呈现数据(有哪些美味食品)

服务员:服务于客户,将菜单信息传递给厨师.

厨师:数据加工(洗菜,切菜,做菜)

记住:程序中所有的逻辑都是来自于生活

1.2.1.2.3 程序中的MVC

Java中最典型的MVC架构设计就是JSPv + servletc + javabeanm的模式。

其中:

JSP 通常会充当视图层角色(借助html呈现数据)

Servlet 通常会充当控制层角色(主要负责流程控制)

JavaBean 通常会用于处理业务逻辑,数据逻辑(充当业务层对象)

目前市场上基于这样的一种架构有一些对应的框架(FrameWork):

Struts1.0

Struts2.0 (WebWork)

Spring MVC

……

1.2.1.3 Spring MVC 概述

Spring MVC是MVC架构模式的一种完美实现,是为了简化Java中MVC的编程过程而推出的一个Spring中的WEB应用模块。

Spring MVC 概要架构图及工作流程如下(先了解):

Spring MVC 核心架构图及工作流程(先了解,写完项目案例再重点强化)

从Spring MVC 核心架构分析,Spring MVC围绕DispatcherServlet而计,使用HandlerMapping决定由哪个处理器处理请求。

说明:具体流程可根据断点调试法逐步跟踪尝试(暂且可先记住简要流程)

Spring MVC 中的核心组件:

DispatcherServlet (控制器, 请求入口)—C,前端控制,负责发送和接受数据。

HandlerMapping (控制器, 请求派发)—C,存储url路径与servlet的映射关系

Controller (控制器, 请求处理流程) —C,后端控制器。

ModelAndView (模型, 封装业务处理结果和视图)—V,用于展现数据。

ViewResolver(视图解析器,解析对应的视图关系)—C,用于解析视图。出示访问路径url。

Spring MVC 设计的优势:

简化传统MVC 编程过程.

更好提高代码的编码效率以及程序的可扩展性.

1.2.2 Spring MVC 基本实现

1.2.2.1 案例分析

1.2.2.1.1 案例功能分析

浏览器中输入某个地址,直接访问资源访问地址,页面显示helloworld

1.2.2.1.2 基本步骤分析

创建spring maven web项目

添加Spring MVC 依赖库(建议通过maven添加)

添加Spring 配置文件spring-mvc.xml。

Web.xml中配置Spring MVC 前端核心处理器(DispatcherServlet)

创建Spring MVC 后端处理器(Controller)

spring-mvc.xml文件中配置Spring后端处理器及视图解析器(ViewResolver)

部署运行spring web项目(tomcat)

1.2.2.2 基本配置

1.2.2.2.1 创建Maven Web项目

具体maven web项目的创建过程可参考doc.tedu.cn.,也可参考

项目创建完成以后

右键选择Generate   Deployment  Descriptor     stub生成部署描述符(web.xml)

右键项目,选择Preperties属性,修改project facets,将java版本改成1.8.

右键项目, 选择Preperties属性,修改targeted runtimes,选择对应的tomcat服务器.

1.2.2.2.2 pom.xml基本依赖实现

添加spring-webmvc依赖,参考官网说明.

<dependencies>

<dependency>

  <groupId>org.springframework</groupId>

  <artifactId>spring-webmvc</artifactId>

  <version>4.3.9.RELEASE</version>

</dependency>

</dependencies>

假如添加好了依赖,都没有找到对应的jar依赖,先检测网络是否是通的.假如网络是通的,还没有下载到具体的依赖,此时要

右键项目,选择maven/upate maven project/fore update…

进行maven强制更新操作.

1.2.2.2.3 spring核心配置文件

项目的资源目录中直接创建spring-mvc.xml核心配置文件

<?xml version="1.0" encoding="UTF-8"?>

<beans default-lazy-init="true"

    xmlns="http://www.springframework.org/schema/beans" 

    xmlns:p="http://www.springframework.org/schema/p"

    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 

    xmlns:context="http://www.springframework.org/schema/context"

    xmlns:tx="http://www.springframework.org/schema/tx"

    xmlns:aop="http://www.springframework.org/schema/aop" 

    xmlns:mvc="http://www.springframework.org/schema/mvc"

    xmlns:util="http://www.springframework.org/schema/util"

    xmlns:jpa="http://www.springframework.org/schema/data/jpa"

    xsi:schemaLocation="  

       http://www.springframework.org/schema/beans   

       http://www.springframework.org/schema/beans/spring-beans-4.3.xsd  

       http://www.springframework.org/schema/mvc   

       http://www.springframework.org/schema/mvc/spring-mvc-4.3.xsd   

       http://www.springframework.org/schema/tx   

       http://www.springframework.org/schema/tx/spring-tx-4.3.xsd   

       http://www.springframework.org/schema/aop

       http://www.springframework.org/schema/aop/spring-aop-4.3.xsd

       http://www.springframework.org/schema/util

       http://www.springframework.org/schema/util/spring-util-4.3.xsd

       http://www.springframework.org/schema/data/jpa

       http://www.springframework.org/schema/data/jpa/spring-jpa-1.3.xsd

       http://www.springframework.org/schema/context

       http://www.springframework.org/schema/context/spring-context-4.3.xsd" >

   

</beans>

1.2.2.2.4 web.xml 基本配置

在的web.xml文件中配置spring mvc 前端控制器。

<servlet>

      <servlet-name>dispatcherServlet</servlet-name>

<servlet-class>

org.springframework.web.servlet.DispatcherServlet

</servlet-class>

      <init-param>

          <param-name>contextConfigLocation</param-name>

          <param-value>classpath:applicationContext.xml</param-value>

      </init-param>

      <load-on-startup>1</load-on-startup>

</servlet>

<servlet-mapping>

      <servlet-name>dispatcherServlet</servlet-name>

      <url-pattern>*.do</url-pattern>

<servlet-mapping>

1.2.2.3 基于xml方式实现

1.2.2.3.1 创建后端控制器

通过控制器处理用于请求,例如创建HelloController.class实现Controller接口,重写handleRequest()方法,获取请求数据,对请求数据进行类型转换等。

public class HelloController implements Controller{

public ModelAndView handleRequest(HttpServletRequest request,

HttpServletResponse response) throws Exception {

          // ModelAndView类即有map数据也有视图数据。

ModelAndView mv=new ModelAndView("hello");

          //添加数据 key——value

mv.addObject("message","helloworld");

return mv;

}

}

1.2.2.3.2 创建后端hello.jsp页面

在的pages文件目录下创建hello.jsp文件中添加如下内容。

<body>

   <h1>${message}</h1>

</body>

其中${message} 用于显示控制层封装返回的数据

1.2.2.3.3 配置controller及视图解析

打开spring-mvc配置文件配置控制器,处理映射以及视图解析操作。

具体代码如下

<!-- 将Controller这个Bean对象交给Spring管理 -->

<bean id="helloController"

            class="com.company.spring.controller.XmlHelloController">

</bean>

<!-- 配置HandlerMapping 映射处理器配置url到具体的Controller之间的映射-->

 <bean class=

"org.springframework.web.servlet.handler.SimpleUrlHandlerMapping">

             <property name="mappings">

                  <props>

                      <prop key="/hello.do">helloController</prop>

                  </props>

             </property>

   </bean>

  <!-- 配置视图解析器 -->

 <bean  id="viewResolver" class=

"org.springframework.web.servlet.view.InternalResourceViewResolver">

            <property name="prefix" value="/WEB-INF/pages/"/>

            <property name="suffix" value=".jsp"/>

</bean>

1.2.2.3.4 启动tomcat进行测试

部署项目到tomcat,启动tomcat并确定启动OK,然后在地址栏输入对应url,查看运行结果。

分析具体处理流程:

相关组件分析:

DispatcherServlet 核心前端处理(负责流程调度)

SimpleUrlHandlerMapping(记录url到handler的映射)

HelloController(又称之为handler处理器,处理客户端请求)

ModelAndView (封装数据以及对应的视图)

InternalResourceViewResolver(负责视图解析,基于view找到具体的view对象,例如hello.jsp)

1.2.2.4 基于注解方式实现

1.2.2.4.1 创建后端控制器

Sping2.5之前我们都是通过实现Controller接口方式定义我们的处理类,Spring2.5之后可通过@Controller和@RequestMapping注解定义我们的处理类。

先创建maven web项目,然后创建后端控制注解类AnnotationHelloController,类名随意:

//访问项目

//http://localhost:8080/项目名称/hello/doSayHello.do

@Controller     //spring容器管理注解(控制器)

@RequestMapping("/hello/")  //这个注解写在类上,代表请求映射路径,通过后面的/hello/找到这个类

public class AnnotationHelloController {

@RequestMapping("doSayHello")  //这个注解写在方法上,可以找到当前方法,(里面的内容)也代表访问的路径,和类的加载一起就是/hello/doSayHello

public String doSayHello(Model model) {

model.addAttribute("message", "helloworld");

return "hello";

}

}

说明:

通过@Controller配置这是一个控制器。

通过@RequestMapping实现Url与控制器及方法的映射

1.2.2.4.2 配置组件扫描及MVC注解应用

在基于xml实现方式上,applictionContext.xml配置文件中添加如下代码

<!-- 配置组件扫描 -->

<context:component-scan base-package="com.company"/>

<!-- 配置MVC注解扫描 -->

<mvc:annotation-driven/>

 

 <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">

             <property name="prefix" value="/WEB-INF/pages/"/>

             <property name="suffix" value=".jsp"/>

        </bean>  

1.2.3 Spring MVC 请求映射

1.2.3.1 请求路径映射

实际项目中我们要借助@RequestMapping注解定义映射路径。其注解应用位置

类定义处: 提供初步的请求映射信息(路径)。

方法定义处: 提供进一步的细分映射信息

1.2.3.1.1 普通url映射

@RequestMapping(value={"/doSayHello", "/user/doSayWelcome"}):

多个URL路径可以映射到同一个处理器的功能处理方法。

1.2.3.1.2 rest风格URL映射

REST即表述性状态传递(英文:Representational State Transfer,简称REST),是一种软件架构编码风格,是基于网络应用进行设计和开发的编码方式。可以降低开发的复杂度,提高程序的可伸缩性。例如:

@RequestMapping("/msg/{xxx}")

请求的URL可以是“/msg/hello”或“/msg/welcome”,后面的{xxx}是占位符,大括号里面可以随便传值,但#,%等个别符号还是不能用。

@RequestMapping("/msg/{id}/create"):

请求的URL可以是“/users/1/create”。大括号里面的id可以是任意一位数字,直接可以在浏览器访问的时候把参数写在url访问路径中传过来。再在方法参数中通过注解@PathVariable(“id”) integer id ,来接受这个值。

@RequestMapping("/msg/{mId}/topics/{tId}")

public ModelAndView doSaveMsg(@PathVariable("mId") String msg,@PathVariable("tId") Integer id){  }

这样也是可以的,请求时的URL可以是“/users/10/topics/12”。

说明:通过@PathVariable可以提取URI模板模式中的{×××}中的×××变量。

http://localhost:8080/项目名/doUpdate.do?id=1

http://localhost:8080/项目名/doUpdate/1.do

1.2.3.2 请求方式映射

1.2.3.2.1 请求方式限定

项目中Controller层的对象的每个方法默认可以处理任意方式的请求,假如要指定控制层的方法只能处理GET或只能处理POST请求,那该如何实现呢。

借助@RequestMapping注解中的method属性指定具体的请求处理方式,例如

@RequestMapping(value=”doSaveObj”,method=RequestMethod.POST)

public String doSaveObject(Object obj){….}

1.2.3.2.2 请求方式组合

项目中还可在控制层的方法上借助@RequestMapping注解中的method属性指定使用哪几种方式处理请求。

@RequestMapping(value=”doSaveObj”,method={RequestMethod.POSTRequestMethod.GET})

public String doSaveObject(Object obj){….}

        提示:一般浏览器只支持GET或POST方式。

1.2.3.3 请求参数映射

1.2.3.3.1 标准Servlet API对象

请求映射方法中可以直接使用ServletAPI 中的对象获取参数数据,例如HttpServletRequestHttpSession对象等,例如:

@RequestMapping(value="request",method=RequestMethod.GET)

@ResponseBody

public String withRequest(HttpServletRequest request){

System.out.println(request.getRequestURI());

return "Obtainer 'foo' query parameter value

'"+request.getParameter("gid")+"'";

}

提示:@ResponseBody注解作用:该注解作用于将Controller的方法返回的对象,通过适当的HttpMessageConverter转换为制定格式后,写入到Response对象的body数据区,使用情况:返回的数据不是Html标签的页面,而是其他数据格式的数据时,(如Json、xml等)使用;

问题:借助servlet原生API处理请求参数HttpServletRequest 对应的jar包在哪?

答:   tomcat/lib

问题: 什么场景下直接使用HttpServletRequest对象获取请求数据?

答: 当需要获取请求头,请求体等内部数据,还要对请求数据进行编码等处理,此时可考虑使用HttpServletRequest方式,处理请求数据.如:

@RequestMapping("doSaveObject")

 public String doSaveObject(HttpServletRequest request){

String id=request.getParameter("id");//获取请求参数为id的值

String content=request.getParameter("content");

System.out.println("id="+id);

System.out.println("content="+content);

return "request";//返回值为request。代表将返回到request.jsp

 }

1.2.3.3.2 基本类型参数

SpringMVC 请求一个控制层的资源时,可以在对应方法中,直接使用方法参数变量来接收请求参数的数据,但参数变量的类型要(最好)为对象类型(如:Integer)。

@RequestMapping(value="param",method=RequestMethod.GET)

@ResponseBody

public String withParam(@RequestParam String foo) {

return "Obtained 'foo' query parameter value '" + foo + "'";

}

提示:@RequestParam注解用于接收请求参数中名字为foo的参数值,假如请求参数名与方法中的参数名一致,@RequestParam注解可以省略。假如不一致则可以使用@RequestParam注解定义新的参数名直接接收页面数据,然后传递给方法的参数名,例如

@RequestMapping(value="param", method=RequestMethod.GET)

@ResponseBody

public String withParam(//这里假如web请求的参数为param1

@RequestParam(value="param1",required=false) String foo) {

return "Obtained 'foo' query parameter value '" + foo + "'";

}

提示:required=false表示,请求参数可以不存在,假如为true,请求参数不存在时会抛出异常。

问题:直接借助请求参数名相同的变量接收请求参数数据时,底层会做哪些事情?

  *1)通过反射获取方法对象

  *2)通过反射获取参数信息(如:名字,类型)

  *3)获取请求参数名对应的方法参数信息

  *4)根据对象的方法参数信息进行类型转换以及赋值操作.

需要注意:

  *1)当借助方法参数来接收页面数据时,参数类型最好为对象类型.

  *2)当方法参数名与请求参数名不一致时,也要接收对应数据可以使用@RequestParam 解指定接收哪个参数的值.(例如@RequestParam(name="tid"))

  *3)当方法中某个参数必须要求页面中有对应请求参数时,要借助@RequestParam注解中的required属性进行指定(例如@RequestParam(required=true)

@RequestMapping("doSaveObject")

 public String doSaveObject(

 @RequestParam(name="tid", required=true)Integer id,String content){

 System.out.println("id="+id);

 System.out.println("content="+content);

 return "request";

 }

1.2.3.3.3 请求多个参数

当请求中有多个参数时,可以通过在controller的方法中定义多个参数来接收参数的数据,也可以利用一个spring中的javabean对象,来接收多个参数的数据以简化多个方法参数变量的定义。

@RequestMapping(value="group", method=RequestMethod.GET)

@ResponseBody

public String withParamGroup(Message bean) {//自定义类message交个spring管理

return  "Obtained parameter group " + bean;

}

提示:当使用javabean接收请求参数数据时,bean对象中需要有与参数名对应的set方法(方法名set单词的后面要与这里的参数名对应相等)。

问题:请求中的参数信息是如何封装到msg对象的?

  * 1)基于bean对象的无参构造函数构建msg对象

  * 2)调用msg对象的set方法(set单词后面的名字需要与请求参数名对应)

  <form action="#">

       <input type="text"   name="content"/>  //这里content就是参数名需要与set方法名一致

       <input type="submit"  value="submit"/>

   </form>

@RequestMapping("doSaveObject")

 public String doSaveObject(Message msg){

 System.out.println(msg);

 return "request"; //返回request.jsp

 }

1.2.3.3.4 请求Url参数

SpringMVC请求资源路径的URL可以通过{XXX}形式指定动态的URL,动态URL中的这个可变参数的值可以直接注入到方法对应的参数中。

@RequestMapping(value="path/{var}" ,method=RequestMethod.GET)

@ResponseBody

public String withPathVariable(@PathVariable String var) {

return "Obtained 'var' path variable value '" + var + "'";

}

通过@PathVariable注解指定参数变量var获取请求url中{var}数据

用Rest 风格从请求url中数据的获取 http://localhost:8080/项目名/req/doDeleteObject/30.do,当方法中某个参数(参数名id的值,需要从rest url中的表达式{id}中的id值时,要借助@PathVariable注解进行声明

@RequestMapping("doDeleteObject/{id}")

 public String doDeleteObject(@PathVariable Integer id){

 System.out.println("id="+id);

 return "request";

 }

1.2.3.3.5 请求头参数

当服务端要获取客户端请求头中的数据信息时,可通过@RequestHeader注解,即可将请求头中

的属性值绑定到处理方法的形参中,例如获取请求中Accept属性的值,然后传入到对应方法的参数中。

@RequestMapping(value="header", method=RequestMethod.GET)

@ResponseBody

public String withHeader(@RequestHeader String Accept) {

return "Obtained 'Accept' header '" + Accept + "'";

}

提示:方法中的参数名,需要与请求头参数中的某个参数名相同,具体请求头相关信息可以在浏览器控制台查看。@RequestHeader 标识请求头中的数据。

如果应用中需要获取请求中所有数据(请求实体数据)时,可以在请求方法中定义一个HttpEntity<String>参数,通过此参数获取请求头及请求体中数据,例如:

@RequestMapping(value="entity", method=RequestMethod.POST)

public @ResponseBody String withEntity(HttpEntity<String> entity) {

return "Posted request body '" + entity.getBody() + "';

 headers = " + entity.getHeaders();

}

         提示:entity包含请求中的所有参数信息,如果需要请求头的信息就用entity.getHeaders()方法获取,请求头中的信息,如果需要请求身体,就用entity.getBody()方法获取。

         

         获取请求中cookie中的值,例如获取cookiekeyJSESSIONID的值(需要session传过值,要不然cookie没有值)

@RequestMapping("doWithCookie")

 @ResponseBody

 public String doWithCookie(@CookieValue(value="JSESSIONID") String jsid){

 return "cookie.JSESSIONID="+jsid;

 }//key/value  

         提示:cookie存储数据的格式为key-value,键值对

1.2.4 Spring MVC响应处理

1.2.4.1 响应数据封装

1.2.4.1.1 ModelAndView 对象

在对服务端响应的数据进行封装时,可以直接在方法参数中定义一个ModelAndView类型的参数,借助ModelAndView对象封装响应数据.

@RequestMapping("doModelAndView")

public ModelAndView doModelAndView(ModelAndView mv) {

        //ModelAndView mv=new ModelAndView();

mv.addObject("data", "model and view"); //在map中添加key-value

mv.setViewName("back");//设置视图名为back,也就是可能需要back.jsp显示数据

return mv;

}

提示:ModelAndView 对象由Spring创建,并可以将数据存储到ModelAndView对象的ModalMap类型的属性中(可参考源代码).

1.2.4.1.2 Model 对象

借助参数model封装响应数据Model参数在运行时本质上指向的是一个map对象

@RequestMapping("doModel")

public String doModel(Model model) { //ModelMap

System.out.println("model="+model);

model.addAttribute("data", "modal");//设置mapkey-value

return "back";

}

1.2.4.1.3 Map 对象

使用map处理响应数据的封装。

@RequestMapping("doMap")

public String doMap(Map<String,Object> map) {

map.put("data", "map..");

return "back";

}

假如要使用map参数来接收页面数据,需要借助@RequestParam描述方法中的map参数(了解)

说明:通过map接收页面参数时,需要使用@RequestParam注解声明

@RequestMapping("doMap")

public String doMap(@RequestParam Map<String,Object> map) {

map.put("data", "map..");

return "back";

}

      

1.2.4.1.4 客户端请求重定向,服务器请求转发

return返回的response是服务器请求转发,request作用域比较小,可以请求转发。

Return返回的redirect:dowithUI.do是请求重定向,request作用域带不过去数据,只能用session作用域。

@RequestMapping("doMap")

public String doMap(@RequestParam Map<String,Object> map,

HttpSession session){

map.put("msg", "hello spring");//maprequest作用域

System.out.println("map="+map);

System.out.println("map.class="+map.getClass());//LinkedHashMap

//session.setAttribute("user", "tmooc");//这里为session作用域

return "response";//请求转发(服务端跳转)

//return "redirect:doWithUI.do";//重定向(客户端跳转) redirect

}

@RequestMapping("doWithUI")

public String withUI(){

return "response";

}

1.2.4.2 响应数据转换

1.2.4.2.1 转换为json格式

JSON(JavaScript Object Notation):一种轻量级数据交换格式,通常用于实现客户端与服务端之间的数据传输.

例如:

JSON格式的JavaScript对象:

var o1={id:10,name:’a’,age:20}

var o2=[{id:10,name:’a’,age:20},{id:20,name:’B’,age:30}]

访问json格式的javascript对象中的数据

var id1=o1.id;

var id2=o2[0].id

在实际的项目中,通常会将服务端返回的数据直接转换JSON格式字符串,在客户端将这个字符串再转换为javascript对象,然后从这个对象直接获取数据.

例如:

@RequestMapping("doJSON")

@ResponseBody

public Map<String,Object> doJSON() {

Map<String,Object> map=new HashMap<String, Object>();

map.put("id", 10);

map.put("msg","helloworld");

return map;//如果没有导包,返回的是doJSON.jsp

}

转换为json格式时,项目中需要添加json相关API依赖,例如使用jackson第三方库

<dependency>

    <groupId>com.fasterxml.jackson.core</groupId>

    <artifactId>jackson-databind</artifactId>

    <version>2.8.5</version>

</dependency>

其他案例,用bean对象存储值。把bean对象转换为json格式的字符,再在浏览器上把字符转换为json格式的对象

@RequestMapping("doBeanToJson")

@ResponseBody

public Message doBeanToJson(){

Message msg=new Message();

msg.setId(100);

msg.setContent("msg-content-1");

return msg;

}

             一般从数据库中操作数据时,都会把每一行的数据存储在map中,然后把所有的map放在list集合中进行操作。

@RequestMapping("doListToJson")

@ResponseBody

public List<Map<String,Object>> doListToJson(){

List<Map<String,Object>> list=

new ArrayList<>();

Map<String,Object> map=new HashMap<>();

map.put("id", 10);

map.put("content", "content-A");

list.add(map);

map=new HashMap<>();

map.put("id", 20);

map.put("content", "content-B");

list.add(map);

return list;

}

1.2.5 Spring MVC 拦截器

1.2.5.1 Spring MVC拦截器概述

         拦截器是SpringMVC中的一个核心应用组件,主要用于处理多个Controller的共性问题.当我们的请求由DispatcherServlet派发到具体Controller之前,首先要执行拦截器中一些相关方法,在这些方法中可以对请求进行相应预处理(例如权限检测,参数验证),这些方法可以决定对这个请求进行判断是拦截还是放行.

        通过spring mvc 架构图分析,拦截器在Spring MVC中处理流程中的一个位置

分析:拦截器与过滤器有什么异同点?

相同点:都可以对请求进行预处理

不同点:对请求进行预处理的时间不同.

思考:假如对请求数据进行编码,是应在过滤器还是拦截器?——过滤器

拦截器在spring mvc 框架中的执行流程如下图所示:

拦截器方法执行流程具体细节,参考如下所示:

当我们系统中有多个拦截器时,这些拦截器可以构成一个拦截器链.如下图所示:

1.2.5.2 Spring 拦截器应用

1.2.5.2.1 拦截器如何编写?

编写自定义的Interceptor(拦截器)实现HandlerInterceptor接口或继承HandlerInterceptorAdapter,重写相关方法处理具体业务。

package cn.spring.mvc.interceptor;

import javax.servlet.http.HttpServletRequest;

import javax.servlet.http.HttpServletResponse;

import javax.servlet.http.HttpSession;

import org.springframework.web.servlet.HandlerInterceptor;

import org.springframework.web.servlet.ModelAndView;

/**自定义拦截器*/

public class SysUserInterceptor implements HandlerInterceptor {

    /**此preHandle方法在Controller业务方法执行之前执行

     * @return 返回值绝对请求是放行还是拦截

     * false:表示拦截

     * true:表示放行

     * */

@Override

public boolean preHandle(

HttpServletRequest request,

HttpServletResponse response,

Object handler)

throws Exception {

System.out.println("preHandle");

/*HttpSession session=request.getSession();

Object user=session.getAttribute("user");

if(user==null){

return false;// false:拦截

}*/

return true;//true:放行

}

/**此postHandle方法在Controller的业务方法执行之后执行

 * @param handler指向具体的Controller对象

 * 请问方法中的modelAndView参数一定有值吗? ——可以没有值

 *

 * */

@Override

public void postHandle(

HttpServletRequest request,

HttpServletResponse response,

Object handler,

ModelAndView modelAndView)

throws Exception {

System.out.println("postHandler"

+ ".modelAndView="+modelAndView);

}

/**此afterCompletion方法在Controller的业务方法执行结束并且

*视图解析OK以后执行.*/

@Override

public void afterCompletion(

HttpServletRequest request,

HttpServletResponse response,

Object handler,

Exception ex)

throws Exception {

        System.out.println("afterCompletion.handler="+handler.getClass());

}

}

1.2.5.2.2 拦截器如何配置?

我们自己的拦截器编写完成以后需要在spring配置文件中进行配置,例如:

配置文件spring-mvc.xml代码实现

<?xml version="1.0" encoding="UTF-8"?>

<beans

    default-lazy-init="true"

    xmlns="http://www.springframework.org/schema/beans"

    xmlns:p="http://www.springframework.org/schema/p"

    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

    xmlns:context="http://www.springframework.org/schema/context"

    xmlns:tx="http://www.springframework.org/schema/tx"

    xmlns:aop="http://www.springframework.org/schema/aop"

    xmlns:mvc="http://www.springframework.org/schema/mvc"

    xmlns:util="http://www.springframework.org/schema/util"

    xmlns:jpa="http://www.springframework.org/schema/data/jpa"

    xsi:schemaLocation="  

       http://www.springframework.org/schema/beans   

       http://www.springframework.org/schema/beans/spring-beans-4.3.xsd  

       http://www.springframework.org/schema/mvc   

       http://www.springframework.org/schema/mvc/spring-mvc-4.3.xsd   

       http://www.springframework.org/schema/tx   

       http://www.springframework.org/schema/tx/spring-tx-4.3.xsd   

       http://www.springframework.org/schema/aop

       http://www.springframework.org/schema/aop/spring-aop-4.3.xsd

       http://www.springframework.org/schema/util

       http://www.springframework.org/schema/util/spring-util-4.3.xsd

       http://www.springframework.org/schema/data/jpa

       http://www.springframework.org/schema/data/jpa/spring-jpa-1.3.xsd

       http://www.springframework.org/schema/context

       http://www.springframework.org/schema/context/spring-context-4.3.xsd">

        <!-- 配置bean的扫描 -->

        <context:component-scan base-package="cn.spring"/>

        <!-- 启用mvc注解功能 -->

        <mvc:annotation-driven/>

        <!-- 配置视图解析器(解析modelAndView对象中

                          封装的view信息)/WEB-INF/pages/hello.jsp   

         -->

        <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">

             <property name="prefix" value="/WEB-INF/pages/"/>

             <property name="suffix" value=".jsp"/>

        </bean>

        <!-- 配置拦截器 -->

        <mvc:interceptors>

           

           <mvc:interceptor>

             <!-- 指定拦截哪些请求路径 /**表示拦截全部路径-->

             <mvc:mapping path="/**"/>

             <!-- 排除要拦截的路径 -->

             <mvc:exclude-mapping path="/sys/doLogin.do"/>

             <!-- 拦截器 -->

             <bean class="cn.spring.mvc.interceptor.SysUserInterceptor"/>

           </mvc:interceptor>

        </mvc:interceptors>

</beans>

1.2.6 Spring MVC 异常处理

1.2.6.1 Spring MVC 异常概述

实际项目中我们经常会采用分层架构设计程序,每一层都可能会有异常,假如异常信息没有处理,可能会选择抛出,当这些被抛出的异常,假如与具体业务相关,那到控制层(controller)以后,我们一般都进行相应的处理(处理方式应该相对友好)

在Spring mvc 项目中,边界出现异常以后我们通常会在Controller中进行处理,常用封层架构分析:

1.2.6.2 Spring MVC 异常处理

在spring中处理异常时,通常会在Controller中定义具体的异常处理方法,这个方法上使用@HandlerException注解进行描述.例如在指定Controller中定义异常处理方法:

@ExceptionHandler(value=Exception.class)

@ResponseBody

public String handleException(Exception e){

System.out.println("局部异常处理");

return e.getMessage(); //返回异常处理消息

}

当Spring的controller中没有定义具体的异常方法,我们需要在外部定义一个全局的异常处理类,这个类使用@ControllerAdvice注解进行修饰.然后在这个类中定义具体的异常处理方法,这些方法再使用@HandlerExcpeiton进行修饰,例如

@ControllerAdvice

public class AdviceExceptionHandler {

@ExceptionHandler(Throwable.class)

@ResponseBody  //直接在页面中返回字符串的时候使用

public String handlerException(Throwable e){  //处理异常的方法

System.out.println("全局的异常处理");

return e.getMessage();  //返回异常的消息

}

}

当控制层有局部的异常处理方法,还有全局的异常处理方法,此时在进行异常处理时遵循局部优先原则.

提示:控制层@ControllerAdvice修饰类中可以使用@InitBinder,@ModelAttribute等注解对一些方法进行修饰,使用这些注解修饰的方法会优先执行.对这些注解了解.

异常抛出及处理流程参考如下图(可作为扩展学习)

1.2.6.3 异常处理案例

1.2.6.3.1 定义异常和局部异常处理

package cn.spring.mvc.controller;

import org.springframework.stereotype.Controller;

import org.springframework.web.bind.annotation.ExceptionHandler;

import org.springframework.web.bind.annotation.RequestMapping;

import org.springframework.web.bind.annotation.ResponseBody;

@RequestMapping("/sys/")  //定义类路径

@Controller   //定义spring控制层组件,此类对象由spring进行管理

public class SysUserController {

  @RequestMapping("doSaveObject")//定义页面请求路径 /sys/doSaveObject.do

  @ResponseBody  //定义页面返回字符串

  public String doSaveObject(Integer id){

  System.out.println("doSaveObject");

  if(id<0)//这个业务判定以后可能要在业务层实现。(临时定义异常)

  throw new RuntimeException("id can not be null");

  return "Save OK";

  }

  /**定义Controller中的异常处理方法,这些方法需要借助ExceptionHandler注解进行描述,注解中的内容表示我这个方法能够处理的异常类型(包括这个异常的子类类型)*/

  @ExceptionHandler(value=Exception.class)

  @ResponseBody

  public String handleException(Exception e){

  System.out.println("局部异常处理");

  return e.getMessage();

  }  

}

1.2.6.3.2 全局异常处理

         一般如果在具体的控制层controller中没有处理异常,都会在外面定义一个全局的处理异常的类,而这个类的上面需要用@ControllerAdvice注解进行注释,类里面需要在处理具体的异常方法上面加上 @ExceptionHandler(Throwable.class)。括号里面的Throwable.class 是要处理的具体异常。

package cn.spring.mvc.controller;

import org.springframework.web.bind.annotation.ControllerAdvice;

import org.springframework.web.bind.annotation.ExceptionHandler;

import org.springframework.web.bind.annotation.ResponseBody;

/**说明:使用@ControllerAdvice注解描述的类一般要作为全局的异常处理类*/

@ControllerAdvic

 

public class AdviceExceptionHandler {

@ExceptionHandler(Throwable.class)

@ResponseBody

public String handlerException(Throwable e){

System.out.println("全局的异常处理");

return e.getMessage();

}

}

1.3 Mybatis数据持久框架

1.3.1 Mybatis概述

1.3.1.1 Mybatis是什么

MyBatis 是一个优秀的数据持久层框架,由apache的开源项目iBatis演化而来,主要用于解决数据持久化问题,底层实现了对JDBC操作的封装。

参考网址:http://www.mybatis.org

1.3.1.2 MyBatis 应用场景

简化JDBC编写步骤的复杂度

更好的实现ORM(对象关系映射)(Object  Relation Mapping)

 

提示:目前市场上还有一些类似的ORM框架,比如Hibernate.

1.3.1.3 MyBatis 核心API

MyBatis 框架在实现数据的持久化操作时,需要关注了解的核心配置及对象为:

Configs(xml) 配置文件&映射文件(mapper)

SqlSessionFactoryBuilder(负责加载配置文件,及创建SQLSessionFactory对象)

SqlSessionFactory(负责创建session对象)

SqlSession(负责执行SQL操作)

对以上API,先进行基本了解,然后再在实践过程中,再次强化理解即可。

1.3.2 Mybatis 编程实现

1.3.2.1 Mybatis 应用配置

1.3.2.1.1 MyBatis 依赖配置

要使用 MyBatis, 只需将 mybatis-x.x.x.jar 文件置于 classpath 中即可。

如果使用 Maven 来构建项目,则需将下面的 dependency 代码置于 pom.xml 文件中:

<dependency>

  <groupId>org.mybatis</groupId>

  <artifactId>mybatis</artifactId>

  <version>x.x.x</version>

</dependency>

1.3.2.1.2 MyBatis 映射配置

MyBatis 中通过映射文件的配置实现SQL语句与具体操作的映射,例如:

数据准备:

1.先在mysql的test数据库中创建表:

CREATE TABLE `sys_users` (

  `id` int(11) NOT NULL AUTO_INCREMENT,

  `username` varchar(200) NOT NULL,

  `password` varchar(200) NOT NULL,

  `phone` varchar(20) NOT NULL,

  `createdDate` date DEFAULT NULL,

  `modifiedDate` date DEFAULT NULL,

  PRIMARY KEY (`id`)

) ENGINE=InnoDB DEFAULT CHARSET=utf8

2.写入测试数据

insert into sys_users

values

(null,'A','123456','139',now(),now()),

(null,'B','123456','137',now(),now());

3.定义映射文件(定义在资源目录的mapper包下)

<?xml version="1.0" encoding="UTF-8"?>

<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"

  "http://mybatis.org/dtd/mybatis-3-mapper.dtd">

 <mapper namespace="com.project.sys.dao.SysUserDao">

    <select id="findUsers"

            resultType="map">

         select * from sys_users

    </select>

 </mapper>

查询语句 MyBatis 中最常用的元素之一,其中 select 元素用于定义查询语句,select 中的id属性是在命名空间namespace中是唯一的标识符,可以被用来引用对应的sql语句resultType表示从这条语句中返回的期望类型的类的完全限定名(类全名)别名(需要在配置文件中定义别名)。注意如果是集合情形,那应该是集合可以包含的类型,而不能是集合本身。

1.3.2.1.3 MyBatis 核心配置

此配置文件放在maven项目的resource根目录下,起名为mybatis-config.xml.

<?xml version="1.0" encoding="UTF-8"?>

<!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN"

"http://mybatis.org/dtd/mybatis-3-config.dtd">

<configuration>

  <environments default="development">

    <environment id="development">

      <transactionManager type="JDBC"/>

      <dataSource type="POOLED">

        <property name="driver" value="com.mysql.jdbc.Driver"/>

        <property name="url" value="jdbc:mysql:///test"/>

        <property name="username" value="root"/>

        <property name="password" value="root"/>

      </dataSource>

    </environment>

  </environments>

  <mappers>

    <mapper resource="mapper/SysUserMapper.xml"/>

  </mappers>

</configuration>

          通过typeAliases元素定义单个类的别名,这个别名可以在mapper文件中直接使用。

<typeAliases>

        <typeAlias type="com.project.sys.entity.SysUser" alias="sysUser"/>

</typeAliases>

通过typeAliases元素和包名定义多个类的别名,这个别名可以在mapper文件中直接使用。

<typeAliases>

      <!-- 默认为指定包中的类定义一个别名,这个别名为类名的第一单词的首字母小写-->

        <package name="com.project.sys.entity"/>

</typeAliases>

包含mapper映射文件到这个配置文件中

<mappers>

    <mapper resource="mapper/SysUserMapper.xml"/>

</mappers>

1.3.2.2 创建SqlSessionFactory

每个基于 MyBatis 的应用都是以一个 SqlSessionFactory 的实例为中心的。这个SqlSessionFactory 的实例可以通过 SqlSessionFactoryBuilder 基于xml的配置创建。

例如方法一:用mybatis封装好的API获得。

SqlSessionFactory sessionFactory=new SqlSessionFactoryBuilder()

.build(Resources.getResourceAsStream("mybatis-config.xml"));

例如方法二:用jdk的反射获得类的路径下的文件。

SqlSessionFactory factory= new SqlSessionFactoryBuilder().build(getClass()

    .getClassLoader().getResourceAsStream("mybatis-config.xml"));

1.3.2.3 创建SqlSession 执行操作

SqlSession 对象代表了一次SQL会话,包含了面向数据库执行 SQL 命令所需的所有方法。可以通过 SqlSession 实例来直接执行已映射的 SQL 语句。例如:

SqlSession sqlSession=sessionFactory.openSession();

List<Map<String,Object>> list=

sqlSession.selectList("com.project.sys.dao.SysUserDao.findUsers");//命名空间+sqlId

System.out.println(list);

sqlSession.close();

 

对于这样的操作,也可以先定义一个接口,然后通过接口类型对象去执行相关操作,例如:

package com.project.mybatis.dao;

import java.util.List;

import java.util.Map;

import com.project.mybatis.entity.SysUser;

public interface SysUserDao {

List<Map<String,Object>> findUsers();

int insertObject(SysUser entity);

}

 

接口定义以后,设置mapper映射文件中的命名空间为这个接口的全类名,例如:

<?xml version="1.0" encoding="UTF-8"?>

<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"

  "http://mybatis.org/dtd/mybatis-3-mapper.dtd">

 <mapper namespace="com.project.mybatis.dao.SysUserDao">

    <select id="findUsers" resultType="map">

         select * from sys_users

    </select>

</mapper>

 

基于这个配置文件,实现SQL查询,单元测试方法如下:

@Test

public void testFindUsers02() {

SqlSession sqlSession=sessionFactory.openSession();

SysUserDao dao=sqlSession.getMapper(SysUserDao.class);

List<Map<String,Object>> list=dao.findUsers();

System.out.println(list);

sqlSession.close();

}

 

查找一条记录方法session.selectOne( "com.project.sys.dao.SysUserDao.findObjectById",1);

 

1.3.2.4 MyBatis 编程流程总结

Mybatis 的执行流程可以简单概括为加载配置SQL解析SQL执行结果映射

1.3.3 Mybatis 编程增强

1.3.3.1 核心配置配置文件

MyBatis 的配置文件包含了会深深影响 MyBatis 行为的设置(settings属性(properties信息。文档的顶层结构如下:

configuration 配置

properties 属性

settings 设置

typeAliases 类型别名

typeHandlers 类型处理器

objectFactory 对象工厂

plugins 插件

environments 环境

environment 环境变量

transactionManager 事务管理器

dataSource 数据源

databaseIdProvider 数据库厂商标识

mappers 映射器

1.3.3.1.1 properties 属性

在java代码对应的资源目录resources下创建config.properties文件,内容如下:

driver=com.mysql.jdbc.Driver

url=jdbc:mysql:///test

username=root

password=root

在核心配置文件中引入此配置,并获取对应值

<properties resource="config.properties"/>

<dataSource type="POOLED">

  <property name="driver" value="${driver}"/>

  <property name="url" value="${url}"/>

  <property name="username" value="${username}"/>

  <property name="password" value="${password}"/>

</dataSource>

这个例子中的 username  password 将会由 properties 元素中设置的相应值来替换,目的是让程序变得更加灵活。

1.3.3.1.2 Setting元素

这是 MyBatis 中极为重要的调整设置,它们会改变 MyBatis 的运行时行为,例如MyBatis 缓存设置,延迟加载设置等。

<settings>

  <setting name="cacheEnabled" value="true"/>     //设置二级缓存开启

  <setting name="lazyLoadingEnabled" value="true"/>  //延迟加载

  <setting name="multipleResultSetsEnabled" value="true"/>  //已启用多个结果集

  <setting name="useColumnLabel" value="true"/>  //使用列标签

  <setting name="useGeneratedKeys" value="false"/>  //使用生成密钥

  <setting name="autoMappingBehavior" value="PARTIAL"/>//自动生成映射行为

  <setting name="autoMappingUnknownColumnBehavior" value="WARNING"/>

  <setting name="defaultExecutorType" value="SIMPLE"/>

  <setting name="defaultStatementTimeout" value="25"/>

  <setting name="defaultFetchSize" value="100"/>

  <setting name="safeRowBoundsEnabled" value="false"/>

  <setting name="mapUnderscoreToCamelCase" value="false"/>

  <setting name="localCacheScope" value="SESSION"/>

  <setting name="jdbcTypeForNull" value="OTHER"/>

  <setting name="lazyLoadTriggerMethods"

value="equals,clone,hashCode,toString"/>

</settings>

提示:暂时不需要掌握所有配置,先了解

1.3.3.1.3 TypeAliases 设置

类型别名是为 Java 类型设置一个短的名字。它只和 XML 配置有关,存在的意义仅在于用来减少类完全限定名的冗余。例如:

<typeAliases>

  <typeAlias alias="Author" type="domain.blog.Author"/>

  <typeAlias alias="Blog" type="domain.blog.Blog"/>

  <typeAlias alias="Comment" type="domain.blog.Comment"/>

  <typeAlias alias="Post" type="domain.blog.Post"/>

  <typeAlias alias="Section" type="domain.blog.Section"/>

  <typeAlias alias="Tag" type="domain.blog.Tag"/>

</typeAliases>

例如:

当这样配置时,Blog可以用在任何使用domain.blog.Blog的地方。也可以指定一个包名,MyBatis 会在包名下面搜索需要的 Java Bean。

<typeAliases>

        <package name="com.project.sys.entity"/>

  </typeAliases>

MyBatis为许多常见的Java类型内置类相应的类别名,例如

别名

映射类型

iterator

Iterator

collection

Collection

arraylist

ArrayList

list

List

hashmap

HashMap

map

Map

bigdecimal

BigDecimal

decimal

BigDecimal

date

Date

boolean

Boolean

float

Float

double

Double

integer

Integer

int

Integer

short

Short

long

Long

byte

Byte

string

String

boolean

boolean

_float

float

_double

double

_int

int

_short

short

_long

long

_byte

byte

1.3.3.2 核心业务操作

1.3.3.2.1 定义实体对象

public class SysUser implements Serializable{

private static final long serialVersionUID = -9202121822896212030L;

private Integer id;

private String  username;

private String  password;

private String  phone;

private Date    createdDate;

private Date    modifiedDate;

//set,get 方法

}

1.3.3.2.2 定义mapper映射文件

定义查询操作

<select id="findUsers" resultType="sysUser">

         select * from sys_users

 </select>

定义一个插 入操作,将数据写入到数据库

<insert id="insertObject"

parameterType="sysUser">

            insert into

(username,password,phone,createdDate,modifiedDate)

   values(null,'AA','123','139',

now(),now())

    </insert>

更新记录信息

<update id="updateObject"

parameterType="sysUser">

update sys_users  set

username = #{username},

password = #{password},

phone = #{phone},

modifiedTime = NOW(),

modifiedUser =now ()

where

id = #{id}

</update>

         其中:#{}可以获取参数中的值,并且会在底层告诉 MyBatis ,并由SqlSessionFactory 创建一个预处理语句

(PreparedStatement)参数(?,?,?)

删除记录信息

    <delete id="deleteObject" 

parameterType="integer">

          delete from sys_users where

id=#{uid}

    </delete>

1.3.3.2.3 mapper单元测试

@Test

public void testFindUsers() {

SqlSession sqlSession = factory.openSession();

SysUserDao dao = sqlSession.getMapper(SysUserDao.class);

List<SysUser> list = dao.findUsers();

System.out.println(list);

sqlSession.close();

}

@Test

public void testFindUserById() {

SqlSession sqlSession = factory.openSession();

SysUserDao dao = sqlSession.getMapper(SysUserDao.class);

SysUser user = dao.findUserById(1);

System.out.println(user);

sqlSession.close();

}

@Test

public void testInsertObject() {

SqlSession session = factory.openSession(false);

SysUserDao dao = session.getMapper(SysUserDao.class);

SysUser entity = new SysUser();

entity.setUsername("tarena");

entity.setPassword("123");

entity.setPhone("123456");

int rows = dao.insertObject(entity);

session.commit();

Assert.assertEquals(1, rows);

session.close();

}

底层处理过程分析:

1.3.3.3 业务映射文件

MyBatis 的真正强大在于它的映射语句,也是它的魔力所在。由于它的异常强大,映射器的 XML 文件就显得相对简单。如果拿它跟具有相同功能的 JDBC 代码进行对比,你会立即发现省掉了将近 95% 的代码。MyBatis 就是针对 SQL 构建的,并且比普通的方法做的更好。

SQL 映射文件有很少的几个顶级元素(按照它们应该被定义的顺序):

cache – 给定命名空间的缓存配置(SqlSessionFactory)。

cache-ref – 其他命名空间缓存配置的引用。

resultMap – 是最复杂也是最强大的元素,用来描述如何从数据库结果集中来加载对象。

sql – 可被其他语句引用的可重用语句块(然后借助include进行包含)。

insert – 映射插入语句

update – 映射更新语句

delete – 映射删除语句

select – 映射查询语句

1.3.3.3.1 Select 元素应用分析

Select 元素用于定义查询语句,其常用属性如下:

parameterType 将会传入这条语句的参数类的完全限定名或别名。这个属性是可选的,因为 MyBatis 可以通过 TypeHandler 推断出具体传入语句的参数

resultType 从这条语句中返回的期望类型的类的完全限定名或别名。注意如果是集合情形,那应该是集合可以包含的类型,而不能是集合本身。

resultMap 外部 resultMap 的命名引用。结果集的映射是 MyBatis 最强大的特性,在复杂映射的场景下可选择resultMap.

提示:使用 resultType  resultMap,但不能同时使用。

例如:resultMap的应用(当查询的结果与要映射的对象字段不匹配时,可以使用ResultMap做映射转换),实体类的对象的property属性名称对数据库的column字段(列)名。

<resultMap id="userResultMap" type="sysUser">

  <id property="id" column="user_id" />

  <result property="username" column="user_name"/>

  <result property="password" column="hashed_password"/>

</resultMap>

<select id="selectUsers" resultMap="userResultMap">

  select user_id, user_name, hashed_password

  from some_table

  where id = #{id}

</select>

1.3.3.3.2 insert/update/delete元素应用分析

insert 元素用于定义sql插入操作,例如 

<insert id="insertObject" parameterType="sysUser"

            useGeneratedKeys="true" keyProperty="id">

       insert into sys_users(id,username,password,phone,createdDate,modifiedDate)

       values(null,#{username},#{password},#{phone},now(),now())

 </insert>

提示:如果你的数据库支持自动生成主键的字段(比如 MySQL  SQL Server),那么你可以设置 useGeneratedKeys=”true”,然后再把 keyProperty 设置到目标属性上就OK

通过update元素定义SQL修改语句

<update id="updateObject" parameterType="sysUser">

       update sys_users

       set phone=#{phone},modifiedDate=now()

       where id=#{id}

 </update>

        通过delete元素定义删除操作

 <delete id="deleteOjbect" parameterType="int">

       delete from sysUser where id=#{id}

 </delete>

1.3.3.3.3 参数parameters

参数是 MyBatis 非常强大的元素,例如:

<insert id="insertObject" parameterType="sysUser"

      useGeneratedKeys="true" keyProperty="id">

       insert into

       sys_users(id,username,password,phone,createdDate,modifiedDate)

     values(null,#{username},#{password},#{phone},now(),now())

   </insert>

默认情况下,使用#{}格式的语法会导致 MyBatis 创建预处理语句属性并安全地设置值(比如?)。这样做更安全,更迅速,通常也是首选做法,不过有时你只是想直接在 SQL 语句中插入一个不改变的字符串。比如,像 ORDER  BY(排序),你可以这样来使用

<select id="findUsers" resultType="sysUser">

         select * from sys_users  order by ${columnName}

</select>

提示:

1)$符号在应用时有可能会出现SQL注入的风险。

2)$符号接收接口方法参数值时,此值一般要使用@Param注解进行定义.

3)#符号接收接口方法参数数据时,假如接口中参数个数大于一个,建议使用@param注解对参数进行定义.

课后总结:$与#的异同点

1.3.3.3.4 sql 元素应用分析

sql这个元素可以被用来定义可重用的 SQL 代码段,可以包含在其他语句中。

  <sql id="orderBy">

        order by ${columnName}

  </sql>

  <select id="findUsers" resultType="sysUser">

         select * from sys_users

         <include refid="orderBy"/>

  </select>

课堂练习:

写一个模糊查询(like),查询所有手机号中包含139(用户输入的)的用户信息,并对这些用户信息按id降序排序.

Mapper文件映射

接口定义-声明函数

实体类定义-存值

单元测试

1.3.4 Mybatis 高级应用

1.3.4.1 日志配置及应用

Mybatis内置的日志工厂提供日志功能,具体的日志实现有以下几种工具:

SLF4J(日志框架标准,类似JDBC标准)

Apache Commons Logging

Log4j 2 (是log4j的升级版,配置文件升级为xml格式了)

Log4j(日志处理库,配置文件格式为.properties)

JDK logging

具体选择哪个日志实现工具由MyBatis的内置日志工厂确定。它会使用最先找到的(按上文列举的顺序查找)。 如果一个都未找到,日志功能就会被禁用。如果你的应用部署在一个包含Commons Logging的环境, 而你又想用其他的日志框架,你可以通过在MyBatis的配置文件mybatis-config.xml里面添加一项setting(配置)来选择一个不同的日志实现。例如以Log4J为例。配置日志功能。

 

       首先在pom文件中添加项目log4j依赖:

<dependency>

<groupId>log4j</groupId>

<artifactId>log4j</artifactId>

<version>1.2.17</version>

</dependency>

其次定义一个log4j.properties文件放在类路径(例如maven项目的resource目录)

log4j.rootLogger=INFO,stdout

log4j.appender.stdout=org.apache.log4j.ConsoleAppender

log4j.appender.stdout.layout=org.apache.log4j.PatternLayout

log4j.appender.stdout.layout.ConversionPattern=%d [%-5p] %c - %m%n

log4j.logger.com.mybatis3=DEBUG

log4j.logger.com.project=DEBUG

然后在mybatis核心的配置文件中添加此日志配置

  <settings>

         <setting name="logImpl" value="LOG4j"/>

</settings>

1.3.4.2 缓存配置及应用

MyBatis 包含一个非常强大的查询缓存特性,它可以非常方便地配置和定制。MyBatis缓存架构:一级缓存是session级别的缓存,默认是开启的,范围比二级小。二级缓存是factory级别的,默认是关闭的,需要自己在配置文件中如上所述的进行配置

默认情况下mybatis只开启了一级缓存(一般称之为sqlsession级别)。

@Test

public void testFindUserById() {

SqlSession sqlSession = factory.openSession();

SysUserDao dao = sqlSession.getMapper(SysUserDao.class);

SysUser user = dao.findUserById(1);

user = dao.findUserById(1);   //通过控制台的日志,可以判断sql语句执行了几次

System.out.println(user);

sqlSession.close();

}

说明:多次执行如上单元测试,通过日志分析仅发送了一次sql查询。

 

假如需要使用二级缓存(SqlSessionFactory级别)则需要:

第一步:在核心配置文件中配置 (默认是否是开启的要看具体版本)

<settings>

     <setting name="cacheEnabled" value="true"/>

</settings>

 

第二步:mybatis映射文件中添加cache标签。例如:

<cache eviction="LRU" flushInterval="60000" size="512" readOnly="true"/>

此时在同一个SqlSessionFactory中执行多次SQLSesion查询,根据日志看结果,看具体发送了几次sql

@Test

public void testFindUserById() {

SqlSession sqlSession = factory.openSession();

SysUserDao dao = sqlSession.getMapper(SysUserDao.class);

SysUser user = dao.findUserById(1);

    sqlSession.close();   //session关闭一次,缓存就会清空,这里测试的是二级缓存

    sqlSession=factory.openSession();

    dao = sqlSession.getMapper(SysUserDao.class);

user = dao.findUserById(1);

System.out.println(user);

sqlSession.close();

}

 

这个cache标签表示创建了一个 LRU缓存,并每隔 60 秒刷新,存数结果对象或列表的 512(字节) 个引用,而且返回的对象被认为是只读的,因此在不同线程中的调用者之间修改它们会 导致冲突。其中:

eviction 表示回收策略(例如LRU,FIFO等,默认为LRU)

flushInterval 表示刷新间隔时间,单位为毫秒。

size(引用数目)可以被设置为任意正整数,要记住你缓存的对象数目和你运行环境的可用内存资源数目。默认值是 1024。

readOnly(只读)属性可以被设置为 true 或 false。只读的缓存会给所有调用者返回缓存对象的相同实例。因此这些对象不能被修改。这提供了很重要的性能优势。可读写的缓存会返回缓存对象的拷贝(通过序列化) 。这会慢一些,但是安全,因此默认是 false。

假如希望将来的某个时候,命名空间中共享相同的缓存配置和实例,可以借助cache-ref 元素来引用另外一个缓存。

<cache-ref namespace="com.someone.application.data.SomeMapper"/>

//关联别的命名空间的二级缓存,可以共享它的缓存。

1.3.4.3 动态sql应用

MyBatis 的强大特性之一便是它的动态 SQL,动态sql可以依据条件的不同,更好的拼接不同的SQL语句。

MyBatis 常用的动态sql元素:

if

where

foreach

….等等

 

1.3.4.3.1 If 元素

If语句通常用于进行相关条件的判断,返回true则执行,false则不执行.

<select id="findUsers" resultType="sysUser">

  SELECT * FROM SYS_USERS WHERE id >10

  <if test="phone != nulland phone!=''">

     AND phone like #{phone}

  </if>

  <if test="username != null and username !=''">

     AND username like concat("%",#{username},"%")

  </if>

 </select>

 

例如:

定义接口方法:

List<SysUser> findUsers(@Param("columnName") String columnName,

@Param("ph") String phone);

定义SQL映射:

    <select id="findUsers" resultType="sysUser">

             select *

             <include refid="fromTable"/>

             where id>0

             <if test="ph!=null and ph!=''">

                and phone=#{ph}

             </if> 

             order by ${columnName}  desc      

     </select>

共用代码sql语句:

<sql id="fromTable">

         from sys_users

 </sql>

单元测试:

@Test

public void testFindUsers(){

SqlSession session=factory.openSession();

SysUserDao dao=session.getMapper(SysUserDao.class);

List<SysUser> list=dao.findUsers("id","139");

System.out.println(list);

session.close();

}

1.3.4.3.2 Where元素

Where 元素用于定义查询条件,并且可以去除空格或者是and,or等不符合语法要求的字符.

<select id="findUsers" resultType="sysUser">

  SELECT * FROM SYS_USERS

  <where>

  <if test="id != null">

     and id>#{id}                   // #{id}取参数中的值。

  </if>

  <if test="phone != null">

     and phone like #{phone}

  </if>

  <if test="username != null and username !=''">

     AND username like concat("%",#{username},"%")   //concat(),sql字符串拼接

  </if>

  </where>

</select>

1.3.4.3.3 foreach 元素

foreach用于迭代mybatis找那个参数(如:string[])的多个参数数据.

例如根据多个id的值删除多个元素.

定义接口方法:

int deleteObject(@Param("id") String[] ids);

定义映射sql

  <delete id="deleteObjectById">

     delete from sys_users

     where id in

     <foreach collection="id" open="("  close=")" separator="," item="item">

        #{item}

     </foreach>

  </delete>

 

定义单元测试:

@Test

public void testDeleteObject(){

SqlSession session=factory.openSession(false);

SysUserDao dao=session.getMapper(SysUserDao.class);

String[] ids={"14","15"};

int rows=dao.deleteObject(ids);

session.commit();

System.out.println("rows="+rows);

session.close();

}

 

动态SQL不局限于这个几个,还有其它的,但最重要是这个几个.

1.4 SSM 框架整合

Spring+SpringMVC+MyBatis

1.4.1 技术环境说明

1.4.1.1 服务端技术说明

Web服务器:tomcat

数据库服务器:mysql

服务端技术框架:Spring +SpringMVC + Mybatis

……

1.4.1.2 客户端技术说明

HTML,CSS,JavaScript

JQuery

…..

1.4.1.3 技术架构说明

项目整体采用MVC分层架构

1.4.2 技术整合

1.4.2.1 创建Maven Web项目

创建Maven Web项目(项目名称CGB-JT-SYS-V1.0)

生成项目的web.xml配置文件

项目设置(编码utf-8,target runtimes,project facets)

1.4.2.2 整合Spring MVC

输入控制对象是直接与用户输入相关的对象

添加Spring MVC依赖

<dependency>

 <groupId>org.springframework</groupId>

 <artifactId>spring-webmvc</artifactId>

 <version>4.3.9.RELEASE</version>

</dependency>

配置Spring MVC核心控制器(web.xml)

<servlet>

    <servlet-name>dispatcherServlet</servlet-name>

<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>

    <init-param>

      <param-name>contextConfigLocation</param-name>

      <param-value>classpath:spring-*.xml</param-value>

    </init-param>

    <load-on-startup>1</load-on-startup>

  </servlet>

  <servlet-mapping>

    <servlet-name>dispatcherServlet</servlet-name>

    <url-pattern>*.do</url-pattern>

  </servlet-mapping>

添加spring配置文件(spring-configs.xml)

将配置文件直接放置在maven项目的src/main/resources根目录

配置文件模板

<beans default-lazy-init="true"

    xmlns="http://www.springframework.org/schema/beans" 

    xmlns:p="http://www.springframework.org/schema/p"

    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 

    xmlns:context="http://www.springframework.org/schema/context"

    xmlns:tx="http://www.springframework.org/schema/tx"

    xmlns:aop="http://www.springframework.org/schema/aop" 

    xmlns:mvc="http://www.springframework.org/schema/mvc"

    xmlns:util="http://www.springframework.org/schema/util"

    xmlns:jpa="http://www.springframework.org/schema/data/jpa"

    xsi:schemaLocation="  

       http://www.springframework.org/schema/beans   

       http://www.springframework.org/schema/beans/spring-beans-4.3.xsd  

       http://www.springframework.org/schema/mvc   

       http://www.springframework.org/schema/mvc/spring-mvc-4.3.xsd   

       http://www.springframework.org/schema/tx   

       http://www.springframework.org/schema/tx/spring-tx-4.3.xsd   

       http://www.springframework.org/schema/aop

       http://www.springframework.org/schema/aop/spring-aop-4.3.xsd

       http://www.springframework.org/schema/util

       http://www.springframework.org/schema/util/spring-util-4.3.xsd

       http://www.springframework.org/schema/data/jpa

       http://www.springframework.org/schema/data/jpa/spring-jpa-1.3.xsd

       http://www.springframework.org/schema/context

       http://www.springframework.org/schema/context/spring-context-4.3.xsd" > 

</beans>

Spring mvc 配置

   <!-- 自动扫描该包 -->

    <context:component-scan base-package="com.jt" />

    <!—启用MVC注解-->

    <mvc:annotation-driven />

    <!-- 定义跳转的文件的前后缀 ,视图模式配置 -->

    <bean id="viewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver">

        <!-- 自动给后面action的方法return的字符串加上前缀和后缀,变成一个 可用的url地址 -->

        <property name="prefix" value="/WEB-INF/pages/" />

        <property name="suffix" value=".jsp"></property>

    </bean>  

测试

在com.jt.sys.controller编写控制器,验证MVC环境

@Controller

@RequestMapping("/role/")

public class RoleController {

@RequestMapping("listUI")

public String listUI(){

System.out.println("listUI()");

return "sys/roles";

}

}

在浏览器输入http://localhost:8080/CGB-JT-SYS-V1.0/role/listUI.do检测是否能够到达控制器并跳转到

/WEB-INF/pages/sys/role.jsp

1.4.2.3 整合JSON对象转换

添加Jackson依赖(课后了解fastjson)

<dependency>

<groupId>com.fasterxml.jackson.core</groupId>

<artifactId>jackson-databind</artifactId>

<version>2.8.5</version>

</dependency>

在需要返回JSON对象的方法上使用@ResponseBody注解,例如

@RequestMapping("doTestJackson")

@ResponseBody

public Map<String,Object> doTestJackson(){

 Map<String,Object> map=new HashMap<String,Object>();

         map.put(“id”,100);

         map.put(“name”,101);

return map;

}

配置好以后在浏览器中直接方法对应的地址,检测浏览器输出

1.4.2.4 整合DRUID连接池对象

DRUID 是阿里推出的一个能够有效处理高并发问题的连接池,性能比C3P0,DBCP等更好.

添加DRUID依赖

添加mysql驱动依赖(不要选5.1.6版本

<dependency>

<groupId>mysql</groupId>

<artifactId>mysql-connector-java</artifactId>

<version>5.1.40</version>

</dependency>

添加druid依赖

<dependency>

  <groupId>com.alibaba</groupId>

  <artifactId>druid</artifactId>

  <version>1.0.23</version>

</dependency>

添加config.properties (src/main/resources目录)

driver=com.mysql.jdbc.Driver

url=jdbc:mysql:///test

username=root

password=root

配置DRUID(还是在spring的这个spring-configs.xml文件中配置)

加载properties文件: 

<util:properties id="cfg" location="classpath:config.properties"/>  

配置DRUID数据源

<!--配置DruidDataSource连接池 -->

<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource"

destroy-method="close" init-method="init" lazy-init="true">

<property name="driverClassName" value="#{cfg.driver}" />

<property name="url" value="#{cfg.url}" />

<property name="username" value="#{cfg.username}" />

<property name="password" value="#{cfg.password}" />

</bean>

编写连接池单元测试

添加Junit依赖

<dependency>

  <groupId>junit</groupId>

  <artifactId>junit</artifactId>

  <version>4.12</version>

</dependency>

编写测试类

@Test

public void testPool() {

ApplicationContext ctx=

new ClassPathXmlApplicationContext("spring-configs.xml");

DruidDataSource dataSource=(DruidDataSource)ctx.getBean("dataSource");

System.out.println(dataSource);

Assert.assertNotEquals(dataSource, null);

}

1.4.2.5 整合MyBatis框架

添加MyBatis依赖

<dependency>

<groupId>org.mybatis</groupId>

<artifactId>mybatis-spring</artifactId>

<version>1.3.1</version>

</dependency>

<dependency>

<groupId>org.mybatis</groupId>

<artifactId>mybatis</artifactId>

<version>3.2.8</version>

</dependency>

在比较新的版本中通常还需要如下两个依赖

<dependency>

<groupId>org.springframework</groupId>

<artifactId>spring-tx</artifactId>

<version>4.3.9.RELEASE</version>

</dependency>

<dependency>

<groupId>org.springframework</groupId>

<artifactId>spring-jdbc</artifactId>

<version>4.3.9.RELEASE</version>

</dependency>

在spring-configs.xml 中添加mybatis配置,并添加如下内容

<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">

   <property name="dataSource" ref="dataSource" />

   <!-- 自动扫描mapping.xml文件 -->

   <property name="mapperLocations" >

<list><value>classpath:mapper/*.xml</value></list>

   </property>

</bean>

    <!-- Mapper接口所在包,Spring会自动查找其下的Mapper -->

<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">

<property name="basePackage" value="com.project.**.dao "/>

</bean>

编写单元测试

@Test

public void testSessionFactory() {

ApplicationContext ctx=

new ClassPathXmlApplicationContext("spring-configs.xml");    

Object sessionFactory=ctx.getBean("sqlSessionFactory");

System.out.println(sessionFactory);

Assert.assertNotEquals(sessionFactory, null);

}

1.4.2.6 整合Log4J输出

目的是实现mybati SQL日志的输出便于调试跟踪(扩展实现)

添加log4J依赖:

<dependency>

<groupId>log4j</groupId>

<artifactId>log4j</artifactId>

<version>1.2.17</version>

</dependency>

添加配置文件(log4j.properties) 

 

log4j.rootLogger=INFO,stdout

log4j.appender.stdout=org.apache.log4j.ConsoleAppender

log4j.appender.stdout.layout=org.apache.log4j.PatternLayout

log4j.appender.stdout.layout.ConversionPattern=%d [%-5p] %c - %m%n

log4j.logger.com.mybatis3=DEBUG

log4j.logger.com.jt=DEBUG

添加配置文件(mybatis-config.xml)

<?xml version="1.0" encoding="UTF-8" ?>

<!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN"

"http://mybatis.org/dtd/mybatis-3-config.dtd">

<configuration>

    <settings>

<!-- mybatis控制台LOG输出 -->

        <setting name="logImpl" value="LOG4J" />

    </settings>

</configuration>

修改配置文件spring-mybatis.xml添加mybatis-config.xml的配置

<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">

<property name="dataSource" ref="dataSource" />

<property name="configLocation" value="classpath:mybatis-config.xml"></property>

<!-- 自动扫描mapping.xml文件 -->

<property name="mapperLocations">

<list>

<value>classpath:mapper/*.xml</value>

</list>

</property>

</bean>

1.4.3 用户角色管理实践

1.4.3.1 用户角色管理概述

京淘项目中有一个权限管理子系统,此系统中包含用户管理,角色管理,菜单管理,组织机构管理等,

具体这些模块业务关系,后续分析,本节课首先从技术角度实现SSM技术的基本整合,实现上以角色管理模块为切入点。

1.4.3.2 创建用户角色表

创建数据库

create database jt_sys default character set utf8;

use jt_sys

CREATE TABLE `sys_roles` (

  `id` bigint(20) NOT NULL AUTO_INCREMENT,

  `name` varchar(100) DEFAULT NULL COMMENT '角色名称',

  `note` varchar(500) DEFAULT NULL COMMENT '备注',

  `createdTime` datetime DEFAULT NULL COMMENT '创建时间',

  `modifiedTime` datetime DEFAULT NULL COMMENT '修改时 间',

  `createdUser` varchar(20) DEFAULT NULL COMMENT '创建 用户',

  `modifiedUser` varchar(20) DEFAULT NULL COMMENT '修改用户',

  PRIMARY KEY (`id`)

) ENGINE=InnoDB  DEFAULT CHARSET=utf8 COMMENT='角色'

1.4.3.3 创建角色实体SysRole

public class SysRole implements Serializable{

private static final long serialVersionUID = -5225339701513043662L;

    private Integer id;

    private String name;

    private String note;

    private Date createdTime;

    private Date modifiedTime;

    private String createdUser;

    private String modifiedUser;

public Integer getId() {

return id;

}

public void setId(Integer id) {

this.id = id;

}

public String getName() {

return name;

}

public void setName(String name) {

this.name = name;

}

public String getNote() {

return note;

}

public void setNote(String note) {

this.note = note;

}

public Date getCreatedTime() {

return createdTime;

}

public void setCreatedTime(Date createdTime) {

this.createdTime = createdTime;

}

public Date getModifiedTime() {

return modifiedTime;

}

public void setModifiedTime(Date modifiedTime) {

this.modifiedTime = modifiedTime;

}

public String getCreatedUser() {

return createdUser;

}

public void setCreatedUser(String createdUser) {

this.createdUser = createdUser;

}

public String getModifiedUser() {

return modifiedUser;

}

public void setModifiedUser(String modifiedUser) {

this.modifiedUser = modifiedUser;

}

@Override

public String toString() {

return "SysRole [id=" + id + ", name=" + name + ", note=" + note + ", createdTime=" + createdTime

+ ", modifiedTime=" + modifiedTime + ", createdUser=" + createdUser + ", modifiedUser=" + modifiedUser

+ "]";

}

    

}

这样的对象我们通常称之为持久化对象(有些企业还会定义在entity包中)

 1)int insertObject(SysRole entity);

 2)SysRole doFindSysRoleById(Integer id);

 这样的对象编写时应注意:

 1)实现Serializable接口,并提供版本ID

 2)提供无参构造函数

 3)提供属性以及对应的set/get方法,一般属性名要与

 表中的字段名保持一致.

 4)有选择性的重写toString()方法

1.4.3.4 创建角色DAO接口

数据访问层对象,负责数据的CRUD操作.

package com.jt.sys.dao;

import java.util.List;

import com.jt.sys.pojo.SysRole;

public interface SysRoleDao {

 /**查询所有角色信息*/

 List<SysRole> findPageObjects();

}

1.4.3.5 基于DAO接口创建映射文件

  映射文件存储在java/main/resuource目录的mapper.sys包中

映射文件的名字为SysRoleMapper.xml.

<mapper namespace="com.jt.sys.dao.SysRoleDao">

    <select id="findPageObjects"

            resultType="com.jt.sys.pojo.SysRole">

            select *

            from sys_roles

    </select>

</mapper>

1.4.3.6 创建角色Service接口及实现类

public interface SysRoleService {

  List<SysRole> findPageObjects();

}

@Service

public class SysRoleServiceImpl implements SysRoleService {

@Autowired

private SysRoleDao sysRoleDao;

@Override

public List<SysRole> findPageObjects() {

//log

List<SysRole> list=sysRoleDao.findPageObjects();

    //log

return list;

}

}

1.4.3.7 基于Service创建单元测试

public class TestRoleService {

private ClassPathXmlApplicationContext ctx;

@Before

public void init(){

ctx=new ClassPathXmlApplicationContext("spring-configs.xml");

}

@Test

public void testFindPageObjects(){

SysRoleService roleService=

ctx.getBean("sysRoleServiceImpl",SysRoleService.class);

List<SysRole> list=

roleService.findPageObjects();

System.out.println(list);

}

@After

public void destory(){

ctx.close();

}

}

1.4.3.8 创建角色Controller

定义控制层类对象

package com.jt.sys.controller;

import java.util.List;

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.ResponseBody;

import com.jt.sys.pojo.SysRole;

import com.jt.sys.service.SysRoleService;

@RequestMapping("/role/")

@Controller

public class SysRoleController {

@Autowired

private SysRoleService sysRoleService;

@RequestMapping("doFindPageObjects")

@ResponseBody

public List<SysRole> doFindPageObjects(){

List<SysRole> list=

sysRoleService.findPageObjects();

return list;//jackson,fastjson

}

}

启动tomcat访问url进行测试.

1.5 SSM 技术整合

    DAY15

1.5.1 项目相关问题

1.5.1.1 编码问题

工作区编码(utf-8)

项目编码(utf-8)

请求响应编码(utf-8)

1.5.1.2 编译问题

JRE 问题(build path 移除jre,然后重新添加)

Maven 问题

2.1) run as / maven clean

2.2) maven update

2.3) project facets

Clean

3.1) project/clean

3.2) tomcat/clean

3.3) tomcat/publish

….

经过如上步骤以后,要检测部署目录下是否已经存在新

编译的class文件.

1.5.1.3 验证问题

项目属性/validation

1.5.2 角色列表显示

1.5.2.1 角色列表客户端操作

1.5.2.1.1 准备列表页面

新建role_list.html,放到WEB-INF/pages/sys目录

清空role_list.html内容

拷贝simple.html文件中表格部分(Responsive Hover Table)到role_list.html

1.5.2.1.2 定义controller中的呈现页面的方法

@RequestMapping("listUI.do")

public String listUI(){

return "sys/role_list";

}

1.5.2.1.3 异步加载role_list页面

点击starter.html左侧菜单列表中的角色管理,异步加载

Role_list.html页面.

$(function(){

      $("#load-role-id").click(function(){

     // console.log("hello world");

     $(".container-fluid").load("role/listUI.do");

      })

 })

1.5.2.1.4 role_list页面加载完成异步加载数据

在role_list.html 底部异步加载数据

<script type="text/javascript">

$(function(){

//页面加载完成以后,异步加载角色信息

doGetObjects();

});

function doGetObjects(){

var url="role/doFindPageObjects.do";

var params={pageCurrent:1};

$.getJSON(url,params,function(result){

console.log(result);

//将服务端返回的数据添填充在表格中

setTableTBodyRows(result);

});

}

function setTableTBodyRows(result){

var tBody=$("#tbodyId");

//迭代结果集

for(var i in result){//循环一次取一行记录

  //构建tr对象

  var tr=$("<tr></tr>");

  //构建td对象

  var tds="<td>"+result[i].id+"</td>"+

  "<td>"+result[i].name+"</td>"+

  "<td>"+result[i].note+"</td>"+

  "<td>"+result[i].createdTime+"</td>"+

  "<td>"+result[i].modifiedTime+"</td>"+

  "<td>"+result[i].createdUser+"</td>"+

  "<td>"+result[i].modifiedUser+"</td>";

  //td追加到tr

  tr.append(tds);

  //tr追加到tbody

  tBody.append(tr);

}

}

</script>

1.5.2.2 角色列表服务端操作

1.5.2.2.1 基于表创建POJO对象

public class SysRole implements Serializable{

private static final long serialVersionUID = -5225339701513043662L;

    private Integer id;

    private String name;

    private String note;

    private Date createdTime;

    private Date modifiedTime;

    private String createdUser;

private String modifiedUser;

//set/get 方法

1.5.2.2.2 基于POJO对象创建DAO接口

public interface SysRoleDao {

 /**查询所有角色信息*/

 List<SysRole> findPageObjects();

}

1.5.2.2.3 基于DAO接口创建mapper映射

在SysUserMapper文件中添加映射操作

<mapper namespace="com.jt.sys.dao.SysRoleDao">

    <select id="findPageObjects"

            resultType="sysRole">

            select *

            from sys_roles

    </select>

</mapper>

1.5.2.2.4 基于DAO接口创建Service接口及实现类

@Service

public class SysRoleServiceImpl

         implements SysRoleService {

@Autowired

private SysRoleDao sysRoleDao;

@Override

public List<SysRole> findPageObjects() {

//log

List<SysRole> list=

sysRoleDao.findPageObjects();

    //log

return list;

}

}

1.5.2.2.5 基于Service添加对应的Controller方法

@RequestMapping("doFindPageObjects")

@ResponseBody

public List<SysRole> doFindPageObjects(

Integer pageCurrent){

List<SysRole> list=

sysRoleService.findPageObjects();

return list;

}

1.5.3 角色删除操作

1.5.3.1 角色删除客户端操作

1.5.3.1.1 列表页面添加checkbox

<input type='checkbox'  name='checkedId'  value='"+result[i].id+"'/>

这样每行第一个元素就是checkbox.

1.5.3.1.2 列表页面添加删除按钮

<div class="input-group-btn">

<button type="submit" class="btn btn-default">

       <i class="fa fa-search"></i>

</button>

<button type="button" class="btn btn-default btn-delete">删除</button>

</div>

1.5.3.1.3 删除按钮上注册click事件

页面加载完成以后,在对应的删除按钮上注册click事件.

$(".input-group-btn").on("click",".btn-delete",doDeleteObject);

在.input-group-btn 标识的div元素中查找包含class为btn-delete的元素,然后此元素在上注册一个click事件,点击此元素时执行doDeleteObject方法.

1.5.3.1.4 定义执行删除操作的JS处理函数

获取选中的角色列表中的记录id

function getCheckedIds(){

var checkedIds="";

//获取所有tbody中名字为checkedIdinput元素

    $("tbody input[name='checkedId']")

//迭代这些input元素

.each(function(){

//假如这个inputchecked属性值为true

if($(this).prop("checked")){

if(checkedIds.length==0){

 checkedIds+=$(this).val();

}else{

 checkedIds+=","+$(this).val();

}

}

});

return checkedIds;

}

function doDeleteObject(){

//1.获得选中的角色 id(不定项)

var checkedIds=getCheckedIds();

if(checkedIds.length==0){

alert("请选择一个");

return;

}

//2.将这些id值异步传输到服务端,执行删除动作

var url="role/doDeleteObject.do";

var params={"checkedIds":checkedIds}

$.post(url,params,function(result){

alert(result);

doGetObjects();

});

}

1.5.3.2 角色删除服务端操作

1.5.3.2.1 DAO接口中添加删除方法

int deleteObject(@Param("ids")String[] ids);

1.5.3.2.2 DAO接口对应mapper中添加删除元素

   <delete id="deleteObject">

          delete from sys_roles

          where id in

          <foreach collection="ids"

                   open="(" 

                   close=")" 

                   separator=","

                   item="item">

                 ${item}

          </foreach>

    </delete>

1.5.3.2.3 Service接口及实现类中添加删除业务方法

    @Override

    public int deleteObject(String ids) {

    //1.验证参数有效性

    if(ids==null||ids.length()==0)

    throw new RuntimeException("选中的记录不能为null");

    //2.对参数数据进行转换

    String[] checkedIds=ids.split(",");

    //3.执行删除操作

    return sysRoleDao.deleteObject(checkedIds);

    }

1.5.3.2.4 Controller中添加删除逻辑方法

@RequestMapping("doDeleteObject.do")

@ResponseBody

public String doDeleteObject(String checkedIds){

sysRoleService.deleteObject(checkedIds);

return "delete ok";

}

1.5.4 角色添加模块实现

1.5.4.1 角色添加模块服务端实现

1.5.4.1.1 基于业务创建DAO方法

在SysRoleDao中添加insertObject方法

public interface SysRoleDao {

 /**查询所有角色信息*/

 List<SysRole> findPageObjects();

 /**根据id删除角色信息,这个ids

  * 可能对应多id,例如1,2,3,4*/

 int deleteObject(@Param("ids")String[] ids);

 /**执行insert操作*/

 int insertObject(SysRole entity);

}           

方法的返回值int,表示写入到数据库的记录数.

1.5.4.1.2 基于DAO定义Mapper元素

在SysRoleMapper文件中添加insert元素

    <insert id="insertObject" parameterType="sysRole">

      insert into sys_roles

       (id,name,note,createdTime,modifiedTime,

        createdUser,modifiedUser)

       values

       (null,#{name},#{note},now(),now(),

       #{createdUser},#{modifiedUser})

    </insert>

注意:

元素id需要与DAO接口方法名对象

参数中使用的别名必须实现定义

#获取参数对象中具体值(底层会调用对象的get方法)

1.5.4.1.3 基于DAO创建Service方法及实现

在SysRoleService接口中添加保存数据的方法,并在实现类中做具体实现

@Override

public int saveObject(SysRole entity) {

//log,transaction

int rows=sysRoleDao.insertObject(entity);

//log

return rows;

}

1.5.4.1.4 Service方法进行单元测试

对service中的saveObject方法进行junit单元测试

@Test

 public void testSaveObject(){

 SysRoleService rs=

 ctx.getBean("sysRoleServiceImpl",SysRoleService.class);

 SysRole e=new SysRole();

 e.setName("K");

 e.setNote("KKKKK");

 e.setCreatedUser("admin");

 e.setModifiedUser("admin");

 int rows=rs.saveObject(e);

 Assert.assertEquals(1, rows);

 }

1.5.4.1.5 基于Service编写Controller中的添加方法

在SysRoleController中添加保存角色信息的控制层方法.

@RequestMapping("doSaveObject")

@ResponseBody

public String doSaveObject(SysRole entity){

sysRoleService.saveObject(entity);

return "save ok";

}

1.5.4.2 角色添加模块客户端实现

1.5.4.2.1 编写角色添加模块的添加页面

在WEB-INF/pages/sys目录下创建role_edit.html页面.

1.5.4.2.2 角色列表页面添加一个添加按钮

<button type="button" class="btn btn-default btn-add">添加</button>

1.5.4.2.3 处理角色列表添加按钮点击click事件

$(function(){

$(".input-group-btn").on("click",".btn-add",doLoadEditPage);

})

点击按钮时加载添加页面

function doLoadEditPage(){

  //console.log("doLoadEditPage");

   $(".container-fluid").load("role/editUI.do");

   //load函数异步加载新的内容,

   //后续会替换原有位置内容

}

1.5.4.2.4 处理添加页面的cancel按钮上的click事件

在添加页面设置cancel按钮(type=button,)

<div class="box-footer">

<button type="button" class="btn btn-default btn-cancel">Cancel</button>

<button type="button" class="btn btn-info pull-right btn-save">Save</button>

</div>

在cancel按钮上注册点击事件

$(function(){

      $(".box-footer").on("click",".btn-cancel",doCancel)

 });

点击cancel按钮时返回列表页面

function doCancel(){

    $(".container-fluid").load("role/listUI.do");

}

1.5.4.2.5 处理页面的save按钮上的点击事件

Save按钮的设置(type=button)

<div class="box-footer">

<button type="button" class="btn btn-default btn-cancel">Cancel</button>

<button type="button" class="btn btn-info pull-right btn-save">Save</button>

</div>

Save 按钮上注册点击事件

$(function(){

        $(".box-footer").on("click",".btn-save",doSaveOrUpdate)

 });

点击save按钮时获得表单数据,然后异步提交表单数据

   function doSaveOrUpdate(){

    //1.获取表单数据

    var params=getFormData();

    //2.把数据异步提交服务端

    var url="role/doSaveObject.do"

    $.post(url,params,function(result){

    alert(result);

    //退出当前页面

    doCancel();

    });

    }

获取表单数据的方式:

/*获取表单数据*/

    function getFormData(){

    // json格式的javascript对象

       var params={

      "name":$("#nameId").val(),

      "note":$("#noteId").val()

       };

    return params;

    }

1.5.5 角色模块修改

1.5.5.1 根据id查询角色信息服务端实现

1.5.5.1.1 角色DAO中添加根据id查询的方法

在SysRoleDao接口中添加findObjectById方法

public interface SysRoleDao {

 /**查询所有角色信息*/

 List<SysRole> findPageObjects();

 /**根据id删除角色信息,这个ids

  * 可能对应多id,例如1,2,3,4*/

 int deleteObject(@Param("ids")String[] ids);

 /**执行insert操作*/

 int insertObject(SysRole entity);

 /**根据id查询角色信息*/

 SysRole findObjectById(Integer id);

}

1.5.5.1.2 基于DAO接口方法定义添加mapper元素

在SysRoleMapper中添加select元素

 <select id="findObjectById"

             resultType="sysRole">

           select *

           from sys_roles

           where id=#{id}     

     </select>

1.5.5.1.3 基于DAO创建Service接口方法及方法的实现

在SysRoleService接口及实现类中添加findObjectById方法

@Override

public SysRole findObjectById(Integer id) {

if(id==null)

throw new RuntimeException("id is null");

SysRole role=sysRoleDao.findObjectById(id);

return role;

}

1.5.5.1.4 基于ServiceController中添加查询方法

在SysRoleController类中添加控制逻辑方法,根据id查找角色信息.

@RequestMapping("doFindObjectById")

@ResponseBody

public SysRole doFindObjectById(Integer id){   

return sysRoleService.findObjectById(id);

}

1.5.5.2 根据id查找角色信息客户端实现

在本模块中修改页面和添加页面设计时我们要共用一个页面,但页面上要呈现的数据不同(例如页面标题,角色信息)

1.5.5.2.1 列表页面添加修改按钮

<div class="input-group-btn">

<button type="button" class="btn btn-default btn-update">修改</button>

</div>

1.5.5.2.2 处理修改按钮的点击事件

修改按钮上注册点击事件

$(function(){

$(".input-group-btn")

.on("click",".btn-add,.btn-update",doLoadEditPage);

});

点击修改按钮执行如下操作

function doLoadEditPage(){

   var title;

   if($(this).hasClass("btn-add")){

   title="添加角色";

   }else{

   title="修改角色";

   var ids=getCheckedIds();//数组

   if(ids.length!=1){

   alert("要选择一个");

   return;

   }

   //绑定数据,目的是在编辑页面可以获取此值

   //因为修改时需要根据进行查询

   $(".container-fluid").data("id",ids[0]);

   }

   var url="role/editUI.do";

   $(".container-fluid").load(url,function(){

   $(".box-title").html(title);

   });

}

1.5.5.2.3 处理编辑页面加载完成的事件

编辑页面在加载完成以后获取修改时绑定的id值,然后根据此值

进行查询,将查询的结果填充到编辑页面.

$(function(){

        //获得绑定的id(点击列表页面时绑定的值)

        var id=$(".container-fluid").data("id");

        //假如id有值则说明是更新,此时根据id进行记录查找

        if(id){doFindObjectById(id);}

    });

   

 根据id查找角色对象,然后初始化页面 

    function doFindObjectById(id){

    var url="role/doFindObjectById.do";

    var params={"id":id};

    $.getJSON(url,params,function(result){

    doInitEditForm(result)

    });

}

    初始化表单页面

    function doInitEditForm(result){

    $("#nameId").val(result.name);

    $("#noteId").val(result.note);

    }

1.5.5.3 根据id修改角色信息服务端实现

1.5.5.3.1 根据id修改角色信息(dao层接口方法实现)

/**修改角色信息*/

   int updateObject(SysRole entity);

1.5.5.3.2 根据dao修改mapper文件的映射关系

<update id="updateObject" parameterType="sysRole">

   update sys_roles set name=#{name},note=#{note},modifiedTime=now(),

   modifiedUser=#{modifiedUser} where id=#{id}

 </update>

1.5.5.3.3 定义service接口及实现类的方法的实现。

public interface SysRoleService {

   /**修改数据*/

   int updateObject(SysRole entity);

}

@Override

public int updateObject(SysRole entity) {

if(entity==null) throw new RuntimeException("更新对象不能为空");

if(entity.getId()==null) throw new RuntimeException("更新对象的Id不能为空");

int row=sysRoleDao.updateObject(entity);

return row;

}

1.5.5.3.4 定义controller控制层的方法。

@Autowired

private SysRoleService  sysRoleService;

/*************************************************************************************/

@RequestMapping("role/doUpdateObject.do")

@ResponseBody

public String doUpdateObject(SysRole entity){

sysRoleService.updateObject(entity);

return "update success";

}

@RequestMapping("role/edittUI")

public String doEditPage(){

System.out.println("doEditPag()");

return "sys/role_edit";

}

1.5.5.4 根据id修改角色信息客户端实现

1.5.5.4.1 Starter.html文件补充修改

<script type="text/javascript">

   $(function() {

//console.log("page load ok");

$("#load-role-id").click(function(){

/* 异步加载(底层发送ajax请求) */

$(".container-fluid").load("listUI.do",function(){

$(".container-fluid").removeData("id");

});

});

$("#load").click(function(){

/* 异步加载(底层发送ajax请求) */

$(".container-fluid").load("mainUI.do",function(){

$(".container-fluid").removeData("id");

});

});

});

   </script>

1.5.5.4.2 role_edit.html文件修改

function  doSaveOrUpdate(){

var id=$(".container-fluid").data("id");

/*if(id){

//1.获取表单数据

var params={"id":id,"name":$("#nameId").val(),"note":$("#noteId").val()};

//2.把数据异步提交服务端

var url="role/doUpdateObject.do";

}else{

//1.获取表单数据

var params=getFormData();

//2.把数据异步提交服务端

var url="role/doSaveObject.do";

} */

var params=getFormData();

var insertUrl="role/doSaveObject.do";

var updateUrl="role/doUpdateObject.do";

//条件运算符。三元运算符 

var url=id?updateUrl:insertUrl;

//动态给json对象添加元素。 

if(id)params.id=id;

$.post(url,params,function(result){

alert(result);

//退出当前页面

doCancel();  

});

}

修改页面点击保存按钮时,判定是保存还是修改.

    function doSaveOrUpdate(){

    //1.获取表单数据

    var params=getFormData();

    //2.把数据异步提交服务端

    //获取当前页面绑定的id

    var id=$(".container-fluid").data("id");

    //假如id有值说明是修改,此时在参数中添加id(k/v)

    if(id)params.id=id;

    //根据id值设置要执行的url

    var insertUrl="role/doSaveObject.do";

    var updateUrl="role/doUpdateObject.do";

    var url=id?updateUrl:insertUrl;

    $.post(url,params,function(result){

    alert(result);

    //退出当前页面

    doCancel();

    });

    }

角色管理

DAY17

1.5.6 角色查询模块实现

1.5.6.1 角色查询服务端实现

设计:查询时和点击角色管理时实现方法的重用.

1.5.6.1.1 角色Dao修改查询方法参数

public interface SysRoleDao {

 /**查询所有角色信息*/

 List<SysRole> findPageObjects(

 @Param("name") String name);

}

1.5.6.1.2 角色Mapper修改查询元素

<select id="findPageObjects" resultType="sysRole">

         select * from sys_roles

            <where>

             <if test="name!=null and name!=''">

                 name like concat("%",#{name},"%")

             </if>

          </where>

            order by createdTime desc

</select>

1.5.6.1.3 角色Service修改查询方法参数

@Override

public List<SysRole> findPageObjects(String name) {

//log

List<SysRole> list=

sysRoleDao.findPageObjects(name);

    //log

return list;

}

1.5.6.1.4 角色Controller修改查询方法参数

@RequestMapping("doFindPageObjects")

@ResponseBody

public List<SysRole> doFindPageObjects(

Integer pageCurrent,String name){

System.out.println("name="+name);

List<SysRole> list=

sysRoleService.findPageObjects(name);

return list;

}

1.5.6.2 角色查询客户端实现

1.5.6.2.1 角色查询按钮上注册点击事件

$(function(){

//页面加载完成以后,异步加载角色信息

doGetObjects();

//$("").click(function(){})

$(".input-group-btn")

.on("click",".btn-search",doQueryObject)

});

1.5.6.2.2 编写查询按钮的事件处理函数

/**处理查询按钮的点击事件*/

function doQueryObject(){

//debugger;

doGetObjects();

}

function doGetObjects(){

var url="role/doFindPageObjects.do";

var params={pageCurrent:1};

//console.log($(this));

params.name=$("#searchNameId").val();

console.log(params);

$.getJSON(url,params,function(result){

console.log(result);

//将服务端返回的数据添填充在表格中

setTableTBodyRows(result);

});

}

1.5.7 角色分页功能实现

1.5.7.1 角色列表分页服务端实现

1.5.7.1.1 角色DAO 方法定义

结合业务定义DAO方法参数:

定义查询当前页数据的方法

 List<SysRole> findPageObjects(

   @Param("name") String name,

   @Param("startIndex")Integer startIndex,

   @Param("pageSize")Integer pageSize);

定义查询总记录数的方法

 int getRowCount(@Param("name") String name);

1.5.7.1.2 角色Mapper元素定义

对通用代码进行提取

<sql id="whereSqlId">

          <where>

             <if test="name!=null and name!=''">

                 name like concat("%",#{name},"%")

             </if>

          </where> 

  </sql>

查询当前页记录

   

    <select id="findPageObjects"

            resultType="sysRole">

            select *

            from sys_roles

            <include refid="whereSqlId"/>

            order by createdTime desc

            limit #{startIndex},#{pageSize}

    </select>

    

    

统计记录数(要根据此记录数计算总页数)

    <select id="getRowCount" resultType="int">

        select count(*)

        from sys_roles

        <include refid="whereSqlId"/>

    </select>

1.5.7.1.3 定义PageObject封装业务数据

通过此对象封装页面显示的记录以及分页信息

public class PageObject {

/**当前页的记录*/

private List<SysRole> records;

/**总页数(计算)*/

private int pageCount;

/**总记录数(从数据库获取)*/

private int rowCount;

/**当前页的页码(从页面获取)*/

private int pageCurrent;

//set/get

}

1.5.7.1.4 角色Service方法定义及实现

定义查询当前页信息及分页信息的方法

@Override

public PageObject findPageObjects(

Integer pageCurrent,

String name) {

//1.获取当前页数据

//1.1定义每页最多显示3条记录

int pageSize=3;

//1.2计算每页查询的起始位置(limit startIndex,pageSize)

int startIndex=(pageCurrent-1)*pageSize;

List<SysRole> list=

sysRoleDao.findPageObjects(name,

startIndex,//查询的起始位置

pageSize);

//2.获取总记录数,计算总页数

int rowCount=sysRoleDao.getRowCount(name);

int pageCount=rowCount/pageSize;

if(rowCount%pageSize!=0){

pageCount++;

}

//3.封装数据(封装到pageObject)

PageObject pageObject=new PageObject();

pageObject.setRecords(list);

pageObject.setRowCount(rowCount);

pageObject.setPageCount(pageCount);

pageObject.setPageCurrent(pageCurrent);

return pageObject;//pageObject

}

1.5.7.1.5 角色Controller方法定义及实现

@RequestMapping("doFindPageObjects")

@ResponseBody

public PageObject doFindPageObjects(

Integer pageCurrent,String name){

System.out.println("name="+name);

//List<SysRole> list=

//sysRoleService.findPageObjects(name);

PageObject pageObject=

sysRoleService.

findPageObjects(pageCurrent, name);

return pageObject;

}//{pageCount:10,.....,records:[{id:1,name:'A',..},{}]}

1.5.7.2 角色列表分页客户端实现

1.5.7.2.1 角色列表添加分页div

在sys_role.html添加分页div

<div id="pageId" class="box-footer clearfix">

    <ul class="pagination pagination-sm no-margin pull-right">

                <li><a class="first">首页</a></li>

                <li><a class="pre">上一页</a></li>

                <li><a class="next">下一页</a></li>

                <li><a class="last">尾页</a></li>

                <li><a class="rowCount">总记录数(3)</a></li>

                <li><a class="pageCount">总页数(3)</a></li>

                <li><a class="pageCurrent">当前页(1)</a></li>

     </ul>

  </div>

1.5.7.2.2 角色分页div元素数据初始化

在doGetObjects方法中数据查询结束以后要调用如下方法初始化

页面信息.

function setPagination(pageObject){

$(".rowCount").html("总记录数("+pageObject.rowCount+")")

$(".pageCount").html("总页数("+pageObject.pageCount+")")

$(".pageCurrent").html("当前页("+pageObject.pageCurrent+")")

$("#pageId").data("pageCurrent",pageObject.pageCurrent);

$("#pageId").data("pageCount",pageObject.pageCount);

}

1.5.7.2.3 角色分页div元素事件监听

$("#pageId").on("click",".first,.pre,.next,.last",doJumpToPage)

1.5.7.2.4 角色分页div元素事件处理

/*跳转某一页 */

function doJumpToPage(){

var pageCurrent=$("#pageId").data("pageCurrent");

var pageCount=$("#pageId").data("pageCount");

    //获取点击对象的class属性值

var cls=$(this).prop("class");

if(cls=="first"){//首页

pageCurrent=1;

}else if(cls=="pre"&&pageCount>1){//上一页

pageCurrent--;

}else if(cls=="next"&&pageCurrent<pageCount){//下一页

pageCurrent++;

}else if(cls=="last"){//尾页

pageCurrent=pageCount;

}

//重新绑定pageCurrnet

$("#pageId").data("pageCurrent",pageCurrent);

//重新查询

doGetObjects();

}

1.5.7.2.5 查询结束初始化分页信息

function doGetObjects(){

var url="role/doFindPageObjects.do";

var pageCurrent=$("#pageId").data("pageCurrent");

if(!pageCurrent)pageCurrent=1;

var params={"pageCurrent":pageCurrent};

//console.log($(this));

params.name=$("#searchNameId").val();

console.log(params);

$.getJSON(url,params,function(result){

console.log(result);

//将服务端返回的数据添填充在表格中

setTableTBodyRows(result.records);

//设置分页信息

setPagination(result);

});

}

京淘

权限管理系统

Day18

1.5.8 角色管理初步优化

1.5.8.1 统一异常处理

  异常处理是提高系统容错机制的一种方式,并可以更好的改善用户体验.例如

我们的业务数据异常通常要反馈给用户,这种反馈可通过异常的传递进行实现.

  我们项目统一异常处理的基本步骤:

定义一个异常类,例如ServiceException处理业务层异常

定义一个全局的异常处理器(handler),在此类中进行全局异常处理.

   

   

1.5.8.1.1 定义业务层异常类

public class ServiceException extends RuntimeException {

private static final long serialVersionUID = -4181072079763562413L;

public ServiceException() {

super();

}

public ServiceException(String message, Throwable cause,

 boolean enableSuppression, boolean writableStackTrace) {

super(message, cause, enableSuppression, writableStackTrace);

}

public ServiceException(String message, Throwable cause) {

super(message, cause);

}

public ServiceException(String message) {

super(message);

}

public ServiceException(Throwable cause) {

super(cause);

}

}

1.5.8.1.2 定义统一异常处理类

@ControllerAdvice

public class ControllerExceptionHandler {

  @ExceptionHandler(ServiceException.class)

  @ResponseBody

  public String handleServiceException(

  ServiceException e){

  e.printStackTrace();

  return e.getMessage();

  }

}

1.5.8.2 统一结果集封装

在实际项目中,为了让客户端更好的处理服务端数据,通常会在服务端对数据进行

统一的数据封装(例如状态码,具体信息,数据等).

1.5.8.2.1 定义JsonResult

借助此类对控制层返回的数据进行统一封装,便于在客户端对数据进行更好处理.

public class JsonResult {//Result/R

     /**状态码(例如404是一个状态)

      * State=1,表示业务成功完成.

  * State=0,表示业务完成失败*/

 private int state=1;

 /**与状态码对应的具体消息(

  * 例如404表示错误信息,url对应的地址没找到)

  * */

 private String message="OK";

 /**借助此属性封装服务端返回的具体数据,

  * 例如查询的结果*/

   private Object data;

   public JsonResult() {}

   public JsonResult(int state,String message) {

   this.state=state;

   this.message=message;

   }

   public JsonResult(int state,String message,Object data) {

   this.state=state;

   this.message=message;

   this.data=data;

   }

   /**当在控制层将一个JsonResult对象

    * 转换为JSON串时,会调用此对象对应的

    * get方法*/

   public int getState() {

return state;

 }

   public String getMessage() {

return message;

 }

   public Object getData() {

return data;

 }

}

1.5.9 用户管理需求设计

1.5.9.1 功能设计

在本模块重点是实现后台用户的管理,包括用户的添加,修改,禁用,启用操作,

1.5.9.2 表的设计

CREATE TABLE `sys_users` (

  `id` int(11) NOT NULL AUTO_INCREMENT,

  `username` varchar(50) NOT NULL,

  `password` varchar(100),

  `salt` varchar(50),

`email` varchar(100),

  `mobile` varchar(100),

  `valid` tinyint(4),

  `createdTime` datetime,

  `modifiedTime` datetime,

  `createdUser` varchar(20),

  `modifiedUser` varchar(20),

  PRIMARY KEY (`id`),

  UNIQUE KEY `username` (`username`)

) ENGINE=InnoDB  DEFAULT CHARSET=utf8;

1.5.9.3 实体设计

public class SysUser implements Serializable {

private static final long serialVersionUID = 1768931144633277198L;

private Integer id;

private String username;

private String password;

private String email;

private String mobile;

private String salt;  //盐值

private Integer valid;

private Date createdTime;

private String createdUser;

private String modifiedUser;

private Date modifiedTime;

    //set,get

}

1.5.10 用户管理原型设计

1.5.10.1 用户列表页面

1.5.10.2 用户添加页面

1.5.10.3 用户修改页面

1.5.11 用户管理查询模块

1.5.11.1 查询模块DAO实现

定义SysUserDao接口,并在接口中添加如下两个方法,要考虑到分页。

List<SysUser> findPageObjects(

@Param("username")String username,

@Param("startIndex")Integer startIndex,

@Param("pageSize") Integer pageSize);

int getRowCount(@Param("username")String username);

1.5.11.2 查询模块Mapper元素实现

在SysUserMapper中添加查询元素。

定义公共的查询条件:

<sql id="pageWhereSqlId">

    <where>

<if test="username!=null and username!=''">

username LIKE CONCAT('%',#{username},'%')

</if>

</where>

</sql>

<select id="findPageObjects" resultType="sysUser">

select

id,

username,

email,

mobile,

valid

from sys_users

<include refid="pageWhereSqlId"/>

LIMIT #{startIndex},#{pageSize}

</select>

<select id="getRowCount"  parameterType="sysUser" 

            resultType="java.lang.Integer">

select count(*) from sys_users

<include refid="pageWhereSqlId"/>

</select>

1.5.11.3 查询模块Service接口方法定义及实现

定义SysUserService接口及接口方法实现。

public interface SysUserService {

PageObject findPageObjects(String username, Integer currentPage);

}

京淘

权限管理子系统

Day19

1.5.12 分页公共模块提取

1.5.12.1 为什么要提取

当多个页面都需要分页信息,又不想重复编写这段代码,需要将这些分页时

使用的片段代码进行提取.

1.5.12.2 提取基本步骤

1.5.12.2.1 定义page.html

在WEB-INF/pages/common文件夹下创建page.html

其内容如下:

<ul class="pagination pagination-sm no-margin pull-right">

<li><a class="first">首页</a></li>

<li><a class="pre">上一页</a></li>

<li><a class="next">下一页</a></li>

<li><a class="last">尾页</a></li>

<li><a class="rowCount">总记录数(3)</a></li>

<li><a class="pageCount">总页数(3)</a></li>

<li><a class="pageCurrent">当前页(1)</a></li>

</ul>

<script type="text/javascript">

//注册分页click 监听事件

$("#pageId").on("click",".first,.pre,.next,.last",doJumpToPage)

/*设置分页信息 */

function setPagination(pageObject){

$(".rowCount").html("总记录数("+pageObject.rowCount+")")

$(".pageCount").html("总页数("+pageObject.pageCount+")")

$(".pageCurrent").html("当前页("+pageObject.pageCurrent+")")

$("#pageId").data("pageCurrent",pageObject.pageCurrent);

$("#pageId").data("pageCount",pageObject.pageCount);

}

/*跳转某一页 */

function doJumpToPage(){

var pageCurrent=$("#pageId").data("pageCurrent");

var pageCount=$("#pageId").data("pageCount");

var cls=$(this).prop("class");

if(cls=="first"){//首页

pageCurrent=1;

}else if(cls=="pre"&&pageCount>1){//上一页

pageCurrent--;

}else if(cls=="next"&&pageCurrent<pageCount){//下一页

pageCurrent++;

}else if(cls=="last"){//尾页

pageCurrent=pageCount;

}

//重新绑定pageCurrnet

$("#pageId").data("pageCurrent",pageCurrent);

//重新查询

doGetObjects();

}

</script>

1.5.12.2.2 IndexController 中添加页面请求

@RequestMapping("pageUI")

public String pageUI(){

return "common/page";

}

1.5.12.2.3 列表页面加载page.html

列表页面加载完成以后,异步加载page.html页面

$("#pageId").load("pageUI.do");

1.5.13 用户禁用操作实现

1.5.13.1 服务端实现

1.5.13.1.1 SysUserDao添加禁用方法

int validById(

@Param("id")Integer id,

@Param("valid")Integer valid,

@Param("modifiedUser")String modifiedUser);

    

1.5.13.1.2 SysUserMapper添加禁用元素

  <update id="validById">

       update sys_users

       set valid=#{valid},modifiedTime=now(),

           modifiedUser=#{modifiedUser}

       where id=#{id}

   </update>

1.5.13.1.3 SysUserService接口及实现类添加禁用方法

@Override

public int validById(Integer id, Integer valid,String modifiedUser) {

//1.参数有效性验证

 if(id==null||id<0)

 throw new ServiceException("id 值无效");

 if(valid==null||valid<0)

     throw new ServiceException("状态值无效");

//2.修改状态信息

 int rows=sysUserDao.validById(id, valid,modifiedUser);

return rows;

}

1.5.13.1.4 SysUserController中添加禁用方法

@RequestMapping("doValidById")

@ResponseBody

public JsonResult doValidById(

Integer id,

Integer valid){

sysUserService.validById(id, valid,"admin");

return new JsonResult(1, "valid ok");

}

1.5.13.2 客户端实现

1.5.13.2.1 禁用按钮添加

修改setTableTBodyRows方法

function setTableTBodyRows(result){

//debugger;

console.log("setTableTBodyRows");

var tBody=$("#tbodyId");

//清空原有数据

tBody.empty();

//迭代结果集

for(var i in result){//循环一次取一行记录

  //构建tr对象

  var tr=$("<tr></tr>");

  //tr对象上绑定一个id

  tr.data("id",result[i].id);

  tr.data("valid",result[i].valid?0:1);

  //构建td对象

  var tds="<td><input type='radio' name='checkedId' value='"+result[i].id+"'/></td>"+

  "<td>"+result[i].username+"</td>"+

  "<td>"+result[i].email+"</td>"+

  "<td>"+result[i].mobile+"</td>"+

  "<td>"+(result[i].valid?"启用":"禁用")+"</td>"+

  "<td>"+result[i].createdTime+"</td>"+

  "<td>"+result[i].modifiedTime+"</td>"+

  "<td><button class='btn btn-defaultss' onClick='doValid(this)'>"+(result[i].valid?"禁用":"启用")+"</button></td>";

  //td追加到tr

  tr.append(tds);

  //tr追加到tbody

  tBody.append(tr);

}

}

1.5.13.2.2 处理按钮事件

function doValid(obj){

//1.url

var url="user/doValidById.do";

//2.params

var id=$(obj).parents("tr").data("id");

var valid=$(obj).parents("tr").data("valid");

var params={"id":id,"valid":valid}

console.log(params);

//3.post

$.post(url,params,function(result){//4.callback

if(result.state==1){

alert(result.message);

doGetObjects();

}else{

alert(result.message);

}

});

}

1.5.14 用户添加页面呈现

1.5.14.1 用户添加页面

1.5.14.1.1 创建添加页面user_edit.html

在WEB-INF/page/sys目录下创建user_edit.html

1.5.14.1.2 SysUserController中实现

@RequestMapping("editUI")

public String editUI(){

return "sys/user_edit";

}

1.5.14.2 用户列表页面

$(".input-group-btn")

.on("click",".btn-add",doLoadEditPage);

 

 

function doLoadEditPage(){

$(".container-fluid")

.load("user/editUI.do");

}

1.5.15 用户添加页面呈现角色信息

1.5.15.1 服务端实现

1.5.15.1.1 创建CheckBox

在com.jt.common.vo包中创建checkbox类,然后从角色表中查询角色ID和Name然后存储到CheckBox类型对象

public class CheckBox implements Serializable {

private static final long serialVersionUID = 2031967811425337153L;

private Integer id;

private String  name;

    //set,get

1.5.15.1.2 SysRoleDao接口添加查询方法

 List<CheckBox> findObjects();

1.5.15.1.3 SysRoleMapper文件添加查询元素

     <select id="findObjects"

             resultType="checkBox">

          select id,name

          from sys_roles

     </select>

1.5.15.1.4 SysRoleService接口及实现类中添加查询方法

@Override

public List<CheckBox> findObjects() {

return sysRoleDao.findObjects();

}

1.5.15.1.5 SysRoleController中添加查询方法

@RequestMapping("doFindObjects")

@ResponseBody

public JsonResult doFindObjects(){

List<CheckBox> list=

sysRoleService.findObjects();

return new JsonResult(1, "ok",list);

}

1.5.15.2 客户端实现

1.5.15.2.1 定义角色信息加载方法

在用户页面加载完成,加载角色信息

  function doLoadRoles(){

    var url="role/doFindObjects.do";

    $.getJSON(url,function(result){

    if(result.state==1){

      doSetRolesCheckBox(result.data);

    }else{

      alert(result.message);

    }

    });

    }

    function doSetRolesCheckBox(data){

    var rolesDiv=$("#rolesId");

    var cBox="<input type='checkbox' name='roleBox' value=[id]>[name]";

    for(var i in data){

    rolesDiv.append(cBox.replace('[id]',data[i].id)

        .replace('[name]',data[i].name));

    }//replace函数时一个字符串函数,用于实现字符串的替换,但会返回一个新串

    }

1.5.16 用户添加页面数据保存

1.5.16.1 服务端实现

1.5.16.1.1 创建中间表

用户与角色是多对多的关系,而这种关系数据的保存需要借助中间表实现

CREATE TABLE `sys_roles_users` (

  `user_id` int(11) NOT NULL DEFAULT '0',

  `role_id` int(11) NOT NULL DEFAULT '0',

  PRIMARY KEY (`user_id`,`role_id`)

) ENGINE=InnoDB DEFAULT CHARSET=utf8

1.5.16.1.2 Dao中实现

在SysUserDao接口中添加insertObject方法

public interface SysUserDao {

int insertObject(SysUser entity);

}

创建SysUserRoleDao 接口用于操作用户和角色的关系数据

public interface SysUserRoleDao {

 int insertObject(

 @Param("userId")Integer userId,

 @Param("roleIds")String[] roleIds);

}

1.5.16.1.3 Mapper中实现

在SysUserMapper中添加insert元素,用于保存用户数据.

   <insert id="insertObject"

           parameterType="sysUser"

           useGeneratedKeys="true"

           keyProperty="id">

           insert into sys_users

           (username,password,email,mobile,salt,valid,

           createdUser,modifiedUser,createdTime,modifiedTime)

           values

           (#{username},#{password},#{email},#{mobile},#{salt},#{valid},

           #{createdUser},#{modifiedUser},now(),now())

   </insert>

创建SysUserRoleMapper,并定义保存关系数据的方法

<mapper namespace="com.jt.sys.dao.SysUserRoleDao">

      <insert id="insertObject">

          insert into sys_roles_users

          (user_id,role_id)

          values

          <foreach collection="roleIds" 

                   separator=","

                   item="roleId">

             (#{userId},#{roleId})

          </foreach>

      </insert>

</mapper>

1.5.16.1.4 Service 中实现

在SysUserService接口及实现类中添加保存用户信息的方法:

@Override

 public int saveObject(SysUser entity,

String roleIds) {//2,3,4,5,

//1.保存用户信息

System.out.println("start.id="+entity.getId());

int rows=sysUserDao.insertObject(entity);

System.out.println("end.id="+entity.getId());

//2.保存关系数据(用户与角色关系数据)

sysUserRoleDao.insertObject(entity.getId(),

roleIds.split(","));

return rows;

 }

注册在此service中现在要关联两个DAO接口

1.5.16.1.5 Controller 中实现

在SysUserController中添加用户保存请求的处理方法

@RequestMapping("doSaveObject")

@ResponseBody

public JsonResult doSaveObject(

SysUser entity,

String roleIds){

sysUserService.saveObject(entity, roleIds);

return new JsonResult(1,"save ok");

}

1.5.16.2 客户端实现

1.5.16.2.1 按钮事件注册

页面加载完成以后在cancel和save按钮上注册事件

$(".box-footer")

        .on("click",".btn-cancel",doCancel)

        .on("click",".btn-save",doSaveOrUpdate);

1.5.16.2.2 按钮事件处理

处理按钮的cancel事件

    function doCancel(){

    //异步加载列表页面

    $(".container-fluid").load("user/listUI.do");

    }

处理按钮的save事件

function doSaveOrUpdate(){

    //1.获取表单数据

    var params=getFormData();

    console.log(params);

    //2.异步提交数据

    var url="user/doSaveObject.do";

    $.post(url,params,function(result){

    if(result.state==1){

    alert(result.message);

    doCancel();

    }else{

    alert(result.message);

    }

    });

    }

获取表单数据

    function getFormData(){

    // json格式的javascript对象

       var params={

      "username":$("#usernameId").val(),

      "password":$("#passwordId").val(),

      "mobile":$("#phoneId").val(),

      "email":$("#emailId").val()

    };//SysUser

    var checkedIds=[];

    $("#rolesId input[type='checkbox']")

    .each(function(){

    if($(this).prop("checked")){

    checkedIds.push($(this).val());

    }

    });

    params.roleIds=checkedIds.toString();

    return params;

}

1.5.17 用户修改页面

1.5.17.1 服务端设计与实现

1.5.17.1.1 Dao方法定义

用户点击修改时会从数据库根据id查询用户信息然后呈现在页面,

SysUserDao定义根据id查询数据的方法

SysUser findObjectById(Integer id);

SysUserRoleDao 定义根据userId查询角色信息的方法

List<Integer> findRolesByUserId(Integer id);

1.5.17.1.2 Mapper元素定义

在SysUserMapper中定义根据id查询用户信息的方法

   <select id="findObjectById"

           resultType="sysUser">

      select *

      from sys_users  

      where id=#{id}

   </select>

在SysUserRoleMapper中定义根据用户id查询数据的方法

      <select id="findRolesByUserId"

              resultType="integer">

              select role_id

              from sys_roles_users

              where user_id=#{id}

      </select>

1.5.17.1.3 Service方法定义

在SysUserService接口及实现类定义如下方法

 @Override

 public Map<String, Object> findObjectById(Integer id) {

 //查找用户信息

 SysUser user=sysUserDao.findObjectById(id);

 //查询角色信息

 List<Integer> roleIds=

 sysUserRoleDao.findRolesByUserId(id);

 //封装数据

 Map<String,Object> map=new HashMap<String,Object>();

 map.put("sysUser", user);

 map.put("roleIds", roleIds);

 return map;

 }

1.5.17.1.4 Controller方法定义

在SysUserController中定义返回页面的方法

@RequestMapping("editUI")

public String editUI(){

return "sys/user_edit";

}

在SysUserController中定义方法

@RequestMapping("doFindObjectById")

@ResponseBody

public JsonResult doFindObjectById(Integer id){

Map<String,Object>  map=

sysUserService.findObjectById(id);

return new JsonResult(1, "ok", map);

}

1.5.17.2 客户端设计与实现

1.5.17.2.1 按钮事件注册

在user_list.html的修改按钮上注册事件

$(".input-group-btn")

.on("click",".btn-add,.btn-update",doLoadEditPage);

1.5.17.2.2 按钮事件处理

在user_list.html页面定义加载页面的js函数

function doLoadEditPage(){

//1.获得点击对象上的class,根据class判定点击的对象

var title;

if($(this).hasClass("btn-add")){

title="添加用户";

}else if($(this).hasClass("btn-update")){

title="修改用户"

//获得选中的id

var id=getCheckedId();

console.log("id="+id)

//对获得的数据进行验证

if(!id){

alert("请先选择");

return;

}

//绑定数据(修改页面要使用此shuju)

$(".container-fluid")

.data("id",id)

}

//2.发异步请求加载页面

$(".container-fluid")

       .load("user/editUI.do",function(){

//修改页面标题

$(".box-title").html(title);

});

}

1.5.17.2.3 页面数据初始化

页面加载完成先加载用户角色信息

    function doLoadRoles(){

    var url="role/doFindObjects.do";

    $.getJSON(url,function(result){

    if(result.state==1){

      doSetRolesCheckBox(result.data);

      //获取绑定的id,根据此值判定是否要修改操作

      var id=$(".container-fluid").data("id");

      //假如是修改操作,则根据id获取数据,然后初始化页面

      if(id)doFindObjectById(id);

    }else{

      alert(result.message);

    }

    });

}

    function doSetRolesCheckBox(data){

    var rolesDiv=$("#rolesId");

    var cBox="<label><input type='checkbox' class='minimal'  name='roleBox' value=[id]>[name]</label>";

    for(var i in data){

    rolesDiv.append(

    cBox.replace('[id]',data[i].id)

        .replace('[name]',data[i].name));

    }//replace函数时一个字符串函数,用于实现字符串的替换,但会返回一个新串

    }

    

    

/*根据id查找用户对象,然后初始化页面 */

    function doFindObjectById(id){

    var url="user/doFindObjectById.do";

    var params={"id":id};

    console.log(params);

    $.getJSON(url,params,function(result){

    doInitEditForm(result)

    });

    }

    /*初始化表单页面*/

    function doInitEditForm(result){

        console.log(result);

        //1.获得用户基本信息

        var user=result.data.sysUser;

        //2.初始化用户基本信息

        $("#usernameId").val(user.username);

        $("#phoneId").val(user.mobile);

      $("#emailId").val(user.email);

      //3.获得用户角色信息

      var roleIds=result.data.roleIds;

      //4.初始化用户角色信息

      //4.1迭代页面所有角色checkbox

      $("#rolesId input[type='checkbox']")

      .each(function(){

      //4.2比对checkbox是否与用户角色值相等

      for(var i in roleIds){

       if($(this).val()==roleIds[i]){

      $(this).prop("checked",true);

       }

      }

      });

    }

1.5.18 用户修改模块

1.5.18.1 服务端设计与实现

1.5.18.1.1 Dao 接口中方法定义

在SysUserDao接口中添加修改方法

int updateObject(SysUser entity);

在SysUserRoleDao中添加删除方法(目的是在修改用户角色关系时,先删除原先关系在添加新的关系)

int deleteObject(Integer userId);

1.5.18.1.2 Mapper 文件中元素定义

在SysUserMapper中添加修改元素

<update id="updateObject">

        update sys_users

        <set>

           <if test="username!=null and username!=''">

             username=#{username},

           </if>

           <if test="password!=null and password!=''">

             password=#{password},

           </if>

           <if test="mobile!=null and mobile!=''">

             mobile=#{mobile},

           </if>

           <if test="email!=null and email!=''">

             email=#{email},

           </if>

           <if test="modifiedUser!=null and modifiedUser!=''">

            modifiedUser=#{modifiedUser},

           </if>

            modifiedTime=now()

        </set>

        where id=#{id}

   </update>

在SysUserRoleMapper中添加删除元素定义

<delete id="deleteObject">

      

         delete from sys_roles_users

         where user_id=#{userId}

         

</delete>

1.5.18.1.3 Service中方法定义及实现

在SysUserService接口中定义方法并在对应的实现类中实现方法:

@Override

 public int updateObject(SysUser entity,

String roleIds) {

 //1.参数业务验证

 if(entity==null)

     throw new ServiceException("更新对象不能为空");

 if(entity.getId()==null)

 throw new ServiceException("更新用户时id不能为空");

 if(StringUtils.isEmpty(roleIds))

 throw new ServiceException("用户角色不能为空");

 //2.更新数据

 //2.1 更新用户基本信息

 int rows=sysUserDao.updateObject(entity);

 //2.2删除用户角色关系数据

 sysUserRoleDao.deleteObject(entity.getId());

 //2.3重新建立关系

 sysUserRoleDao.insertObject(entity.getId(),

 roleIds.split(","));

return rows;

}

1.5.18.1.4 Controller中方法定义

在SysUserController中定义修改方法

@RequestMapping("doUpdateObject")

@ResponseBody

public JsonResult doUpdateObject(

SysUser entity,

String roleIds){

entity.setModifiedUser("admin");

sysUserService.updateObject(entity, roleIds);

return new JsonResult(1, "OK");

}

1.5.18.2 客户端设计与实现

1.5.18.2.1 按钮事件注册

$(".box-footer")

        .on("click",".btn-cancel",doCancel)

        .on("click",".btn-save",doSaveOrUpdate);

1.5.18.2.2 按钮事件处理

添加cancel事件处理函数

   function doCancel(){

    //异步加载列表页面

    $(".container-fluid")

    .load("user/listUI.do");

    //移除绑定的id

    $(".container-fluid")

    .removeData("id");

    }

添加保存按钮事件处理函数

function doSaveOrUpdate(){

    //1.获取表单数据

    //1.1获取表单用户填写的数据

    var params=getFormData();

    //1.2假如是修改则需要传递用户id

    var id=$(".container-fluid").data("id");

    if(id)params.id=id;

    //2.异步提交数据

    //2.1定义url

    var insertUrl="user/doSaveObject.do";

    var updateUrl="user/doUpdateObject.do";

    //2.2根据id的值判定添加还是修改

    var url=id?updateUrl:insertUrl;

    $.post(url,params,function(result){

    if(result.state==1){

    alert(result.message);

    doCancel();

    }else{

    alert(result.message);

    }

    });

    }

京淘

权限管理系统

菜单管理

Day20

1.5.19 菜单列表模块

菜单管理是对系统中的菜单(例如一个添加,修改等)进行管理的一个业务模块.包括对菜单的CRUD操作.

1.5.19.1 业务描述

用户点击页面左侧菜单管理,显示如下页面

点击菜单名称可展开,例如点击系统管理

1.5.19.2 服务端设计及实现

1.5.19.2.1 创建表sys_menus

CREATE TABLE `sys_menus` (

  `id` int(11) NOT NULL AUTO_INCREMENT,

  `name` varchar(50) DEFAULT NULL COMMENT '资源名称',

  `url` varchar(200) DEFAULT NULL COMMENT '资源URL',

  `type` int(11) DEFAULT NULL COMMENT '类型     1:菜单   2:按钮',

  `sort` int(11) DEFAULT NULL COMMENT '排序',

  `note` varchar(100) DEFAULT NULL COMMENT '备注',

  `parentId` int(11) DEFAULT NULL COMMENT '父菜单ID,一级菜单为0',

  `permission` varchar(500) DEFAULT NULL COMMENT '授权(如:role:create)',

  `createdTime` datetime DEFAULT NULL COMMENT '创建时间',

  `modifiedTime` datetime DEFAULT NULL COMMENT '修改时间',

  `createdUser` varchar(20) DEFAULT NULL COMMENT '创建用户',

  `modifiedUser` varchar(20) DEFAULT NULL COMMENT '修改用户',

  PRIMARY KEY (`id`)

) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8 ;

向表中写入数据

insert into sys_menus values

(null,'系统管理',null,1,1,'系统管理…',0,'sys:view',now(),now(),'admin','admin');

insert into sys_menus values

(null,'角色管理',null,1,1,'角色管理…',1,'role:view',now(),now(),'admin','admin');

insert into sys_menus values

(null,'用户管理',null,1,1,'用户管理…',1,'role:view',now(),now(),'admin','admin');

1.5.19.2.2 DAO接口及方法定义

public interface SysMenuDao {

List<Map<String,Object>> findObjects();

}

1.5.19.2.3 Mapper元素定义

创建SysMenuMapper,并添加select元素,实现菜单信息的获取

<mapper namespace="com.jt.sys.dao.SysMenuDao">

<select id="findObjects" resultType="map">

Select m.*,

    (select p.name from sys_menus p where p.id = m.parentId) as

parentName

from sys_menus m

</select>

</mapper>

1.5.19.2.4 Service 中方法的定义

定义SysMenuService接口以及实现类,并添加findObjects方法

@Service

public class SysMenuServiceImpl implements SysMenuService {

@Autowired

private SysMenuDao menuDao;

@Override

public List<Map<String, Object>> findObjects() {

return menuDao.findObjects();

}

}

1.5.19.2.5 Controller中方法定义

@Controller

@RequestMapping("/menu/")

public class SysMenuController {

@Resource

private SysMenuService menuService;

@RequestMapping("listUI")

public String listUI(){

return "sys/menu_list";

}

@RequestMapping("doFindObjects")

@ResponseBody

public JsonResult doFindObjects(){

List<Map<String, Object>> list =menuService.findObjects();

return new JsonResult(1, "ok", list);

}

}

1.5.19.3 客户端设计及实现

1.5.19.3.1 定义menu_list.html页面

重点关注table元素定义

<div class="box-body table-responsive no-padding">

<table id="menuTable" class="table table-hover">

<thead>

   <tr>

<th data-field="selectItem" data-checkbox="true"></th>

  </tr>

</thead>

</table>

</div>

页面中添加js

<script type="text/javascript" src="bower_components/treegrid/jquery.treegrid.extension.js"/>

<script type="text/javascript" src="bower_components/treegrid/jquery.treegrid.min.js"/>

<script type="text/javascript" src="bower_components/treegrid/tree.table.js"/>

<script type="text/javascript">

/**

 * 初始化表格的列

 */

var colunms = [

{

field : 'selectItem',

radio : true

},

{

title : '菜单ID',

field : 'id',

visible : false,

align : 'center',

valign : 'middle',

width : '80px'

},

{

title : '菜单名称',

field : 'name',

align : 'center',

valign : 'middle',

sortable : true,

width : '180px'

},

{

title : '上级菜单',

field : 'parentName',

align : 'center',

valign : 'middle',

sortable : true,

width : '180px'

},

{

title : '类型',

field : 'type',

align : 'center',

valign : 'middle',

sortable : true,

width : '100px',

formatter : function(item, index) {

if (item.type == 1) {

return '<span class="label label-success">菜单</span>';

}

if (item.type == 2) {

return '<span class="label label-warning">按钮</span>';

}

}

},

{

title : '排序号',

field : 'sort',

align : 'center',

valign : 'middle',

sortable : true,

width : '100px'

},

{

title : '菜单URL',

field : 'url',

align : 'center',

valign : 'middle',

sortable : true,

width : '160px'

},

{

title : '授权标识',

field : 'permission',

align : 'center',

valign : 'middle',

sortable : true

} ];

$(function(){

  doGetObjects();

});

function doGetObjects() {

var tableId="menuTable";

var table = new TreeTable(tableId,"menu/doFindObjects.do", colunms);

table.setExpandColumn(2);

table.init();

}

</script>

说明:本案例中会用到treeGrid库,需要将相应的js引入到当前页面

1.5.19.3.2 设置start.html页面

在此页面的菜单管理选项上注册点击事件,点击时加载菜单列表页面

   $("#load-menu-id").click(function(){

     // console.log("hello world");

     $(".container-fluid").load("menu/listUI.do",function(){

     $(".container-fluid").removeData("id");

     });

      })

1.5.20 菜单删除模块

1.5.20.1 服务端设计及实现

1.5.20.1.1 Dao接口方法定义

SysMenuDao接口中添加如下两个方法

public interface SysMenuDao {

int hasChild(Integer menuId);

int deleteObject(Integer id);

}

从业务角度在对菜单进行删除操作时,只能删除树结构中的叶子节点(没有子的菜单的节点).

从技术角度实现上,在执行删除操作应先查询这个菜单下有没有菜单,可以

通过对子元素个数进行统计实现.假如统计结果为0表示没有子元素,这样

就可以对菜单进行删除操作了.

1.5.20.1.2 Mapper中元素定义

查询要删除的菜单是否有子菜单

<select id="hasChild" resultType="int">

select count(*)

from  sys_menus

where parentId = #{menuId}

</select>

删除菜单按钮

<delete id="deleteObject">

delete from sys_menus where id = #{id}

</delete>

1.5.20.1.3 Service接口中方法定义

SysMenuService 接口及实现类方法定义及实现

@Override

public void deleteObject(Integer menuId) {

if(menuId==null)

throw new ServiceException("删除菜单,菜单id不能为空!");

int count = menuDao.hasChild(menuId);

if(count!=0)

throw new ServiceException("请先删除子菜单或按钮!");

int i = menuDao.deleteObject(menuId);

if(i!=1)throw new ServiceException("删除菜单失败!");

}

1.5.20.1.4 Controller中方法定义

@RequestMapping("doDeleteObject")

@ResponseBody

public JsonResult doDeleteObject(Integer menuId){

menuService.deleteObject(menuId);

return new JsonResult();

}

1.5.20.2 客户端设计及实现

1.5.20.2.1 删除按钮事件注册

页面加载完成,采用如下语句注册事件

$('.input-group-btn').on('click','.btn-delete',doDeleteObject)

1.5.20.2.2 删除按钮事件处理

  /*获得选中的id*/

  function getSelectedId(){

//1.1 获得选中的对象,默认返回值为一个对象数组

var selections=$("#menuTable")

.bootstrapTreeTable("getSelections");

if(selections.length==0){

 return -1;//表示没选择任何对象

}

//1.2获得选中数组中下标为0的元素id的值

return selections[0].id;

  }

  //删除菜单项

  function doDeleteObject(){

var menuId=getSelectedId();

if(menuId==-1){

alert("请先选择");

return;

}

var params = {'menuId':menuId};

var url = 'menu/doDeleteObject.do';

$.post(url,params,function(result){

if(result.state==1){

alert('删除成功!');

doGetObjects();

}else{

alert(result.message);

}

});

}

1.5.21 菜单添加页面设计

1.5.21.1 业务描述

点击菜单列表页面的添加按钮进入如下页面:

点击上级菜单时,弹出树结构,选择具体选项

1.5.21.2 页面呈现

1.5.21.2.1 Controller方法定义

@RequestMapping("editUI")

public String editUI(){

return "sys/menu_edit";

}

1.5.21.2.2 定义添加页面

在WEB-INF/pages/sys/menu_edit.html,在对应div添加如下form元素

<form class="form-horizontal">

        <div class="box-body">

          <div class="form-group">

            <label for="nameId" class="col-sm-2 control-label">类型</label>

            <div class="col-sm-10">

              <label class="radio-inline">

<input type="radio" name="typeId" value="1" checked> 菜单 </label>

<label class="radio-inline">

<input type="radio" name="typeId" value="2"> 按钮 </label>

            </div>

          </div>

          <div class="form-group">

            <label for="nameId" class="col-sm-2 control-label">菜单名称</label>

            <div class="col-sm-10">

              <input type="text" class="form-control"  id="nameId" placeholder="名称">

            </div>

          </div>

          <div class="form-group">

            <label for="parentId" class="col-sm-2 control-label">上级菜单</label>

            <div class="col-sm-10">

              <input type="text" class="form-control load-sys-menu" readonly="readonly" id="parentId" placeholder="上级菜单">

            </div>

          </div>

          <div class="form-group">

            <label for="nameId" class="col-sm-2 control-label">菜单URL</label>

            <div class="col-sm-10">

              <input type="text" class="form-control"  id="urlId" placeholder="url">

            </div>

          </div>

          <div class="form-group">

<label for="permissionId" class="col-sm-2 control-label">授权标识:</label>

<div class="col-sm-10">

<input type="text" id="permissionId"

       placeholder="多个用逗号分隔,如:user:list,user:create"

   class="form-control">

</div>

  </div>

  <div class="form-group">

<label for="sortId" class="col-sm-2 control-label">排序号:</label>

<div class="col-sm-10">

<input type="text" id="sortId" placeholder="排序号"

class="form-control">

</div>

 </div>

        </div>

        <!-- /.box-body -->

        <div class="box-footer">

          <button type="button" class="btn btn-default btn-cancel">Cancel</button>

          <button type="button" class="btn btn-info pull-right btn-save">Save</button>

        </div>

        <!-- /.box-footer -->

      </form>

1.5.21.2.3 Js定义

在sys_menu.html文件中定义,页面加载事件

  $(".input-group-btn").on("click",".btn-add",doLoadEditPage)

事件处理函数

function doLoadEditPage(){

var title="添加菜单"

var url="menu/editUI.do";

$(".container-fluid").load(url,function(){

    $(".box-title").html(title);

});

}

1.5.22 上级菜单操作

1.5.22.1 服务端设计及实现

1.5.22.1.1 定义Node对象封装树节点

定义一个值对象,用于封装zTree上节点信息

public class Node {

private Integer id;

private Integer parentId;

private String name;

    //set/get

}

不写这个类可以吗?可以(借助SysMenu或map都可以封装)

1.5.22.1.2 DAO操作

在SysMenuDao中定义查询节点的方法

List<Node> findZtreeNodes();

1.5.22.1.3 Mapper操作

在SysUserMapper中定义查询节点的信息

<select id="findZtreeNodes" resultType="node">

select

id,

name,

parentId

from

sys_menus

</select>

1.5.22.1.4 Service操作

在SysUserService接口及实现类中定义查询节点的方。

@Override

public List<Node> findZtreeNodes() {

// TODO Auto-generated method stub

return menuDao.findZtreeNodes();

}

1.5.22.1.5 Controller操作

@RequestMapping("treeUI")

@ResponseBody

public JsonResult treeUI(){

List<Node> list =

menuService.findZtreeNodes();

return new JsonResult(1,"ok",list);

}

1.5.22.2 客户端设计与实现

1.5.22.2.1 编辑页面添加DIV元素

在编辑页面menu_edit.html添加用于呈现树结构的div元素,例如

<div class="layui-layer layui-layer-page layui-layer-molv layer-anim" id="menuLayer" type="page" times="2" showtime="0" contype="object"

style="z-index:59891016; width: 300px; height: 450px; top: 100px; left: 500px; display:none">

<div class="layui-layer-title" style="cursor: move;">选择菜单</div>

<div class="layui-layer-content" style="height: 358px;">

<div style="padding: 10px;" class="layui-layer-wrap">

<ul id="menuTree" class="ztree"></ul>    <!-- 动态加载树 -->

</div>

</div>

<span class="layui-layer-setwin"> <a class="layui-layer-ico layui-layer-close layui-layer-close1 btn_cancle" ></a></span>

<div class="layui-layer-btn layui-layer-btn-">

<a class="layui-layer-btn0 btn-confirm">确定</a>

<a class="layui-layer-btn1 btn-cancle">取消</a>

</div>

</div>

在这个页面呈现数据时需要导入ztree和layer库,然后放到bower_components这个目录下

在start.html页面引入CSS,在当前html页面引入js。

例如:

<link rel="stylesheet" 

href="bower_components/layer/skin/default/layer.css">

<link rel="stylesheet"

href="bower_components/ztree/css/metroStyle/metroStyle.css"/>

在编辑页面后面引入

<script type="text/javascript" src="bower_components/ztree/jquery.ztree.all.min.js"></script>

<script type="text/javascript"

 src="bower_components/layer/layer.js"></script>

1.5.22.2.2 上级菜单选项注册事件

事件注册

$('.form-horizontal')

.on('click','.load-sys-menu',doLoadZTreeNodes);

1.5.22.2.3 上级菜单事件处理

定义配置信息

var zTree;

var setting = {

data : {

simpleData : {

enable : true,

idKey : "id",  //节点数据中保存唯一标识的属性名称

pIdKey : "parentId",  //节点数据中保存其父节点唯一标识的属性名称

rootPId : null  //根节点id

}

}

}

定义事件处理函数

 

 function doLoadZTreeNodes(){

  $('#menuLayer').css('display','block');

  var url ='menu/treeUI.do';

  $.getJSON(url,function(result){

  if(result.state==1){

  zTree = $.fn.zTree.init($("#menuTree"),setting,result.data);

  }else{

  alert(result.message);

  }

  })

  }

点击ZTree确定按钮,执行

  function doSetSelectedNode(){

 var selectedNodes = zTree.getSelectedNodes();

 var node = selectedNodes[0];

 $('#menuLayer').css('display','none');

 $('#parentId').val(node.name);

 $('.form-horizontal').data('parentId',node.id);

}

1.5.23 菜单数据保存

1.5.23.1 服务端设计与实现

1.5.23.1.1 Entity 类定义

在pojo包中定义SysMenu类,用于封装数据

public class SysMenu implements Serializable{

private static final long serialVersionUID = 6880195210216766454L;

private Integer id;

private String name;

private String url;

private int type;

private int sort;

private String note;

private Integer parentId;

private String permission;

private Date createdTime;

private Date modifiedTime;

private String createdUser;

private String modifiedUser;

    //set/get

}

1.5.23.1.2 Dao 方法定义及实现

int insertObject(SysUser entity);

1.5.23.1.3 Service方法定义及实现

在SysMenuService接口及实现类中定义saveObject方法

@Override

public int saveObject(SysMenu entity) {

if(entity==null)

throw new ServiceException("保存的数据不能为空");

int rows=sysMenuDao.insertObject(entity);

if(rows!=1)

throw new ServiceException("数据保存失败");

return rows;

}

1.5.23.1.4 Controller方法定义及实现

在SysMenuController中定义doSaveObject方法

@RequestMapping("doSaveObject")

@ResponseBody

public JsonResult doSaveObject(SysMenu entity){

sysMenuService.saveObject(entity);

return new JsonResult(1, "save ok");

}

1.5.23.2 客户端设计与实现

1.5.23.2.1 保存按钮事件注册

  $('.form-horizontal')

  .on('click','.btn-save',doSaveOrUpdate)

1.5.23.2.2 保存按钮事件处理

获取表单数据

  function getFormData(){

  var params={

type:$("input[name='typeId']:checked").val(),

name:$("#nameId").val(),

parentId: $('.form-horizontal').data('parentId'),

url:$("#urlId").val(),

permission:$("#permissionId").val(),

sort:$("#sortId").val()

  }

  return params;

  }

提交表单数据

function doSaveOrUpdate(){

  //1.获取表单数据

  var params=getFormData();

  //2.异步提交表单数据

  var url= menu/doSaveObject.do;

  $.post(url,params,function(result){

  if(result.state==1){

  alert(result.message);

  doCancel();

  }else{

  alert(result.message);

  }

  });

  }

1.5.24 修改页面加载

修改时要根据用户选择的id查询数据库,然后将结果填充在表单中.

1.5.24.1 服务端实现

1.5.24.1.1 Dao方法定义

定义根据id查询的方法

Map<String,Object>

findObjectById(Integer id);

1.5.24.1.2 Mapper元素定义

根据id 查询菜单信息.

<select id="findObjectById"

           resultType="map">

           select m.*,p.name parentName

           from sys_menus m left join sys_menus p

           on m.parentId=p.id

           where m.id=#{id}

</select>

1.5.24.1.3 Service方法定义

根据id执行业务查询操作.

@Override

public Map<String, Object> findObjectById(Integer id) {

if(id==null||id<1)

throw new ServiceException("id的值无效");

Map<String,Object> map=

sysMenuDao.findObjectById(id);

return map;

}

1.5.24.1.4 Controller方法定义

@RequestMapping("treeUI")

@ResponseBody

public JsonResult treeUI(){

List<Node> list=sysMenuService.findZtreeNodes();

return new JsonResult(1,"ok",list);

}

1.5.24.2 客户端实现

1.5.24.2.1 列表页面操作

在列表页面点击修改时加载编辑页面(和添加页面共用一个)

修改按钮事件注册:

  $(".input-group-btn")

  .on("click",".btn-add,.btn-update",doLoadEditPage)

修改按钮事件处理:

function doLoadEditPage(){

var title;

if($(this).hasClass("btn-add")){

title="添加菜单";

}else if($(this).hasClass("btn-update")){

title="修改菜单";

var id=getSelectedId();

if(id==-1){

alert("请先选择");

return;

}

$(".container-fluid").data("id",id);

}

var url="menu/editUI.do";

$(".container-fluid").load(url,function(){

    $(".box-title").html(title);

});

}

1.5.24.2.2 编辑页面操作

在编辑页面加载完成以后根据id的值发异步获取查询数据库

初始化页面数据.

  $(function(){

  var id=$(".container-fluid").data("id");

  //假如id有值说明是修改,然后根据id查找菜单信息

  if(id){

  doFindObjectById(id);

  }

  });

根据id查找

  function doFindObjectById(id){

  var url="menu/doFindObjectById.do";

  var params={"id":id};

  $.getJSON(url,params,function(result){

  if(result.state==1){

  doInitFormData(result.data)

  }else{

  alert(result.message);

  }

  })

  }

通过数据初始化表单

  $(function(){

  $('.form-horizontal')

  .on('click','.load-sys-menu',doLoadZTreeNodes)

  .on('click','.btn-save',doSaveOrUpdate)

  

  $('#menuLayer')

  .on('click','.btn-confirm',doSetSelectedNode)

  .on('click','.btn-cancle',doHideTree);

  

  var id=$(".container-fluid").data("id");

  //假如id有值说明是修改,然后根据id查找菜单信息

  if(id){

  doFindObjectById(id);

  }

  });

1.5.25 修改页面数据保存

1.5.25.1 服务端设计与实现

1.5.25.1.1 Dao方法定义

在SysMenuDao接口中定义修改数据的方法

int updateObject(SysMenu entity);

1.5.25.1.2 Mapper元素定义

在SysMenuMapper中定义修改元素

   <update id="updateObject">

        update sys_menus

        set

        name=#{name},type=#{type},url=#{url},

        sort=#{sort},parentId=#{parentId},

        permission=#{permission}

        where id=#{id}

   </update>

1.5.25.1.3 Service方法定义

在SysMenuService接口及实现类中定义修改方法

@Override

public int updateObject(SysMenu entity) {

if(entity==null)

throw new ServiceException("更新的数据不能为空");

int rows=sysMenuDao.updateObject(entity);

if(rows!=1)

throw new ServiceException("数据更新失败");

return rows;

}

1.5.25.1.4 Controller方法定义

在SysMenuController中定义修改方法

@RequestMapping("doUpdateObject")

@ResponseBody

public JsonResult doUpdateObject(SysMenu entity){

sysMenuService.updateObject(entity);

return new JsonResult(1, "update ok");

}

1.5.25.2 客户端设计及实现

1.5.25.2.1 获取表单数据异步更新

function doSaveOrUpdate(){

  //1.获取表单数据

  var params=getFormData();

  var id=$(".container-fluid").data("id");

  if(id)params.id=id;

  console.log(params);

  //2.异步提交表单数据

  var insertUrl="menu/doSaveObject.do";

  var updateUrl="menu/doUpdateObject.do";

  var url=id?updateUrl:insertUrl;

  $.post(url,params,function(result){

  if(result.state==1){

  alert(result.message);

  doCancel();

  }else{

  alert(result.message);

  }

  });

  }

退出时移除绑定的id

  function doCancel(){

  $(".container-fluid").load("menu/listUI.do");

  $(".container-fluid").removeData("id")

  }

京淘项目

1.6  权限管理&Shiro安全框架

 

1.6.1 权限管理简介

1.6.1.1 权限管理概述

权限管理属于系统安全的范畴,是实现对用户访问系统的控制,可以按照安全规则或者安全策略控制用户可以访问而且只能访问自己被授权的资源。

权限管理包括用户身份认证和授权两部分,简称认证授权。对于需要访问控制的资源用户首先经过身份认证,认证通过后用户具有该资源的访问权限方可访问。

1.6.1.2 用户身份认证

1.6.1.2.1 认证概述

身份认证,就是判断一个用户是否为合法用户。最常用的简单身份认证方式是系统通过核对用户输入的用户名和口令,看其是否与系统中存储的该用户的用户名和口令一致,来判断用户身份是否正确。对于采用指纹等系统,则出示指纹;对于硬件Key等刷卡系统,则需要刷卡。

1.6.1.2.2 认证流程

   在实际项目中有些资源用户可以直接访问,有些资源必须对用户什么进行认证(检测用户的合法性),认证通过则可以访问,认证失败则不允许访问.

1.6.1.2.3 关键术语

Subject:主体(被要求认证和授权管理的主体)

访问系统的用户,主体可以是用户、程序等,进行认证的都称为主体;

Principal:身份信息

是主体(subject)进行身份认证的标识,标识必须具有唯一性,如用户名、手机号、邮箱地址等,

一个主体可以有多个身份,但是必须有一个主身份(Primary Principal)。

credential:凭证信息

是只有主体自己知道的安全信息,如密码、证书等。

1.6.1.3 用户身份授权

1.6.1.3.1 授权概述

授权,即访问控制,控制谁能访问哪些资源。主体进行身份认证后需要分配权限方可访问系统的资源,对于某些资源没有权限是无法访问的。

1.6.1.3.2 授权流程

1.6.1.3.3 关键术语

授权一般指允许who(Subject)对what(Resource)执行how(Permission)操作。

Resource:系统中的资源,一般会对应一个Url,这个url的表现一般为菜单。

Permission:对资源的访问标识(例如user:add,user:update,user:delete,user:view)

主题,资源,权限在权限管理系统中通常会划分为不同的模块,例如其描述图如下:

1.6.1.4 权限设计模型

权限设计模型如图所示:

实际应用中经常还会将权限和资源合为一张表:权限(权限名称、资源名称、资源访问地址)

1.6.1.5 权限访问控制

基于角色的访问控制(RBAC -Role-Based Access Control):扩展性相对较差

基于资源的访问控制(RBAC-Resource-Based Access Control):扩展性比较好

在使用RBAC时,实际应用中又分粗粒度权限控制和细粒度的权限控制,粗粒度一般指对资源类型的访问控制(例如用户可以导出所有订单明细),细粒度一般对具体某个资源实例的访问控制(例如只能导出自己工资信息),对于粗粒度的权限控制可以使用过滤器或spring拦截器都可以,对于细粒度的权限控制一般都会在业务层进行相关判定。

1.6.2 Shiro安全框架

1.6.2.1 Shiro基础

1.6.2.1.1 Shiro 概述

Shiro是apache旗下一个开源安全框架,它将软件系统的安全认证相关的功能抽取出来,实现用户身份认证,权限授权、加密、会话管理等功能,组成了一个通用的安全认证框架,使用shiro就可以非常快速的完成认证、授权等功能的开发,降低系统成本。

课后了解:Spring security 安全框架

1.6.2.1.2 Shiro 基本架构

通过Shiro框架进行权限管理时,要涉及到的一些核心对象,主要包括:

认证管理对象,授权管理对象,会话管理对象,缓存管理对象,加密管理对象

以及Realm管理对象(领域对象:负责处理认证和授权领域的数据访问题)

1.6.2.1.3 Shiro 认证流程

具体流程分析如下:

1、首先调用Subject.login(token)进行登录,其会自动委托给Security Manager,调用之前必须通过SecurityUtils. setSecurityManager()设置;

2、SecurityManager负责真正的身份验证逻辑;它会委托给Authenticator进行身份验证;

3、Authenticator才是真正的身份验证者,Shiro API中核心的身份认证入口点,此处可以自定义插入自己的实现;

4、Authenticator可能会委托给相应的AuthenticationStrategy进行多Realm身份验证,默认ModularRealmAuthenticator会调用AuthenticationStrategy进行多Realm身份验证;

5、Authenticator会把相应的token传入Realm,从Realm获取身份验证信息,如果没有返回/抛出异常表示身份验证失败了。此处可以配置多个Realm,将按照相应的顺序及策略进行访问。

1.6.2.1.4 Shiro 授权流程

1.6.2.2 Shiro 应用实践

本讲的shiro应用主要讲解shiro是集成到spring如何实现权限控制

1.6.2.2.1 添加shiro依赖

假设本项目spring配置已经基本实现

  <dependency>

  <groupId>org.apache.shiro</groupId>

  <artifactId>shiro-spring</artifactId>

  <version>1.3.2</version>

  </dependency>

  <dependency>

    <groupId>org.apache.shiro</groupId>

    <artifactId>shiro-ehcache</artifactId>

   <version>1.3.2</version>

  </dependency>

1.6.2.2.2 构建Realm

通过Realm实现基本认证及权限控制

public class ShiroUserRealm extends AuthorizingRealm{

@Resource

private SysUserDao sysUserDao;

@Override

protected AuthorizationInfo doGetAuthorizationInfo(

PrincipalCollection principals) {

System.out.println("==doGetAuthorizationInfo==");

SysUser user = (SysUser) SecurityUtils.getSubject()

.getSession().getAttribute("currentUser");

int userId = user.getId();

List<String> permsList = new ArrayList<>();

permsList = sysUserDao.findUserPermissions(userId);

//用户权限列表

Set<String> permsSet = new HashSet<String>();

for(String perm : permsList){

if(perm!=null && !("".equals(perm))){

permsSet.add(perm);

}

}

System.out.println("permsSet="+permsSet);

SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();

info.setStringPermissions(permsSet);

return info;

}

@Override

protected AuthenticationInfo doGetAuthenticationInfo(

AuthenticationToken token) throws AuthenticationException {

        System.out.println("==doGetAuthenticationInfo==");

//1.  AuthenticationToken 转换为 UsernamePasswordToken

UsernamePasswordToken upToken = (UsernamePasswordToken) token;

//2.  UsernamePasswordToken 中来获取 username

String username = upToken.getUsername();

//判断用户名是否存在,若存在,返回user对象

SysUser user =sysUserDao.findObjectByName(username);

//盐值.

ByteSource credentialsSalt = ByteSource.Util.bytes(user.getSalt());

//自动完成密码比对   - 密码的比对:

//通过 AuthenticatingRealm  credentialsMatcher 属性来进行的密码的比对!

SimpleAuthenticationInfo info =

new SimpleAuthenticationInfo(

username, user.getPassword(),credentialsSalt,getName());

SecurityUtils.getSubject().getSession()

.setAttribute("currentUser",user);

return info;

}

}

1.6.2.2.3 登录认证Service实现

@Service

public class SysShiroServiceImpl  implements SysShiroService {

@Override

public void login(String username, String password) {

Subject subject = SecurityUtils.getSubject();

if(subject.isAuthenticated())return;

// 把用户名和密码封装为 UsernamePasswordToken 对象

        UsernamePasswordToken token =

        new UsernamePasswordToken(username, password);

        try{//登录认证 - 调用userRealm

        subject.login(token);

        }catch (IncorrectCredentialsException ice) {

        throw new ServiceException("密码错误!");

        } catch(AuthenticationException ae){

        ae.printStackTrace();

        throw new ServiceException("认证失败");

        }

}

}

1.6.2.2.4 登录认证Controller实现

@Controller

public class SysLoginController {

@Autowired

private SysShiroService loginService;

@RequestMapping("/loginUI")

public String login(){

return "login";

}

/**登录操作*/

@RequestMapping("/login")

@ResponseBody

public JsonResult login(String username,String password){

System.out.println(username+"/"+password);

    loginService.login(username, password);

return new JsonResult();

}

}

1.6.2.2.5 Shiro 注解应用

Shiro中通过使用@RequiresPermissions注解实现对某些方法的标识权限标识,例如

@RequiresPermissions(“role:edit”)

@RequestMapping("editUI")

public String editUI(){

return "sys/role_edit";

}

1.6.2.2.6 修改Web.xml

<!-- spring整合shiro安全框架 -->

<filter>

        <filter-name>DelegatingFilterProxy</filter-name>

        <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>

        <!-- 初始化参数 -->

        <init-param>

            <param-name>targetBeanName</param-name>

            <param-value>shiroFilter</param-value>

        </init-param>

</filter>

<filter-mapping>

      <filter-name>DelegatingFilterProxy</filter-name>

      <url-pattern>/*</url-pattern>

</filter-mapping>

1.6.2.2.7 添加spring-shiro配置文件

<!-- shiro工厂bean配置 -->

     <bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">

         <!-- shiro的核心安全接口 -->

         <property name="securityManager" ref="securityManager"/>

          <!-- 要求登录时的连接 -->

        <property name="loginUrl" value="/toLogin.do"></property>

         <!-- 登录成功后要跳转的连接(此处已经在登录中处理了) -->

         <!-- <property name="successUrl" value="/index.jsp"></property> -->

         <!-- 访问未对其授权的资源时,要跳转的连接 

         <property name="unauthorizedUrl" value="/default.html"></property>-->

         <!-- shiro连接约束配置 -->

         <property name="filterChainDefinitions">

             <value>

                 <!-- 对静态资源设置允许匿名访问 -->

                 /images/** = anon

                 /js/** = anon

                 /css/** = anon

                 /static/** = anon

                 /bootstrap/** = anon

                 /jquery/** = anon

                 <!-- 可匿名访问路径,例如:验证码、登录连接、退出连接等 -->

                 /confirmUser.do = anon

                 <!-- 退出 -->

                 /logout.do = logout  <!-- 会调用Subjectlogout方法,此方法会将session清空 -->

                 <!-- 剩余其他路径,必须认证通过才可以访问 -->

                 /** = authc

             </value>

         </property>

     </bean>

     

     <!-- 配置shiro安全管理器 -->

     <bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">

         <property name="realm" ref="userRealm"></property>

     </bean>

     

     <!-- 自定义Realm -->

    <bean id="userRealm" class="com.jt.system.service.ShiroUserRealm">

    <!-- 配置凭证算法匹配器 -->

    <property name="credentialsMatcher">

    <bean class="org.apache.shiro.authc.credential.HashedCredentialsMatcher">

    <property name="hashAlgorithmName" value="MD5"/>

    <!-- <property name="hashIterations" value="1024"/> -->

    </bean>

    </property>

    </bean>

<!--Shiro生命周期处理器-->

<bean id="lifecycleBeanPostProcessor" class="org.apache.shiro.spring.LifecycleBeanPostProcessor"/>

<!--启用shiro注解权限检查-->

<bean class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator"

    depends-on="lifecycleBeanPostProcessor"/>

<bean  class="org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor">

    <property name="securityManager" ref="securityManager"/>

</bean>

1.7 Spring 事务管理

1.7.1 Spring 事务管理

1.7.1.1 Spring 事务概述

事务是一个不可分割的逻辑工作单元,具备ACID特性,实际工作中可借助Spring进行事务管理。

事务四大特性:ACID

原型子型(一个事务中的多个操作要么都成功要么都失败)

一致性(例如存钱操作,存之前和存之前的钱数应该是一致的)

隔离性(事务与事务应该是相互隔离的)

持久性(事务一旦提交,数据要持久保存)

Spring提供了两种事务管理方式, 编程式事务和声明式事务。编程式事务指的是通过编码方式实现事务;声明式事务基于 AOP,将具体业务逻辑与事务处理解耦。声明式事务管理使业务代码逻辑不受污染, 因此在实际使用中声明式事务用的比较多。

Spring中声明式事务处理有两种方式,一种是在配置文件(xml)中做相关的事务规则声明,另一种是基于@Transactional 注解的方式。

本讲重点讲解实际项目中最常用的声明式事务管理,以简化事务的编码操作。

1.7.1.2 Spring事务案例分析

例如现有两个订单操作,需要更新库存。

当库存充足时两个事务都可以成功,当库存不够时有的事务就要回滚。

说明:Spring声明式事务管理底层基于AOP实现

1.7.2 Spring 声明式事务处理

1.7.2.1 基于注解方式实现

Step1:在spring配置文件中启用事务注解

<bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">

<property name="dataSource" ref="dataSource"/>

</bean>

<!--设置注解驱动的事务管理  -->

<tx:annotation-driven transaction-manager="txManager"/>

Step2:在类或方法中使用@Transaction注解应用事务。

name 当在配置文件中有多个 TransactionManager , 可以用该属性指定选择哪个事务管理器。

propagation 事务的传播行为,默认值为 REQUIRED。

isolation 事务的隔离度,默认值采用 DEFAULT。

timeout 事务的超时时间,默认值为-1。如果超过该时间限制但事务还没有完成,则自动回滚事务。

read-only 指定事务是否为只读事务,默认值为 false;为了忽略那些不需要事务的方法,比如读取数据,可以设置 read-only 为 true。

rollback-for 用于指定能够触发事务回滚的异常类型,如果有多个异常类型需要指定,各类型之间可以通过逗号分隔。

no-rollback- for 抛出 no-rollback-for 指定的异常类型,不回滚事务。

说明:@Transactional 注解可以用在方法上也可以添加到类级别上。当把@Transactional 注解放在类级别时,表示所有该类的公共方法都配置相同的事务属性信息。见清单 2,EmployeeService 的所有方法都支持事务并且是只读。当类级别配置了@Transactional,方法级别也配置了@Transactional,应用程序会以方法级别的事务属性信息来管理事务,换言之,方法级别的事务属性信息会覆盖类级别的相关配置信息。

1.7.2.2 基于xml方式实现

在配置文件中通过xml配置方式实现声明式事务管理。

配置事务管理器

<bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">

<property name="dataSource" ref="dataSource"/>

</bean>

配置事务处理方式

<tx:advice id="txAdvice"  transaction-manager="txManager">

<tx:attributes>

<tx:method name="*"

propagation="REQUIRED"

isolation="READ_COMMITTED"

timeout="-1"

read-only="false"

rollback-for="java.lang.Throwable"

no-rollback-for=”NoTransactionException"/>

</tx:attributes>

</tx:advice>

<aop:config>

<aop:pointcut id="operation" expression="execution(* beans.service..*.*(..))"/>

<aop:advisor advice-ref="txAdvice" pointcut="operation"/>

</aop:config>

课堂练习:

Step01:定义事务管理器

<bean id="transactionManager"

class="org.springframework.jdbc.datasource.DataSourceTransactionManager">

<property name="dataSource" ref="dataSource" />

</bean>

 

定义事务策略

 

<tx:advice id="txAdvice" transaction-manager="transactionManager">

<tx:attributes>

<!--定义查询方法都是只读的 -->

<tx:method name="query*" read-only="true" />

<tx:method name="find*" read-only="true" />

<tx:method name="get*" read-only="true" />

<!-- 主库执行操作,事务传播行为定义为默认行为 -->

<tx:method name="save*" propagation="REQUIRED" />

<tx:method name="update*" propagation="REQUIRED" />

<tx:method name="delete*" propagation="REQUIRED" />

<!--其他方法使用默认事务策略 -->

<tx:method name="*" />

</tx:attributes>

</tx:advice>

 

<aop:config>

<!-- 定义切面,所有的service的所有方法 -->

<aop:pointcut id="txPointcut"

 expression="execution(* com.jt.sys.service.*.*(..))" />

<!-- 应用事务策略到Service切面 -->

<aop:advisor advice-ref="txAdvice" pointcut-ref="txPointcut"/>

</aop:config>

1.7.3 Spring事务增强

1.7.3.1 Spring 事务的传播特性

事务传播特性:事务方法之间相互调用时,事务的传播方式.

重点掌握 Propagation.REQUIRED

@Transactional(propagation=Propagation.REQUIRED) 如果没有事务创建新事务, 如果当前有事务参与当前事务

@Transactional(propagation=Propagation.REQUIRES_NEW)

必须是新事务, 如果有当前事务, 挂起当前事务并且开启新事务.

课后了解:

@Transactional(propagation=Propagation.MANDATORY)必须有事务, 如果当前没有事务就抛异常

@Transactional(propagation=Propagation.NEVER)绝对不能有事务, 如果在事务中调用则抛出异常

@Transactional(propagation=Propagation.NESTED)必须被嵌套到其他事务中

@Transactional(propagation=Propagation.NOT_SUPPORTED)不支持事务

@Transactional(propagation=Propagation.SUPPORTS)支持事务, 如果没有事务也不会创建新事务

1.7.3.2 Spring 事务的隔离级别

思考:多个事务并发执行时可能会导致什么问题?(脏读,不可重复读,幻读)

当多个事务并发执行时,可通过设置事务的隔离级别保证事务的完整性,一致性。

事务的隔离级别从低到高有如下几种方式:

1)READ_UNCOMMITTED (此级别可能会出现脏读)

2)READ_COMMITTED(此级别可能会出现不可重复读)

3)REPEATABLE_READ(此级别可能会出现幻读)

4)SERIALIZABLE(多事务串行执行)

说明:spring中一般采用 @Transactional(isolation=Isolation.READ_COMMITTED) 方式声明级别, 这种方式是并发性能和安全性折中的选择. 是大多数软件项目采用的隔离级别.

回顾MySQL中隔离级别:

查看InnoDB存储引擎 系统级的隔离级别 和 会话级的隔离级别

更改会话级的隔离级别

更改系统级的隔离级别

思考:

1)MySQL 中如何查看当前系统默认隔离级别?

show variables like '%storage_engine%';

2)MySQL 中如何设置事务隔离级别?

set session transaction isolation level 'reapable read'

1.8 AJAX

1.8.1 Ajax 概述

1.8.1.1 Ajax 是什么

Ajax 即 Asynchronous Javascript  And  XML,是WEB应用程序开发的一种方法,它使用客户端脚本与web 服务器交互数据,并由最初的全局页面同步加载刷新升级为异步局部加载刷新方式动态刷新页面。

1.8.1.2 Ajax 解决了什么问题

页面加载性能问题(刷新局部页面快还是整个页面快?)

用户体验问题()

1.8.1.3 Ajax 应用模型

传统的web同步模型。

Ajax异步Web应用模型:

1.8.2 Ajax 编程实现

1.8.2.1 Ajax 编程步骤

获取Ajax 请求对象

发送Ajax异步请求

获取Ajax异步数据

局部刷新页面数据

1.8.2.2 Ajax编程实现

1.8.2.2.1 创建Ajax 请求对象

function getRequestObject() {

if (window.XMLHttpRequest) {

// Opera, Safari, Mozilla, Chrome,Internet Explorer 7, and IE 8.

return(new XMLHttpRequest());

} else if (window.ActiveXObject) {

// Version for Internet Explorer 5.5 and 6

return(new ActiveXObject("Microsoft.XMLHTTP"));

} else {

//Fails on older and nonstandard browsers.

return(null);

}

}

1.8.2.2.2 初始化Ajax 请求

function sendRequest() {

var request = getRequestObject();

//Code to call when server responds

request.onreadystatechange =

function() { handleResponse(request) };

//URL of server-side resource,true is send request asynchronously

request.open("GET", "message-data.html", true);

//always null for get request

request.send(null);

}

1.8.2.2.3 Ajax响应处理

function handleResponse(request) {

// 4 means response from server is complete

if (request.readyState == 4&&request.status==200) {

alert(request.responseText);

//htmlInsert(resultRegion, request.responseText);

}

}

function htmlInsert(id, htmlData) {

document.getElementById(id).innerHTML = htmlData;

}

1.8.2.3 Ajax 编程增强

1.8.2.3.1 Ajax 异步GET请求

function showTimeInCity(inputField, resultRegion) {

var baseAddress = "show-time-in-city";

var data = "city=" + getValue(inputField);

var address = baseAddress + "?" + data;

ajaxResult(address, resultRegion);

}

function ajaxResult(address, resultRegion) {

var request = getRequestObject();

request.onreadystatechange =

function() { showResponseText(request,

resultRegion); };

request.open("GET", address, true);

request.send(null);

}

function getValue(id) {

return(escape(document.getElementById(id).value));

}

function showResponseText(request, resultRegion) {

if ((request.readyState == 4) &&

(request.status == 200)) {

htmlInsert(resultRegion, request.responseText);

}

}

1.8.2.3.2 Ajax post请求

function showTimeInCityPost(inputField, resultRegion) {

var address = "show-time-in-city";

var data = "city=" + getValue(inputField);

ajaxResultPost(address, data, resultRegion);

}

function ajaxResultPost(address, data, resultRegion) {

var request = getRequestObject();

request.onreadystatechange =

function() { showResponseText(request,

resultRegion); };

request.open("POST", address, true);

request.setRequestHeader("Content-Type","application/x-www-form-urlencoded");

request.send(data);

}

function showResponseText(request, resultRegion) {

if ((request.readyState == 4) &&(request.status == 200)) {

htmlInsert(resultRegion, request.responseText);

}

}

1.8.3 JQuery中的Ajax应用

1.8.3.1 load函数应用

$("#time-result-1").load("server-time.jsp");

1.8.3.2 ajax 函数应用

ajax 函数应用:相关选项说明

$.ajax({ url: "address",

success: successHandlerFunction,

data: { param1: "foo bar", param2: "baz“ },

error: errorHandlerFunction,

cache: false,

dataType: "json"

}

Ajax 函数应用案例

$(function() {

$("#data-button-2").click(processAjaxRequest);

});

Function  processAjaxRequest () {

var params =

{ param1: $("#field3").val(),

param2: $("#field4").val() };

$.ajax({ url: "show-params.jsp",

data: params,

success: showAlert });

}

1.8.3.3 getJson函数应用

$.getJSON("url", dataObj, someFunct)//回调函数

1.8.3.4 post 函数应用

$.post("url", dataObj, someFunct)  //回调函数

猜你喜欢

转载自blog.csdn.net/qq_40680190/article/details/80634375
今日推荐