Java高级框架
2021-01-14 09:13:14 0 举报
AI智能生成
Git,Maven,Spring,SpringMVC,Mybatis,及一些前端框架和定时任务框架
作者其他创作
大纲/内容
MyBatis
配置Mybatis步骤
1.在pom.xml中导入岁需要的jar包
2.在resource里面新建mybatis-config.xml文件
在内部配置好jdbc路径,
<configuration>
<properties resource="jdbc.properties"></properties>
<typeAliases>
<!--定义类的别名-->
<!--自动扫描包,将原类名作为别名-->
<package name="com.qf.entiy" />
<package name="com.qf.Dao"/>
</typeAliases>
<plugins>
<!-- com.github.pagehelper为PageHelper类所在包名 -->
<plugin interceptor="com.github.pagehelper.PageInterceptor"></plugin>
</plugins>
<!--JDBC环境配置、选中默认环境-->
<environments default="MySqlDB">
<!--MySql数据库环境配置-->
<environment id="MySqlDB">
<!--事务管理-->
<transactionManager type="JDBC"/>
<!--连接池-->
<dataSource type="com.qf.datasource.MyDruidDataSource">
<property name="driverClass" value="${jdbc.driver}"/>
<property name="jdbcUrl" value="${jdbc.url}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
</dataSource>
</environment>
</environments>
<!--定义别名二选一-->
<!--Mapper注册-->
<mappers>
<!--注册Mapper文件的所在位置-->
<!-- <mapper resource="com\qf\Dao\Mapper.xml"/>-->
<!-- <mapper resource="com\qf\Dao\orderDao.xml" />-->
<!-- <mapper resource="com\qf\Dao\DogDao.xml"/>-->
<!-- <mapper resource="com\qf\Dao\passengerDao.xml"/>-->
<!-- <mapper resource="com\qf\Dao\passportDao.xml"/>-->
<!-- <mapper resource="com\qf\Dao\DepartmentDao.xml"/>-->
<mapper resource="mapper/UserDao.xml"/>
</mappers>
</configuration>
<properties resource="jdbc.properties"></properties>
<typeAliases>
<!--定义类的别名-->
<!--自动扫描包,将原类名作为别名-->
<package name="com.qf.entiy" />
<package name="com.qf.Dao"/>
</typeAliases>
<plugins>
<!-- com.github.pagehelper为PageHelper类所在包名 -->
<plugin interceptor="com.github.pagehelper.PageInterceptor"></plugin>
</plugins>
<!--JDBC环境配置、选中默认环境-->
<environments default="MySqlDB">
<!--MySql数据库环境配置-->
<environment id="MySqlDB">
<!--事务管理-->
<transactionManager type="JDBC"/>
<!--连接池-->
<dataSource type="com.qf.datasource.MyDruidDataSource">
<property name="driverClass" value="${jdbc.driver}"/>
<property name="jdbcUrl" value="${jdbc.url}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
</dataSource>
</environment>
</environments>
<!--定义别名二选一-->
<!--Mapper注册-->
<mappers>
<!--注册Mapper文件的所在位置-->
<!-- <mapper resource="com\qf\Dao\Mapper.xml"/>-->
<!-- <mapper resource="com\qf\Dao\orderDao.xml" />-->
<!-- <mapper resource="com\qf\Dao\DogDao.xml"/>-->
<!-- <mapper resource="com\qf\Dao\passengerDao.xml"/>-->
<!-- <mapper resource="com\qf\Dao\passportDao.xml"/>-->
<!-- <mapper resource="com\qf\Dao\DepartmentDao.xml"/>-->
<mapper resource="mapper/UserDao.xml"/>
</mappers>
</configuration>
3.创建所需要的mapper.xml文件在内部书写sql等
4.书写好的Mapper.xml文件的路径在第二部中进行注册
注意:书写类名路径的情况要根据第二步骤中书声明的typeAliases标签下的内容,mapper.xml文件的存放路径如果在dao层下我们将书写整个路径名,并在pom.xml中配置相关属性
<build>
<resources>
<resource>
<directory>src/main/java</directory>
<includes>
<include>*.xml</include><!-- 默认(新添加自定义则失效) -->
<include>**/*.xml</include><!-- 新添加 */代表1级目录 **/代表多级目录 -->
</includes>
<filtering>true</filtering>
</resource>
</resources>
</build>
<resources>
<resource>
<directory>src/main/java</directory>
<includes>
<include>*.xml</include><!-- 默认(新添加自定义则失效) -->
<include>**/*.xml</include><!-- 新添加 */代表1级目录 **/代表多级目录 -->
</includes>
<filtering>true</filtering>
</resource>
</resources>
</build>
MyBatis的CURD操作
查询
标签
< select id="" resultType="" >
序号参数绑定
WHERE id = #{param1} AND password = #{param2} <!--param1 param2 param3 ...-->
注解参数绑定【推荐】
public User selectUserByIdAndPwd(@Param("id") Integer id , @Param("pwd") String pwd);
Map参数绑定
Map values = new HashMap(); //测试类创建Map
values.put("myId",1); //自定义key,绑定参数
values.put("myPwd","123456");
User user = userDao.selectUserByIdAndPwd_map(values);
values.put("myId",1); //自定义key,绑定参数
values.put("myPwd","123456");
User user = userDao.selectUserByIdAndPwd_map(values);
SELECT * FROM t_users
WHERE id = #{myId} AND password = #{myPwd} <!-- 通过key获得value -->
WHERE id = #{myId} AND password = #{myPwd} <!-- 通过key获得value -->
对象参数绑定
public User selectUserByUserInfo(User user);
对象的名字和后面#{}括号中的一致
模糊查询
SELECT * FROM t_users
WHERE name LIKE concat('%',#{keyword},'%') <!-- 拼接'%' -->
WHERE name LIKE concat('%',#{keyword},'%') <!-- 拼接'%' -->
删除
标签:< delete id="" parameterType="" >
DELETE FROM t_users
WHERE id = #{id} <!--只有一个参数时,#{任意书写}-->
WHERE id = #{id} <!--只有一个参数时,#{任意书写}-->
修改
< update id="" parameterType="" >
UPDATE t_users SET name=#{name}, password=#{password}, sex=#{sex}, birthday=#{birthday}
WHERE id = #{id} <!--方法参数为对象时,可直接使用#{属性名}进行获取-->
WHERE id = #{id} <!--方法参数为对象时,可直接使用#{属性名}进行获取-->
添加
< insert id="" parameterType="" >
<!--手动主键-->
<insert id="insertUser" parameterType="user">
INSERT INTO t_users VALUES(#{id},#{name},#{password},#{sex},#{birthday},NULL);
</insert>
<insert id="insertUser" parameterType="user">
INSERT INTO t_users VALUES(#{id},#{name},#{password},#{sex},#{birthday},NULL);
</insert>
<!--自动主键-->
<insert id="insertUser" parameterType="user">
<!-- 自动增长主键,以下两种方案均可 -->
INSERT INTO t_users VALUES(#{id},#{name},#{password},#{sex},#{birthday},NULL);
INSERT INTO t_users VALUES(NULL,#{name},#{password},#{sex},#{birthday},NULL);
</insert>
<insert id="insertUser" parameterType="user">
<!-- 自动增长主键,以下两种方案均可 -->
INSERT INTO t_users VALUES(#{id},#{name},#{password},#{sex},#{birthday},NULL);
INSERT INTO t_users VALUES(NULL,#{name},#{password},#{sex},#{birthday},NULL);
</insert>
主键回填
< selectKey id="" parameterType="" order="AFTER|BEFORE">
通过last_insert_id()查询主键
<selectKey keyProperty="id" resultType="int" order="AFTER"> <!-- 插入之后 -->
SELECT LAST_INSERT_ID() <!-- 适用于整数类型自增主键 -->
</selectKey>
SELECT LAST_INSERT_ID() <!-- 适用于整数类型自增主键 -->
</selectKey>
通过uuid查询主键
SELECT REPLACE(UUID(),'-','') <!-- 适用于字符类型主键 -->
MyBatis自动ORM失效
MyBatis只能自动维护库表”列名“与”属性名“相同时的一一对应关系,二者不同时,无法自动ORM。
解决方案
方案一:列的别名
在SQL中使用 as 为查询字段添加列别名,以匹配属性名。
方案二:结果映射(ResultMap - 查询结果的封装规则)
通过< resultMap id="" type="" >映射,匹配列名与属性名。
MyBatis处理关联关系-多表连接
实体间的关系:关联关系(拥有 has、属于 belong)
OneToOne:一对一关系(Passenger--- Passport)
OneToMany:一对多关系(Employee --- Department)
ManyToMany:多对多关系(Student --- Subject)
注意:指定“多方”关系时(集合),使用< collection ofType="" >
注意:指定“一方”关系时(对象),使用< association javaType="" >
案例
<mapper namespace="com.qf.Dao.passportDao">
<resultMap id="port_pger" type="com.qf.entiy.passport">
<id column="id" property="id"/>
<result column="nationlity" property="nationlity"/>
<result column="expire" property="expire"/>
<result column="passenger_id" property="passenger_id"/>
<association property="passenger" javaType="com.qf.entiy.passenger">
<id column="id" property="id"/>
<result column="name" property="name"/>
<result column="sex" property="sex"/>
<result column="birthday" property="birthday"/>
</association>
</resultMap>
<select id="OneToOne1" resultMap="port_pger">
select passenger.id,passenger.name,passenger.sex,passenger.birthday,passport.id pid,
passport.nationlity,passport.expire,passport.passenger_id from passenger
join passport on passenger.id=passport.passenger_id where passenger.id=#{id}
</select>
<resultMap id="port_pger" type="com.qf.entiy.passport">
<id column="id" property="id"/>
<result column="nationlity" property="nationlity"/>
<result column="expire" property="expire"/>
<result column="passenger_id" property="passenger_id"/>
<association property="passenger" javaType="com.qf.entiy.passenger">
<id column="id" property="id"/>
<result column="name" property="name"/>
<result column="sex" property="sex"/>
<result column="birthday" property="birthday"/>
</association>
</resultMap>
<select id="OneToOne1" resultMap="port_pger">
select passenger.id,passenger.name,passenger.sex,passenger.birthday,passport.id pid,
passport.nationlity,passport.expire,passport.passenger_id from passenger
join passport on passenger.id=passport.passenger_id where passenger.id=#{id}
</select>
关系总结
一方,添加集合;多方,添加对象。
双方均可建立关系属性,建立关系属性后,对应的Mapper文件中需使用< ResultMap >完成多表映射。
持有对象关系属性,使用< association property="dept" javaType="department" >
持有集合关系属性,使用< collection property="emps" ofType="employee" >
动态sql
概念
MyBatis的映射文件中支持在基础SQL上添加一些逻辑操作,并动态拼接成完整的SQL之后再执行,以达到SQL复用、简化编程的效果。
分类
<sql>
<!-- 定义SQL片段 -->
在<select>...标签中用<include>引用
<include refid="BOOKS_FIELD" />
<where>
<!-- WHERE,会自动忽略前后缀(如:and | or) -->
<!-- where子句中满足条件的if,会自动忽略后缀(如:,) -->
<trim>
< trim prefix="" suffix="" prefixOverrides="" suffixOverrides="" >代替< where > 、< set >
<!-- 增加SET前缀,自动忽略最后的【逗号】 -->
set
<!-- set子句中满足条件的if,会自动忽略后缀(如:,) -->
<foreach>
<delete id="deleteBookByIds">
DELETE FROM t_books
WHERE id IN
<foreach collection="list" open="(" separator="," close=")" item="id" index="i">
#{id}
</foreach>
</delete>
DELETE FROM t_books
WHERE id IN
<foreach collection="list" open="(" separator="," close=")" item="id" index="i">
#{id}
</foreach>
</delete>
参数
collection
容器类型
list、array、map
open
起始符
close
结束符
separator
分隔符
index
下标号
从0开始,依次递增
item
当前项
任意名称(循环中通过 #{任意名称} 表达式访问)
<if>
<delete>,<insert>,<update>
缓存(Cache)【重点】
内存中的一块存储空间,服务于某个应用程序,旨在将频繁读取的数据临时保存在内存中,便于二次快速访问。
无缓存:用户在访问相同数据时,需要发起多次对数据库的直接访问,导致产生大量IO、读写硬盘的操作,效率低下
有缓存:首次访问时,查询数据库,将数据存储到缓存中;再次访问时,直接访问缓存,减少IO、硬盘读写次数、提高效率
一级缓存
SqlSession级别的缓存,同一个SqlSession的发起多次同构查询,会将数据保存在一级缓存中。
注意:无需任何配置,默认开启一级缓存。
二级缓存
SqlSessionFactory级别的缓存,同一个SqlSessionFactory构建的SqlSession发起的多次同构查询,会将数据保存在二级缓存中。
注意:在sqlSession.commit()或者sqlSession.close()之后生效。
开启全局缓存
< settings >是MyBatis中极为重要的调整设置,他们会改变MyBatis的运行行为,其他详细配置可参考官方文档。
<settings>
<setting name="cacheEnabled" value="true"/> <!-- mybaits-config.xml中开启全局缓存(默认开启) -->
</settings>
<setting name="cacheEnabled" value="true"/> <!-- mybaits-config.xml中开启全局缓存(默认开启) -->
</settings>
指定Mapper缓存
<cache /> <!-- 指定缓存 -->
druil连接池
修改mybatis-config.xml
mybatis-config.xml中连接池相关配置。
<dataSource type="com.qf.mybatis.part2.utils.MyDruidDataSourceFactory"><!--数据源工厂-->
<property name="driverClass" value="${driver}"/>
<property name="jdbcUrl" value="${url}"/>
<property name="username" value="${username}"/>
<property name="password" value="${password}"/>
</dataSource>
<property name="driverClass" value="${driver}"/>
<property name="jdbcUrl" value="${url}"/>
<property name="username" value="${username}"/>
<property name="password" value="${password}"/>
</dataSource>
- 注意:< property name="属性名" />属性名必须与com.alibaba.druid.pool.DruidAbstractDataSource中一致。
PageHelper
概念:PageHelper是适用于MyBatis框架的一个分页插件,使用方式极为便捷,支持任何复杂的单表、多表分页查询操作。
配置MyBatis-config.xml
<plugins>
<!-- com.github.pagehelper为PageHelper类所在包名 -->
<plugin interceptor="com.github.pagehelper.PageInterceptor"></plugin>
</plugins>
<!-- com.github.pagehelper为PageHelper类所在包名 -->
<plugin interceptor="com.github.pagehelper.PageInterceptor"></plugin>
</plugins>
PageHelper应用方式
UserDao userDao = MyBatisUtils.getMapper(UserDao.class);
PageHelper.startPage(1,2);//使用PageHelper设置分页条件
PageHelper.startPage(1,2);//使用PageHelper设置分页条件
开发步骤
PageHelper中提供了多个分页操作的静态方法入口。
引入依赖
pom.xml中引入PageHelper依赖。
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper</artifactId>
<version>5.1.10</version>
</dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper</artifactId>
<version>5.1.10</version>
</dependency>
配置MyBatis-config.xml
<plugins>
<!-- com.github.pagehelper为PageHelper类所在包名 -->
<plugin interceptor="com.github.pagehelper.PageInterceptor"></plugin>
</plugins>
<!-- com.github.pagehelper为PageHelper类所在包名 -->
<plugin interceptor="com.github.pagehelper.PageInterceptor"></plugin>
</plugins>
MyBatis注解操作
通过在接口中直接添加MyBatis注解,完成CRUD。
注意:接口注解定义完毕后,需将接口全限定名注册到mybatis-config.xml的< mappers >中。
<mappers>
<mapper class="com.qf.mybatis.part1.annotations.UserMapper" /><!-- class="接口全限定名"-->
</mappers>
<mapper class="com.qf.mybatis.part1.annotations.UserMapper" /><!-- class="接口全限定名"-->
</mappers>
经验:注解模式属于硬编码到.java文件中,失去了使用配置文件外部修改的优势,可结合需求选用。
@Delete(value = "DELETE FROM t_users WHERE id = #{id}")
@Update("UPDATE t_users SET name = #{name} , password = #{password} , salary = #{salary} , birthday = #{birthday} WHERE id = #{id}")
@Insert("INSERT INTO t_users VALUES(#{id},#{name},#{password},#{salary},#{birthday},null)")
public int insertUser(User user);
public int insertUser(User user);
$符号的应用场景{重点}
${attribute} 属于字符串拼接SQL,而非预编译占位符,会有注入攻击问题,不建议在常规SQL中使用,常用于可解决动态生降序问题。
$符号参数绑定
public List<User> selectAllUsers1(User user); // ${name} ${id} 可获取user中的属性值
public List<User> selectAllUsers2(@Param("rule") String rule); //必须使用@Param否则会作为属性解析
实例
SELECT * FROM t_users
WHERE name = '${name}' or id = ${id} <!-- 拼接name和id,如果是字符类型需要用单引号:'${name}' -->
WHERE name = '${name}' or id = ${id} <!-- 拼接name和id,如果是字符类型需要用单引号:'${name}' -->
实用
SELECT * FROM t_users
ORDER BY id ${rule} <!-- 拼接 asc | desc -->
ORDER BY id ${rule} <!-- 拼接 asc | desc -->
List<User> ulist2 = userDao.selectAllUsers2("desc"); //调用时传入asc | desc
$符号注入攻击
SELECT * FROM t_user
WHERE name = '${name}' <!-- 会存在注入攻击 比如传入参数是 【String name = "tom' or '1'='1";】-->
WHERE name = '${name}' <!-- 会存在注入攻击 比如传入参数是 【String name = "tom' or '1'='1";】-->
Spring
spring引言
传统Web开发存在硬编码所造成的过度程序耦合(例如:Service中作为属性Dao对象)。
部分Java EE API较为复杂,使用效率低(例如:JDBC开发步骤)。
侵入性强,移植性差(例如:DAO实现的更换,从Connection到SqlSession)。
概念
Spring是一个项目管理框架,同时也是一套Java EE解决方案。
Spring是众多优秀设计模式的组合(工厂、单例、代理、适配器、包装器、观察者、模板、策略)。
Spring并未替代现有框架产品,而是将众多框架进行有机整合,简化企业级开发,俗称"胶水框架"。
Spring架构底层
Spring架构由诸多模块组成,可分类为
核心技术:依赖注入,事件,资源,i18n,验证,数据绑定,类型转换,SpEL,AOP。
测试:模拟对象,TestContext框架,Spring MVC测试,WebTestClient。
数据访问:事务,DAO支持,JDBC,ORM,封送XML。
Spring MVC和 Spring WebFlux Web框架。
集成:远程处理,JMS,JCA,JMX,电子邮件,任务,调度,缓存。
语言:Kotlin,Groovy,动态语言。
Spring环境搭建
pom.xml中引入Spring常用依赖
<dependencies>
<!-- Spring常用依赖 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.1.6.RELEASE</version>
</dependency>
</dependencies>
<!-- Spring常用依赖 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.1.6.RELEASE</version>
</dependency>
</dependencies>
创建Spring配置文件
命名无限制,约定俗成命名有:spring-context.xml、applicationContext.xml、beans.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
</beans>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
</beans>
Spring工厂编码
定义目标Bean类型
spring-context.xml中的< beans >内部配置bean标签
调用Spring工厂API(ApplicationContext接口)
public static void main(String[] args){
//1. 读取配置文件中所需创建的bean对象,并获得工厂对象
ApplicationContext ctx = new ClassPathXmlApplicationContext("spring-context.xml");
//2. 通过id获取bean对象
MyClass mc = (MyClass) ctx.getBean("mc");
//3. 使用对象
mc.show();
}
//1. 读取配置文件中所需创建的bean对象,并获得工厂对象
ApplicationContext ctx = new ClassPathXmlApplicationContext("spring-context.xml");
//2. 通过id获取bean对象
MyClass mc = (MyClass) ctx.getBean("mc");
//3. 使用对象
mc.show();
}
依赖与配置文件详解
Spring框架包含多个模块,每个模块各司其职,可结合需求引入相关依赖Jar包实现功能。
schema
配置文件中的顶级标签中包含了语义化标签的相关信息
xmlns:语义化标签所在的命名空间。
xmlns:xsi:XMLSchema-instance 标签遵循Schema标签标准。
xsi:schemaLocation:xsd文件位置,用以描述标签语义、属性、取值范围等。
IoC(Inversion of Control )控制反转【重点】
Inverse Of Controll:控制反转
反转了依赖关系的满足方式,由之前的自己创建依赖对象,变为由工厂推送。(变主动为被动,即反转)
解决了具有依赖关系的组件之间的强耦合,使得项目形态更加稳健
案例解释
此时,如果需要更换其他UserDAO实现类,则UserServiceImpl不用任何改动!
则此时的UserServiceImpl组件变得更加稳健!
DI(Dependency Injection)依赖注入【重点】
概念
在Spring创建对象的同时,为其属性赋值,称之为依赖注入。
Set注入
创建对象时,Spring工厂会通过Set方法为对象的属性赋值。
注意:要生产的对象一定要是线set方法,并且无参构造也要有
流程是,先通过反射创建无参对象,然后通过我们传入的值进行set注入
bean为各种类型属性进行注入
基本类型 + 字符串类型 + 日期类型
<bean id="u1" class="com.qf.spring.part1.injection.User">
<!--base field-->
<property name="id" value="1001" />
<property name="password" value="123456" />
<property name="sex" value="male" />
<property name="age" value="20" />
<property name="bornDate" value="1990/1/1" /><!--注意格式"/"-->
</bean>
<!--base field-->
<property name="id" value="1001" />
<property name="password" value="123456" />
<property name="sex" value="male" />
<property name="age" value="20" />
<property name="bornDate" value="1990/1/1" /><!--注意格式"/"-->
</bean>
容器类型
<bean id="u1" class="com.qf.spring.part1.injection.User">
<!--Array-->
<property name="hobbys">
<array>
<value>Run</value>
<value>Swim</value>
<value>Climb</value>
</array>
</property>
<!--Set-->
<property name="phones">
<set>
<value>13777777777</value>
<value>13888888888</value>
<value>13999999999</value>
</set>
</property>
<!--List-->
<property name="names">
<list>
<value>tom</value>
<value>jack</value>
<value>marry</value>
</list>
</property>
<!--Map-->
<property name="countries">
<map>
<entry key="CN" value="China" />
<entry key="US" value="America" />
<entry key="KR" value="Korea" />
</map>
</property>
<!--Properties-->
<property name="files">
<props>
<prop key="first">One</prop>
<prop key="second">Two</prop>
<prop key="third">Three</prop>
</props>
</property>
</bean>
<!--Array-->
<property name="hobbys">
<array>
<value>Run</value>
<value>Swim</value>
<value>Climb</value>
</array>
</property>
<!--Set-->
<property name="phones">
<set>
<value>13777777777</value>
<value>13888888888</value>
<value>13999999999</value>
</set>
</property>
<!--List-->
<property name="names">
<list>
<value>tom</value>
<value>jack</value>
<value>marry</value>
</list>
</property>
<!--Map-->
<property name="countries">
<map>
<entry key="CN" value="China" />
<entry key="US" value="America" />
<entry key="KR" value="Korea" />
</map>
</property>
<!--Properties-->
<property name="files">
<props>
<prop key="first">One</prop>
<prop key="second">Two</prop>
<prop key="third">Three</prop>
</props>
</property>
</bean>
自建类型
形式一,实体类中有另个实体类属性
<!--次要bean,被作为属性-->
<bean id="addr" class="com.qf.spring.part1.injection.Address">
<property name="position" value="北京市海淀区" />
<property name="zipCode" value="100001" />
</bean>
<bean id="addr" class="com.qf.spring.part1.injection.Address">
<property name="position" value="北京市海淀区" />
<property name="zipCode" value="100001" />
</bean>
<!--主要bean,操作的主体-->
<bean id="u2" class="com.qf.spring.part1.injection.User">
<property name="address" ref="addr" /><!--address属性引用addr对象-->
</bean>
<bean id="u2" class="com.qf.spring.part1.injection.User">
<property name="address" ref="addr" /><!--address属性引用addr对象-->
</bean>
构造注入【了解】
创建对象时,Spring工厂会通过构造方法为对象的属性赋值。
定义目标Bean类型
public class Student {
private Integer id;
private String name;
private String sex;
private Integer age;
//Constructors
public Student(Integer id , String name , String sex , Integer age){
this.id = id;
this.name = name;
this.sex = sex;
this.age = age;
}
}
private Integer id;
private String name;
private String sex;
private Integer age;
//Constructors
public Student(Integer id , String name , String sex , Integer age){
this.id = id;
this.name = name;
this.sex = sex;
this.age = age;
}
}
注入
<!--构造注入-->
<bean id="u3" class="com.qf.zcg.spring.day1.t2.ioc.Student">
<constructor-arg name="id" value="1234" /> <!-- 除标签名称有变化,其他均和Set注入一致 -->
<constructor-arg name="name" value="tom" />
<constructor-arg name="age" value="20" />
<constructor-arg name="sex" value="male" />
</bean>
<bean id="u3" class="com.qf.zcg.spring.day1.t2.ioc.Student">
<constructor-arg name="id" value="1234" /> <!-- 除标签名称有变化,其他均和Set注入一致 -->
<constructor-arg name="name" value="tom" />
<constructor-arg name="age" value="20" />
<constructor-arg name="sex" value="male" />
</bean>
自动注入【了解】
不用在配置中 指定为哪个属性赋值,及赋什么值.
由spring自动根据某个 "原则" ,在工厂中查找一个bean,为属性注入属性值
案例
public class UserServiceImpl implements UserService {
private UserDAO userDAO;
//Getters And Setters
....
}
private UserDAO userDAO;
//Getters And Setters
....
}
<bean id="userDao" class="com.qf.spring.part1.injection.UserDaoImpl" />
<!-- 为UserServiceImpl中的属性基于类型自动注入值 -->
<bean id="userService" class="com.qf.spring.part1.injection.UserServiceImpl" autowire="byType"></bean>
<!-- 为UserServiceImpl中的属性基于类型自动注入值 -->
<bean id="userService" class="com.qf.spring.part1.injection.UserServiceImpl" autowire="byType"></bean>
<bean id="userDao" class="com.qf.spring.part1.injection.UserDaoImpl" />
<!-- 为UserServiceImpl中的属性基于类型自动注入值 -->
<bean id="userService" class="com.qf.spring.part1.injection.UserServiceImpl" autowire="byName"></bean>
<!-- 为UserServiceImpl中的属性基于类型自动注入值 -->
<bean id="userService" class="com.qf.spring.part1.injection.UserServiceImpl" autowire="byName"></bean>
Bean细节
配置< bean scope="singleton | prototype" />
<!--
singleton(默认):每次调用工厂,得到的都是同一个对象。
prototype:每次调用工厂,都会创建新的对象。
-->
<bean id="mc" class="com.qf.zcg.spring.day1.t1.basic.MyClass" scope="singleton" />
singleton(默认):每次调用工厂,得到的都是同一个对象。
prototype:每次调用工厂,都会创建新的对象。
-->
<bean id="mc" class="com.qf.zcg.spring.day1.t1.basic.MyClass" scope="singleton" />
注意:需要根据场景决定对象的单例、多例模式。
可以共用:Service、DAO、SqlSessionFactory(或者是所有的工厂)。
不可共用:Connection、SqlSession、ShoppingCart。
FactoryBean创建复杂对象【了解】
作用:让Spring可以创建复杂对象、或者无法直接通过反射创建的对象。
实现FactoryBean接口
注意:isSingleton方法的返回值,需根据所创建对象的特点决定返回true/false。
例如:Connection 不应该被多个用户共享,返回false。
例如:SqlSessionFactory 重量级资源,不该过多创建,返回true。
注意事项
注意:isSingleton方法的返回值,需根据所创建对象的特点决定返回true/false。
例如:Connection 不应该被多个用户共享,返回false。
例如:SqlSessionFactory 重量级资源,不该过多创建,返回true。
Spring工厂特性
1 饿汉式创建优势
工厂创建之后,会将Spring配置文件中的所有单例对象都创建完成(饿汉式)。
提高程序运行效率。避免多次IO,减少对象创建时间。(概念接近连接池,一次性创建好,使用时直接获取)
提高程序运行效率。避免多次IO,减少对象创建时间。(概念接近连接池,一次性创建好,使用时直接获取)
生命周期方法
- 自定义初始化方法:添加“init-method”属性,Spring则会在创建对象之后,调用此方法。
- 自定义销毁方法:添加“destroy-method”属性,Spring则会在销毁对象之前,调用此方法。
- 销毁:工厂的close()方法被调用之后,Spring会毁掉所有已创建的单例对象。
- 分类:Singleton对象由Spring容器销毁、Prototype对象由JVM销毁。
- 自定义销毁方法:添加“destroy-method”属性,Spring则会在销毁对象之前,调用此方法。
- 销毁:工厂的close()方法被调用之后,Spring会毁掉所有已创建的单例对象。
- 分类:Singleton对象由Spring容器销毁、Prototype对象由JVM销毁。
生命周期注解 (了解)
初始化注解、销毁注解
@PostConstruct //初始化
public void init(){
System.out.println("init method executed");
}
@PreDestroy //销毁
public void destroy(){
System.out.println("destroy method executed");
}
public void init(){
System.out.println("init method executed");
}
@PreDestroy //销毁
public void destroy(){
System.out.println("destroy method executed");
}
生命周期阶段
单例bean:singleton
随工厂启动创建 ==》 构造方法 ==》 set方法(注入值) ==》 init(初始化) ==》 构建完成 ==》随工厂关闭销毁
多例bean:prototype
被使用时创建 ==》 构造方法 ==》 set方法(注入值) ==》 init(初始化) ==》 构建完成 ==》JVM垃圾回收销毁
代理设计模式
概念
将核心功能与辅助功能(事务、日志、性能监控代码)分离,达到核心业务功能更纯粹、辅助业务功能可复用。
静态代理设计模式
通过代理类的对象,为原始类的对象(目标类的对象)添加辅助功能,更容易更换代理实现类、利于维护。
代理类 = 实现原始类相同接口 + 添加辅助功能 + 调用原始类的业务方法。
静态代理的问题
代理类数量过多,不利于项目的管理。
多个代理类的辅助功能代码冗余,修改时,维护性差。
动态代理设计模式
动态创建代理类的对象,为原始类的对象添加辅助功能。
JDK动态代理实现(基于接口)
CGlib动态代理实现(基于继承)
面向切面编程【重点】
概念
AOP(Aspect Oriented Programming),即面向切面编程,利用一种称为"横切"的技术,剖开封装的对象内部,并将那些影响了多个类的公共行为封装到一个可重用模块,并将其命名为"Aspect",即切面。所谓"切面",简单说就是那些与业务无关,却为业务模块所共同调用的逻辑或责任封装起来,便于减少系统的重复代码,降低模块之间的耦合度,并有利于未来的可操作性和可维护性。
AOP开发术语
- 连接点(Joinpoint):连接点是程序类中客观存在的方法,可被Spring拦截并切入内容。
- 切入点(Pointcut):被Spring切入连接点。
- 通知、增强(Advice):可以为切入点添加额外功能,分为:前置通知、后置通知、异常通知、环绕通知等。
- 目标对象(Target):代理的目标对象
- 引介(Introduction):一种特殊的增强,可在运行期为类动态添加Field和Method。
- 织入(Weaving):把通知应用到具体的类,进而创建新的代理类的过程。
- 代理(Proxy):被AOP织入通知后,产生的结果类。
- 切面(Aspect):由切点和通知组成,将横切逻辑织入切面所指定的连接点中。
- 切入点(Pointcut):被Spring切入连接点。
- 通知、增强(Advice):可以为切入点添加额外功能,分为:前置通知、后置通知、异常通知、环绕通知等。
- 目标对象(Target):代理的目标对象
- 引介(Introduction):一种特殊的增强,可在运行期为类动态添加Field和Method。
- 织入(Weaving):把通知应用到具体的类,进而创建新的代理类的过程。
- 代理(Proxy):被AOP织入通知后,产生的结果类。
- 切面(Aspect):由切点和通知组成,将横切逻辑织入切面所指定的连接点中。
作用
Spring的AOP编程即是通过动态代理类为原始类的方法添加辅助功能。
环境搭建
引入AOP相关依赖
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.1.6.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>5.1.6.RELEASE</version>
</dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.1.6.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>5.1.6.RELEASE</version>
</dependency>
spring-context.xml引入AOP命名空间
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd
">
</beans>
xmlns:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd
">
</beans>
开发流程
定义原始类
定义通知类(添加额外功能)
定义bean标签
定义切入点(PointCut)
形成切面(Aspect)
<aop:config>
<!--切点-->
<aop:pointcut id="myPointCut" expression="execution(* save())" />
<!--组装切面 -->
<aop:advisor advice-ref="myAdvice" pointcut-ref="myPointCut" />
</aop:config>
<!--切点-->
<aop:pointcut id="myPointCut" expression="execution(* save())" />
<!--组装切面 -->
<aop:advisor advice-ref="myAdvice" pointcut-ref="myPointCut" />
</aop:config>
AOP小结
- 通过AOP提供的编码流程,更便利的定制切面,更方便的定制了动态代理。
- 进而彻底解决了辅助功能冗余的问题;
- 业务类中职责单一性得到更好保障;
- 辅助功能也有很好的复用性。
- 进而彻底解决了辅助功能冗余的问题;
- 业务类中职责单一性得到更好保障;
- 辅助功能也有很好的复用性。
通知类【可选】
定义通知类,达到通知效果
前置通知:MethodBeforeAdvice
后置通知:AfterAdvice
后置通知:AfterReturningAdvice //有异常不执行,方法会因异常而结束,无返回值
异常通知:ThrowsAdvice
环绕通知:MethodInterceptor
后置通知:AfterAdvice
后置通知:AfterReturningAdvice //有异常不执行,方法会因异常而结束,无返回值
异常通知:ThrowsAdvice
环绕通知:MethodInterceptor
通配切入点
根据表达式通配切入点
日常用法
<!-- public int com.qianfeng.service.UserServiceImpl.queryUser(int,String,com.entity.User) -->
<!--匹配参数-->
<aop:pointcut id="myPointCut" expression="execution(* *(com.qf.aaron.aop.basic.User))" />
<!--匹配方法名(无参)-->
<aop:pointcut id="myPointCut" expression="execution(* save())" />
<!--匹配方法名(任意参数)-->
<aop:pointcut id="myPointCut" expression="execution(* save(..))" />
<!--匹配返回值类型-->
<aop:pointcut id="myPointCut" expression="execution(com.qf.aaron.aop.basic.User *(..))" />
<!--匹配类名-->
<aop:pointcut id="myPointCut" expression="execution(* com.qf.aaron.aop.basic.UserServiceImpl.*(..))" />
<!--匹配包名-->
<aop:pointcut id="myPointCut" expression="execution(* com.qf.aaron.aop.basic.*.*(..))" />
<!--匹配包名、以及子包名-->
<aop:pointcut id="myPointCut" expression="execution(* com.qf.aaron.aop..*.*(..))" />
<!--匹配参数-->
<aop:pointcut id="myPointCut" expression="execution(* *(com.qf.aaron.aop.basic.User))" />
<!--匹配方法名(无参)-->
<aop:pointcut id="myPointCut" expression="execution(* save())" />
<!--匹配方法名(任意参数)-->
<aop:pointcut id="myPointCut" expression="execution(* save(..))" />
<!--匹配返回值类型-->
<aop:pointcut id="myPointCut" expression="execution(com.qf.aaron.aop.basic.User *(..))" />
<!--匹配类名-->
<aop:pointcut id="myPointCut" expression="execution(* com.qf.aaron.aop.basic.UserServiceImpl.*(..))" />
<!--匹配包名-->
<aop:pointcut id="myPointCut" expression="execution(* com.qf.aaron.aop.basic.*.*(..))" />
<!--匹配包名、以及子包名-->
<aop:pointcut id="myPointCut" expression="execution(* com.qf.aaron.aop..*.*(..))" />
JDK和CGLIB选择
- spring底层,包含了jdk代理和cglib代理两种动态代理生成机制
- 基本规则是:目标业务类如果有接口则用JDK代理,没有接口则用CGLib代理
- 如果配置true:<aop:config proxy-target-class="true">,则用CGLIB代理
- 基本规则是:目标业务类如果有接口则用JDK代理,没有接口则用CGLib代理
- 如果配置true:<aop:config proxy-target-class="true">,则用CGLIB代理
后处理器
概念
- spring中定义了很多后处理器;
- 每个bean在创建完成之前 ,都会有一个后处理过程,即再加工,对bean做出相关改变和调整;
- spring-AOP中,就有一个专门的后处理器,负责通过原始业务组件(Service),再加工得到一个代理组件。
- 每个bean在创建完成之前 ,都会有一个后处理过程,即再加工,对bean做出相关改变和调整;
- spring-AOP中,就有一个专门的后处理器,负责通过原始业务组件(Service),再加工得到一个代理组件。
后处理器定义
/**
* 定义bean后处理器
* 作用:在bean的创建之后,进行再加工
*/
public class MyBeanPostProcessor implements BeanPostProcessor{
/**
* 在bean的init方法之前执行
* @param bean 原始的bean对象
* @param beanName
* @return
* @throws BeansException
*/
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
System.out.println("后处理器 在init之前执行~~~"+bean.getClass());
return bean;
}
/**
* 在bean的init方法之后执行
* @param bean postProcessBeforeInitialization返回的bean
* @param beanName
* @return
* @throws BeansException
*/
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
System.out.println("后处理器 在init之后执行~~~"+bean.getClass());
return bean;// 此处的返回是 getBean() 最终的返回值
}
}
* 定义bean后处理器
* 作用:在bean的创建之后,进行再加工
*/
public class MyBeanPostProcessor implements BeanPostProcessor{
/**
* 在bean的init方法之前执行
* @param bean 原始的bean对象
* @param beanName
* @return
* @throws BeansException
*/
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
System.out.println("后处理器 在init之前执行~~~"+bean.getClass());
return bean;
}
/**
* 在bean的init方法之后执行
* @param bean postProcessBeforeInitialization返回的bean
* @param beanName
* @return
* @throws BeansException
*/
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
System.out.println("后处理器 在init之后执行~~~"+bean.getClass());
return bean;// 此处的返回是 getBean() 最终的返回值
}
}
配置后处理器
<!-- 配置后处理器,将对工厂中所有的bean声明周期进行干预 -->
<bean class="com.qianfeng.beanpostprocessor.MyBeanPostProcessor"></bean>
<bean class="com.qianfeng.beanpostprocessor.MyBeanPostProcessor"></bean>
bean生命周期
构造 》 注入属性 满足依赖 》 后处理器前置过程 》 初始化 》后处理器后置过程 》 返回 》 销毁
动态代理源码(了解)
// AbstractAutoProxyCreator是 AspectJAwareAdvisorAutoProxyCreator的父类
// 该后处理器类中的 wrapIfNecessary方法即动态代理生成过程
AbstractAutoProxyCreator#postProcessAfterInitialization(Object bean, String beanName){
if (!this.earlyProxyReferences.contains(cacheKey)) {
// 开始动态定制代理
return wrapIfNecessary(bean, beanName, cacheKey);
}
}
// 该后处理器类中的 wrapIfNecessary方法即动态代理生成过程
AbstractAutoProxyCreator#postProcessAfterInitialization(Object bean, String beanName){
if (!this.earlyProxyReferences.contains(cacheKey)) {
// 开始动态定制代理
return wrapIfNecessary(bean, beanName, cacheKey);
}
}
Spring-Mybatis整合
将mybatis-config.xml文件整合到spring-context.xml
1.导入外部文件:<context:property-placeholder location="classpath:jdbc.properties"/>
2.数据库连接:
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close">
<property name="driverClassName" value="${jdbc.driver}"/>
<property name="password" value="${jdbc.password}"/>
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.username}"/>
<!-- 配置初始化大小、最小、最大 -->
<property name="initialSize" value="1"/>
<property name="minIdle" value="1"/>
<property name="maxActive" value="3"/>
<!-- 配置获取连接等待超时的时间 -->
<property name="maxWait" value="60000"/>
<!-- 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒 -->
<property name="timeBetweenEvictionRunsMillis" value="60000"/>
<!-- 配置一个连接在池中最小生存的时间,单位是毫秒 -->
<property name="minEvictableIdleTimeMillis" value="300000"/>
</bean>
<property name="driverClassName" value="${jdbc.driver}"/>
<property name="password" value="${jdbc.password}"/>
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.username}"/>
<!-- 配置初始化大小、最小、最大 -->
<property name="initialSize" value="1"/>
<property name="minIdle" value="1"/>
<property name="maxActive" value="3"/>
<!-- 配置获取连接等待超时的时间 -->
<property name="maxWait" value="60000"/>
<!-- 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒 -->
<property name="timeBetweenEvictionRunsMillis" value="60000"/>
<!-- 配置一个连接在池中最小生存的时间,单位是毫秒 -->
<property name="minEvictableIdleTimeMillis" value="300000"/>
</bean>
3.加载mybatis的核心配置
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<!-- 连接池 -->
<property name="dataSource" ref="dataSource"/>
<!-- 注册mapper -->
<property name="mapperLocations" value="classpath:mapper/*.xml"/>
<!-- 别名 -->
<property name="typeAliasesPackage" value="com.qianfeng.entity"/>
</bean>
<!-- 连接池 -->
<property name="dataSource" ref="dataSource"/>
<!-- 注册mapper -->
<property name="mapperLocations" value="classpath:mapper/*.xml"/>
<!-- 别名 -->
<property name="typeAliasesPackage" value="com.qianfeng.entity"/>
</bean>
4.<!-- DAO
.1. 扫描所有 DAO接口
.2. 创建实现类
3. 将DAO实现 放入工厂 id=首字母小写的接口名 "orderDAO"
依赖:
1. 知道 DAO接口位置
-->
.1. 扫描所有 DAO接口
.2. 创建实现类
3. 将DAO实现 放入工厂 id=首字母小写的接口名 "orderDAO"
依赖:
1. 知道 DAO接口位置
-->
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="basePackage" value="com.qianfeng.dao"/>
<property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"/>
</bean>
<!-- Service -->
<bean id="orderService" class="com.qianfeng.service.OrderServiceImpl">
<property name="orderDAO" ref="orderDAO"/>
</bean>
<property name="basePackage" value="com.qianfeng.dao"/>
<property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"/>
</bean>
<!-- Service -->
<bean id="orderService" class="com.qianfeng.service.OrderServiceImpl">
<property name="orderDAO" ref="orderDAO"/>
</bean>
事务操作
<!-- 事务管理器 -->
<bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
<!-- 事务增强(Advice) -->
<tx:advice id="txAdvice" transaction-manager="txManager" >
<!-- 为需要事务的方法 设置事务属性 -->
<tx:attributes>
<tx:method name="queryUser" propagation="SUPPORTS" read-only="true"/>
<tx:method name="insertUser" propagation="REQUIRED" read-only="false" rollback-for="Exception"/>
</tx:attributes>
</tx:advice>
<!-- 编织: 组装 -->
<aop:config>
<aop:pointcut id="tx_pc" expression="execution(* queryUser(..))"/>
<aop:pointcut id="tx_p1" expression="execution(* *(com.qf.entiy.User))"/>
<aop:advisor advice-ref="txAdvice" pointcut-ref="tx_pc"/>
<aop:advisor advice-ref="txAdvice" pointcut-ref="tx_p1"/>
</aop:config>
<bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
<!-- 事务增强(Advice) -->
<tx:advice id="txAdvice" transaction-manager="txManager" >
<!-- 为需要事务的方法 设置事务属性 -->
<tx:attributes>
<tx:method name="queryUser" propagation="SUPPORTS" read-only="true"/>
<tx:method name="insertUser" propagation="REQUIRED" read-only="false" rollback-for="Exception"/>
</tx:attributes>
</tx:advice>
<!-- 编织: 组装 -->
<aop:config>
<aop:pointcut id="tx_pc" expression="execution(* queryUser(..))"/>
<aop:pointcut id="tx_p1" expression="execution(* *(com.qf.entiy.User))"/>
<aop:advisor advice-ref="txAdvice" pointcut-ref="tx_pc"/>
<aop:advisor advice-ref="txAdvice" pointcut-ref="tx_p1"/>
</aop:config>
Spring + MyBatis【重点】
配置数据源
将数据源配置到项目中
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.16</version>
</dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.16</version>
</dependency>
引入jdbc.properties配置文件
#jdbc.properties
jdbc.driverClass=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/mydb?useUnicode=true&characterEncoding=UTF-8
jdbc.username=root
jdbc.password=123456
jdbc.driverClass=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/mydb?useUnicode=true&characterEncoding=UTF-8
jdbc.username=root
jdbc.password=123456
事务【重点】
配置DataSourceTransactionManager
事务管理器,其中持有DataSource,可以控制事务功能(commit,rollback等)。
<!-- 1. 引入一个事务管理器,其中依赖DataSource,借以获得连接,进而控制事务逻辑 -->
<bean id="tx" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"></property>
</bean>
<bean id="tx" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"></property>
</bean>
注意:DataSourceTransactionManager 和 SqlSessionFactoryBean 要注入同一个DataSource的Bean,否则事务控制失败!!!
配置事务通知
基于事务管理器,进一步定制,生成一个额外功能:Advice。
此Advice可以切入任何需要事务的方法,通过事务管理器为方法控制事务。
此Advice可以切入任何需要事务的方法,通过事务管理器为方法控制事务。
<tx:advice id="txManager" transaction-manager="tx">
<tx:attributes>
<!--<tx:method name="insertUser" rollback-for="Exception" isolation="DEFAULT"
propagation="REQUIRED" read-only="false"/>-->
<!-- 以User结尾的方法,切入此方法时,采用对应事务实行-->
<tx:method name="*User" rollback-for="Exception"/>
<!-- 以query开头的方法,切入此方法时,采用对应事务实行 -->
<tx:method name="query*" propagation="SUPPORTS"/>
<!-- 剩余所有方法 -->
<tx:method name="*"/>
</tx:attributes>
</tx:advice>
<tx:attributes>
<!--<tx:method name="insertUser" rollback-for="Exception" isolation="DEFAULT"
propagation="REQUIRED" read-only="false"/>-->
<!-- 以User结尾的方法,切入此方法时,采用对应事务实行-->
<tx:method name="*User" rollback-for="Exception"/>
<!-- 以query开头的方法,切入此方法时,采用对应事务实行 -->
<tx:method name="query*" propagation="SUPPORTS"/>
<!-- 剩余所有方法 -->
<tx:method name="*"/>
</tx:attributes>
</tx:advice>
事务属性
隔离级别
隔离级别由低到高为:read-uncommited < read-commited < repeatable-read < serialized-read
isolation 隔离级别
属性
default
(默认值)(采用数据库的默认的设置) (建议)
(默认值)(采用数据库的默认的设置) (建议)
read-uncommited
读未提交
读未提交
read-commited
读提交 (Oracle数据库默认的隔离级别)
读提交 (Oracle数据库默认的隔离级别)
repeatable-read
可重复读 (MySQL数据库默认的隔离级别)
可重复读 (MySQL数据库默认的隔离级别)
serialized-read
序列化读
序列化读
特性
- 安全性:级别越高,多事务并发时,越安全。因为共享的数据越来越少,事务间彼此干扰减少。
- 并发性:级别越高,多事务并发时,并发越差。因为共享的数据越来越少,事务间阻塞情况增多。
- 并发性:级别越高,多事务并发时,并发越差。因为共享的数据越来越少,事务间阻塞情况增多。
并发问题
事务并发时的安全问题
脏读
一个事务读取到另一个事务还未提交的数据。大于等于 read-commited 可防止
不可重复读
一个事务内多次读取一行数据的相同内容,其结果不一致。大于等于 repeatable-read 可防止
幻影读
一个事务内多次读取一张表中的相同内容,其结果不一致。serialized-read 可防止
一个事务读取到另一个事务还未提交的数据。大于等于 read-commited 可防止
不可重复读
一个事务内多次读取一行数据的相同内容,其结果不一致。大于等于 repeatable-read 可防止
幻影读
一个事务内多次读取一张表中的相同内容,其结果不一致。serialized-read 可防止
传播行为
propagation传播行为
当涉及到事务嵌套(Service调用Service)时,可以设置:
- SUPPORTS = 不存在外部事务,则不开启新事务;存在外部事务,则合并到外部事务中。(适合查询)
- REQUIRED = 不存在外部事务,则开启新事务;存在外部事务,则合并到外部事务中。 (默认值)(适合增删改)
- SUPPORTS = 不存在外部事务,则不开启新事务;存在外部事务,则合并到外部事务中。(适合查询)
- REQUIRED = 不存在外部事务,则开启新事务;存在外部事务,则合并到外部事务中。 (默认值)(适合增删改)
读写性
readonly 读写性
- true:只读,可提高查询效率。(适合查询)
- false:可读可写。 (默认值)(适合增删改)
- false:可读可写。 (默认值)(适合增删改)
事务超时
timeout事务超时时间
当前事务所需操作的数据被其他事务占用,则等待。
- 100:自定义等待时间100(秒)。
- -1:由数据库指定等待时间,默认值。(建议)
- 100:自定义等待时间100(秒)。
- -1:由数据库指定等待时间,默认值。(建议)
事务回滚
rollback-for 回滚属性
- 如果事务中抛出 RuntimeException,则自动回滚
- 如果事务中抛出 CheckException(非运行时异常 Exception),不会自动回滚,而是默认提交事务
- 处理方案 : 将CheckException转换成RuntimException上抛,或 设置 rollback-for="Exception"
- 如果事务中抛出 CheckException(非运行时异常 Exception),不会自动回滚,而是默认提交事务
- 处理方案 : 将CheckException转换成RuntimException上抛,或 设置 rollback-for="Exception"
编织
将事务管理的Advice 切入需要事务的业务方法中
<aop:config>
<aop:pointcut expression="execution(* com.qf.spring.service.UserServiceImpl.*(..))" id="pc"/>
<!-- 组织切面 -->
<aop:advisor advice-ref="txManager" pointcut-ref="pc"/>
</aop:config>
<aop:pointcut expression="execution(* com.qf.spring.service.UserServiceImpl.*(..))" id="pc"/>
<!-- 组织切面 -->
<aop:advisor advice-ref="txManager" pointcut-ref="pc"/>
</aop:config>
注解开发
声明bean
用于替换自建类型组件的 <bean...>标签;可以更快速的声明bean
注解
- @Service 业务类专用
@Repository dao实现类专用
@Controller web层专用
- @Component 通用
- @Scope 用户控制bean的创建模式
@Repository dao实现类专用
@Controller web层专用
- @Component 通用
- @Scope 用户控制bean的创建模式
注入(DI)
用于完成bean中属性值的注入
- @Autowired 基于类型自动注入
- @Resource 基于名称自动注入
- @Qualifier("userDAO") 限定要自动注入的bean的id,一般和@Autowired联用
- @Value 注入简单类型数据 (jdk8种+String)
- @Resource 基于名称自动注入
- @Qualifier("userDAO") 限定要自动注入的bean的id,一般和@Autowired联用
- @Value 注入简单类型数据 (jdk8种+String)
事务控制
用于控制事务切入
- @Transactional
- 工厂配置中的 <tx:advice.... 和 <aop:config... 可以省略 !!
- 工厂配置中的 <tx:advice.... 和 <aop:config... 可以省略 !!
注解所需配置
<!-- 告知spring,哪些包中 有被注解的类、方法、属性 -->
<!-- <context:component-scan base-package="com.qf.a,com.xx.b"></context:component-scan> -->
<context:component-scan base-package="com.qf"></context:component-scan>
<!-- 告知spring,@Transactional在定制事务时,基于txManager=DataSourceTransactionManager -->
<tx:annotation-driven transaction-manager="txManager"/>
<!-- <context:component-scan base-package="com.qf.a,com.xx.b"></context:component-scan> -->
<context:component-scan base-package="com.qf"></context:component-scan>
<!-- 告知spring,@Transactional在定制事务时,基于txManager=DataSourceTransactionManager -->
<tx:annotation-driven transaction-manager="txManager"/>
集成JUnit
导入依赖
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>4.3.6.RELEASE</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
</dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>4.3.6.RELEASE</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
</dependency>
编码
可以免去工厂的创建过程;
可以直接将要测试的组件注入到测试类。
可以直接将要测试的组件注入到测试类。
代码
@RunWith(SpringJUnit4ClassRunner.class) //由SpringJUnit4ClassRunner启动测试
@ContextConfiguration("classpath:applicationContext.xml") //spring的配置文件位置
public class SpringTest{//当前测试类也会被纳入工厂中,所以其中属性可以注入
@Autowired // 注入要测试的组件
@Qualifier("userDAO")
private UserDAO userDAO;
@Test
public void test(){
// 测试使用userDAO
userDAO.queryUser();
....
}
}
@ContextConfiguration("classpath:applicationContext.xml") //spring的配置文件位置
public class SpringTest{//当前测试类也会被纳入工厂中,所以其中属性可以注入
@Autowired // 注入要测试的组件
@Qualifier("userDAO")
private UserDAO userDAO;
@Test
public void test(){
// 测试使用userDAO
userDAO.queryUser();
....
}
}
Log
介绍
用于记录系统中发生的各种事件。记录的位置常见的有 :控制台、磁盘文件等
级别
日志级别从低到高:
TRACE、DEBUG、INFO、WARN、ERROR、FATAL
TRACE、DEBUG、INFO、WARN、ERROR、FATAL
作用
- 通过日志观察、分析项目的运行情况 (项目维护)
- 通过日志分析用户的使用情况 (大数据分析)
- 通过日志分析用户的使用情况 (大数据分析)
解决方案1
Log4j+Commons-Logging
导入依赖
项目中添加 Log4j和Commons-Logging的依赖
<!-- https://mvnrepository.com/artifact/log4j/log4j -->
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>
<!-- https://mvnrepository.com/artifact/commons-logging/commons-logging -->
<dependency>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
<version>1.2</version>
</dependency>
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>
<!-- https://mvnrepository.com/artifact/commons-logging/commons-logging -->
<dependency>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
<version>1.2</version>
</dependency>
<!-- 排除掉spring-jcl,其会和 commons-logging产生不兼容(依赖冲突,有同名同包的类 LogFactory) -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>5.1.6.RELEASE</version>
<exclusions>
<exclusion>
<groupId>org.springframework</groupId>
<artifactId>spring-jcl</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>5.1.6.RELEASE</version>
<exclusions>
<exclusion>
<groupId>org.springframework</groupId>
<artifactId>spring-jcl</artifactId>
</exclusion>
</exclusions>
</dependency>
基本使用
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.junit.Test;
public class HelloLog {
// 需要输出日志的类,可以创建一个log属性
Log log = LogFactory.getLog(HelloLog.class);
@Test
public void test1(){
log.trace("hello trace");
log.debug("hello debug");
log.info("hello info");
log.warn("hello warn");
log.error("hello error");
log.fatal("hello fatal");
}
}
import org.apache.commons.logging.LogFactory;
import org.junit.Test;
public class HelloLog {
// 需要输出日志的类,可以创建一个log属性
Log log = LogFactory.getLog(HelloLog.class);
@Test
public void test1(){
log.trace("hello trace");
log.debug("hello debug");
log.info("hello info");
log.warn("hello warn");
log.error("hello error");
log.fatal("hello fatal");
}
}
配置信息
还是看笔记吧配置文件有点多
LayUI
注意官网的
LayUI 标识 :id 和 lay-filter. id用来定位标签。 lay-filter用来定位事件源
Maven
目录
`bin:含有mvn运行的脚本`
`boot:含有plexus-classworlds类加载器框架,Maven 使用该框架加载自己的类库。`
`conf:含有settings.xml配置文件`
`lib:含有Maven运行时所需要的java类库`
`boot:含有plexus-classworlds类加载器框架,Maven 使用该框架加载自己的类库。`
`conf:含有settings.xml配置文件`
`lib:含有Maven运行时所需要的java类库`
环境变量
maven依赖java环境,所以要确保java环境已配置好 (maven-3.3+ 需要jdk7+)
maven本身有2个环境变量要配置:
maven本身有2个环境变量要配置:
`MAVEN_HOME = maven的安装目录`
`PATH = maven的安装目录下的bin目录`
`PATH = maven的安装目录下的bin目录`
Maven配置
本地仓库
maven的conf目录中有 settings.xml ,是maven的配置文件,做如下配置:
<settings xmlns="http://maven.apache.org/SETTINGS/1.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/SETTINGS/1.0.0 http://maven.apache.org/xsd/settings-1.0.0.xsd">
<!-- localRepository
| The path to the local repository maven will use to store artifacts.
|
| Default: ${user.home}/.m2/repository
<localRepository>/path/to/local/repo</localRepository>
-->
<!-- 选择一个磁盘目录,作为本地仓库 -->
<localRepository>D:\Program Files\maven\myrepository</localRepository>
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/SETTINGS/1.0.0 http://maven.apache.org/xsd/settings-1.0.0.xsd">
<!-- localRepository
| The path to the local repository maven will use to store artifacts.
|
| Default: ${user.home}/.m2/repository
<localRepository>/path/to/local/repo</localRepository>
-->
<!-- 选择一个磁盘目录,作为本地仓库 -->
<localRepository>D:\Program Files\maven\myrepository</localRepository>
JDK配置
在 <profiles> 标签中 增加 一个 <profile> 标签,限定maven项目默认的jdk版本.内容如下:
<profiles>
<!-- 在已有的profiles标签中添加profile标签 -->
<profile>
<id>myjdk</id>
<activation>
<activeByDefault>true</activeByDefault>
<jdk>1.8</jdk>
</activation>
<properties>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
<maven.compiler.compilerVersion>1.8</maven.compiler.compilerVersion>
</properties>
</profile>
</profiles>
<!-- 让增加的 profile生效 -->
<activeProfiles>
<activeProfile>myjdk</activeProfile>
</activeProfiles>
<!-- 在已有的profiles标签中添加profile标签 -->
<profile>
<id>myjdk</id>
<activation>
<activeByDefault>true</activeByDefault>
<jdk>1.8</jdk>
</activation>
<properties>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
<maven.compiler.compilerVersion>1.8</maven.compiler.compilerVersion>
</properties>
</profile>
</profiles>
<!-- 让增加的 profile生效 -->
<activeProfiles>
<activeProfile>myjdk</activeProfile>
</activeProfiles>
仓库
查询依赖的顺序
本地仓库 > 私服(如果配置了的话) > 公共仓库(如果配置了的话) > 中央仓库
本地仓库
即在settings.xml 中配置的目录。
使用过了的依赖都会自动存储在本地仓库中,后续可以复用。
私服是架设在局域网的一种特殊的远程仓库,目的是代理远程仓库及部署第三方构件。
远程仓库
中央仓库
中央仓库在国外,下载依赖速度过慢,所以都会配置一个国内的公共仓库替代中央仓库。
公共仓库
除中央仓库之外,还有其他远程仓库。比如aliyun仓库(http://maven.aliyun.com/nexus/content/groups/public/)
<!--settings.xml中添加如下配置-->
<mirrors>
<mirror>
<id>aliyun</id>
<!-- 中心仓库的 mirror(镜像) -->
<mirrorOf>central</mirrorOf>
<name>Nexus aliyun</name>
<!-- aliyun仓库地址 以后所有要指向中心仓库的请求,都会指向aliyun仓库-->
<url>http://maven.aliyun.com/nexus/content/groups/public</url>
</mirror>
</mirrors>
<mirrors>
<mirror>
<id>aliyun</id>
<!-- 中心仓库的 mirror(镜像) -->
<mirrorOf>central</mirrorOf>
<name>Nexus aliyun</name>
<!-- aliyun仓库地址 以后所有要指向中心仓库的请求,都会指向aliyun仓库-->
<url>http://maven.aliyun.com/nexus/content/groups/public</url>
</mirror>
</mirrors>
私服
概念
有了私服之后,当 Maven 需要下载依赖时,直接请求私服,私服上存在则下载到本地仓库;否则,私服请求外部的远程仓库,将构件下载到私服,再提供给本地仓库下载。
私服可以解决在企业做开发时每次需要的jar包都要在中心仓库下载,且每次下载完只能被自己使用,不能被其他开发人员使用
所谓私服就是一个服务器,但是不是本地层面的,是公司层面的,公司中所有的开发人员都在使用同一个私服
启动
解压后在bin目录中执行:
- nexus install 在系统中安装nexus服务
- nexus uninstall 卸载nexus服务
- nexus start 启动服务
- nexus stop 停止服务
- nexus install 在系统中安装nexus服务
- nexus uninstall 卸载nexus服务
- nexus start 启动服务
- nexus stop 停止服务
cmd窗口中运行
Git
基础命令
git config --global user.name "Your Name" #用户名
git config --global user.email "email@example.com" #邮箱
git config -l
git version
架构
版本库:工作区中有一个隐藏目录 .git,这个目录不属于工作区,而是git的 版本库,是git管理的所有内容
暂存区:版本库中包含一个临时区域,保存下一步要提交的文件。
分支:版本库中包含若干分支,提交的文件存储在分支中
本地仓库
对应的就是一个目录,这个目录中的所有文件被git管理起来。
以后会将一个项目的根目录,作为仓库。
仓库中的每个文件的改动 都由git跟踪。
1.新建仓库
选择一个目录,执行指令:git init
2.工作去
执行git init的目录即为工作区,但是不包括.git文件夹
所有文件,都首先在工作区新建,然后可以存入仓库(版本库),进行版本控制。
3.暂存区
暂存区也在 .git 目录内,工作区的文件进入仓库时,要先进入暂存区。
4.分支
版本控制,简单说,就是记录文件的诸多版本,分支就是这些版本的最终记录位置。
本地仓库基本操作
1.查看仓库状态
执行 git status 可以看到工作区中文件的状态
2暂存文件
执行 git add . 将工作区中的文件全部存入暂存区
将工作区的所有文件放到暂存区中
执行 git add 文件名
将指定的文件放到暂存区中
3.提交文件
执行 git commit -m "这里写提交的描述信息" 作用是将暂存区的文件存入分支,形成一个版本
远程仓库
概念
当多人协同开发时,每人都在自己的本地仓库维护版本。
但很重要的一点是,多人之间需要共享代码、合并代码,此时就需要一个远程仓库。
市面上流行的远程仓库
有很多远程仓库可以选择,比如 github(https://github.com/),码云(https://gitee.com/);
此两种可以注册自己测试使用,但如果是商业项目,需要更多支持需要付费。
公司内部也可以有自己构建的远程仓库(http://qianfeng.qfjava.cn:8087/users/sign_in)。
基本操作
将本地提交文件存入远程仓库
1.关联仓库地址
git remote add origin 远程仓库地址
2.git push origin master
可以修改提交到的远程仓库的分支名(master为主分支也是默认分支)
克隆远程仓库
如果仓库已经由别人创建完毕,我们需要其中的内容,则可以通过 git clone 将其复制到本地。
代码共享
多人协同开发时,写好代码的git push 上传到远程仓库;需要代码的 git pull 拉取代码即可。
命令汇总
执行 git status 可以看到工作区中文件的状态
git remote add 标识名(master) 远程地址
本地关联远程仓库
git commit -m "注释"
提交文件
git push 标识名 master
将本地仓库内容上传到远程仓库
表示名一般为orgin
git pull 标识名 master
从远程仓库下载内容到本地仓库
git clone 远程地址
将远程仓库复制到本地,并自动形成一个本地仓库
分支
分支简介
分支,是一个个版本最终存储的位置
分支,就是一条时间线,每次git commit形成一个个版本,一个个版本依次存储在分支的一个个提交点上。
基本操作
查看分支
查看当前仓库的分支 git branch
仓库中默认只有 master 分支
执行git commit时,默认是在master分支上保存版本。
创建分支
在商业项目开发过程中,我们不会轻易的在master分支上做操作。
我们会新建一个开发用的分支,在此分支上做版本的记录。
当代码确实没有问题时,才会将开发分支上成熟的代码版本添加到master分支上。
当代码确实没有问题时,才会将开发分支上成熟的代码版本添加到master分支上。
切换分支
默认情况下,当前使用的分支是 master分支
可以切换到 dev分支,则后续的git commit 便会在dev分支上新建版本(提交点)
新建分支细节
每个分支都有一个指针,新建一个分支,首先是新建一个指针。
而且新分支的指针会和当前分支指向同一个提交点。
新分支包含的提交点就是从第一个提交点到分支指针指向的提交点。
多分支走向
在master分支和新分支,分别进行 git add 和 git commit
分支提交日志
HEAD指向的是当前分支
分支合并
git merge 分支a 合并分支a
当前分支合并分支a
合并的方式有两种:快速合并 和 三方合并。
快速合并
如果分支A当前的修改,是完全基于分支B的修改而来,则B分支合并A分支,就是移动指针即可。
三方合并
在不具备快速合并的条件下,会采用三方合并。
合并冲突
两个分支进行合并,但它们含有对同一个文件的修改,则在合并时出现冲突,git无法决断该保留改文件哪个分支的修改。
冲突解决方案:两个人商量商量留哪些,把最终的代码在提交一遍
dea关联Git
File > Settings 关联过程是自动的
SpringMVC
引言
java开源框架,Spring Framework的一个独立模块。
MVC框架,在项目中开辟MVC层次架构
对控制器中的功能 包装 简化 扩展践行工厂模式,功能架构在工厂之上
MVC框架,在项目中开辟MVC层次架构
对控制器中的功能 包装 简化 扩展践行工厂模式,功能架构在工厂之上
MVC架构
概念
Model
模型:即业务模型,负责完成业务中的数据通信处理,对应项目中的 service和dao
模型:即业务模型,负责完成业务中的数据通信处理,对应项目中的 service和dao
View
视图:渲染数据,生成页面。对应项目中的Jsp
视图:渲染数据,生成页面。对应项目中的Jsp
Controller
控制器:直接对接请求,控制MVC流程,调度模型,选择视图。对应项目中的Servlet
控制器:直接对接请求,控制MVC流程,调度模型,选择视图。对应项目中的Servlet
好处
- MVC是现下软件开发中的最流行的代码结构形态;
- 人们根据负责的不同逻辑,将项目中的代码分成 M V C 3个层次;
- 层次内部职责单一,层次之间耦合度低;
- 符合低耦合 高内聚的设计理念。也实际有利于项目的长期维护
- 人们根据负责的不同逻辑,将项目中的代码分成 M V C 3个层次;
- 层次内部职责单一,层次之间耦合度低;
- 符合低耦合 高内聚的设计理念。也实际有利于项目的长期维护
开发流程
导入依赖
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>5.1.6.RELEASE</version>
</dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>5.1.6.RELEASE</version>
</dependency>
配置核心(前端)控制器
作为一个MVC框架,首先要解决的是:如何能够收到请求!
所以MVC框架大都会设计一款前端控制器,选型在 Servlet 或 Filter两者之一,在框架最前沿率先工作,接收所有请求。
此控制器在接收到请求后,还会负责springMVC的核心的调度管理,所以既是前端又是核心。
所以MVC框架大都会设计一款前端控制器,选型在 Servlet 或 Filter两者之一,在框架最前沿率先工作,接收所有请求。
此控制器在接收到请求后,还会负责springMVC的核心的调度管理,所以既是前端又是核心。
<servlet>
<servlet-name>mvc</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<!-- 局部参数:声明配置文件位置 -->
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:mvc.xml</param-value>
</init-param>
<!-- Servlet启动时刻:可选 -->
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>mvc</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
<servlet-name>mvc</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<!-- 局部参数:声明配置文件位置 -->
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:mvc.xml</param-value>
</init-param>
<!-- Servlet启动时刻:可选 -->
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>mvc</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
后端控制器
等价于之前定义的Servlet
@Controller //声明这是一个控制器
@RequestMapping("/hello") //访问路径 ,等价于url-pattern
public class HelloController {
@RequestMapping("/test1") //访问路径
public String hello1(){
System.out.println("hello world");
return "index"; // 跳转:/index.jsp
}
@RequestMapping("/test2") //访问路径
public String hello2(){
System.out.println("hello c9");
return "views/users";// 跳转:/views/user.jsp
}
}
@RequestMapping("/hello") //访问路径 ,等价于url-pattern
public class HelloController {
@RequestMapping("/test1") //访问路径
public String hello1(){
System.out.println("hello world");
return "index"; // 跳转:/index.jsp
}
@RequestMapping("/test2") //访问路径
public String hello2(){
System.out.println("hello c9");
return "views/users";// 跳转:/views/user.jsp
}
}
配置文件
默认名称:核心控制器名-servet.xml 默认位置:WEB-INF
随意名称:mvc.xml 随意位置:resources 但需要配置在核心控制器中
随意名称:mvc.xml 随意位置:resources 但需要配置在核心控制器中
<!-- 告知springmvc 哪些包中 存在 被注解的类 -->
<context:component-scan base-package="com.qf.controller"></context:component-scan>
<!-- 注册注解开发驱动 -->
<mvc:annotation-driven></mvc:annotation-driven>
<!-- 视图解析器
作用:1.捕获后端控制器的返回值="index"
2.解析: 在返回值的前后 拼接 ==> "/index.jsp"
-->
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<!-- 前缀 -->
<property name="prefix" value="/"></property>
<!-- 后缀 -->
<property name="suffix" value=".jsp"></property>
</bean>
</beans>
<context:component-scan base-package="com.qf.controller"></context:component-scan>
<!-- 注册注解开发驱动 -->
<mvc:annotation-driven></mvc:annotation-driven>
<!-- 视图解析器
作用:1.捕获后端控制器的返回值="index"
2.解析: 在返回值的前后 拼接 ==> "/index.jsp"
-->
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<!-- 前缀 -->
<property name="prefix" value="/"></property>
<!-- 后缀 -->
<property name="suffix" value=".jsp"></property>
</bean>
</beans>
接收请求参数
基本类型收参
请求参数和方法的形参 同名即可
springMVC默认可以识别的日期字符串格式为: YYYY/MM/dd HH:mm:ss
通过@DateTimeFormat可以修改默认日志格式
通过@DateTimeFormat可以修改默认日志格式
实体收参【重点】
请求参数和实体的属性 同名即可
数组收参
简单类型的 数组
前端代码
<form>
......
<input type="checkbox" name="hobby" value="fb"/>足球
<input type="checkbox" name="hobby" value="bb"/>篮球
<input type="checkbox" name="hobby" value="vb"/>排球
</form>
......
<input type="checkbox" name="hobby" value="fb"/>足球
<input type="checkbox" name="hobby" value="bb"/>篮球
<input type="checkbox" name="hobby" value="vb"/>排球
</form>
后端接受代码
//http://localhost:8989/.../test3?hobby=football&hobby=basketball
@RequestMapping("/test3")
public String testParam3(String[] hobby){
for(String h:hobby){
System.out.print(h+" ");
}
return "index";
}
@RequestMapping("/test3")
public String testParam3(String[] hobby){
for(String h:hobby){
System.out.print(h+" ");
}
return "index";
}
集合收参 【了解】
// <input type="text" name="users[0].id"/>
// post请求:http://...?users[0].id=1&users[0].name=zhangsan&users[0].birth=2018-12-12&users[1].id=2&....
@RequestMapping("/test4")
public String testParam4(UserList userList){
for(User user:userList.getUsers()){
System.out.println(user);
}
return "index";
}
// post请求:http://...?users[0].id=1&users[0].name=zhangsan&users[0].birth=2018-12-12&users[1].id=2&....
@RequestMapping("/test4")
public String testParam4(UserList userList){
for(User user:userList.getUsers()){
System.out.println(user);
}
return "index";
}
路径参数
情况1
// {id} 定义名为id的路径;【/hello/{id}】的匹配能力和【/hello/*】等价
// http://localhost:8989/.../hello/10 {id}匹配到10
@RequestMapping("/hello/{id}")
// @PathVariable将{id}路径匹配到值赋给id参数
// 路径名和参数名相同则@PathVariable("id")可简写为 @PathVariable
public String testParam5(@PathVariable("id") Integer id){
System.out.println("id:"+id);
return "index";
}
// http://localhost:8989/.../hello/10 {id}匹配到10
@RequestMapping("/hello/{id}")
// @PathVariable将{id}路径匹配到值赋给id参数
// 路径名和参数名相同则@PathVariable("id")可简写为 @PathVariable
public String testParam5(@PathVariable("id") Integer id){
System.out.println("id:"+id);
return "index";
}
情况2
// http://localhost:8989/.../hello/tom {username}匹配到tom
@RequestMapping("/hello/{username}")
public String testParam6(@PathVariable("username") String name){//将{username}路径匹配到的值赋给name参数
System.out.println("username:"+name);
return "index";
}
@RequestMapping("/hello/{username}")
public String testParam6(@PathVariable("username") String name){//将{username}路径匹配到的值赋给name参数
System.out.println("username:"+name);
return "index";
}
中文乱码
首先,页面中字符集统一
JSP : <%@page pageEncoding="utf-8" %>
HTML : <meta charset="UTF-8">
HTML : <meta charset="UTF-8">
其次,tomcat中字符集设置,对get请求中,中文参数乱码有效
Tomcat配置:URIEncoding=utf-8Tomcat配置:URIEncoding=utf-8
最后,设置此filter,对post请求中,中文参数乱码有效
<!-- 此过滤器会进行:request.setCharactorEncoding("utf-8"); -->
<filter>
<filter-name>encoding</filter-name>
<filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
<init-param>
<param-name>encoding</param-name>
<param-value>utf-8</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>encoding</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<filter>
<filter-name>encoding</filter-name>
<filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
<init-param>
<param-name>encoding</param-name>
<param-value>utf-8</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>encoding</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
跳转
转发
返回值如果返回的是jsp的名字,不加任何前缀,默认为转发
重定向
redirect前缀为重定向
跳转细节
曾删改完事跳重定向
查询转发
传值
C得到数据后,跳转到V,并向V传递数据。进而V中可以渲染数据,让用户看到含有数据的页面
转发跳转:Request作用域
重定向跳转:Session作用域
转发跳转:Request作用域
重定向跳转:Session作用域
Request和Session
//形参中 即可获得 request 和 session对象
@RequestMapping("/test1")
public String testData(HttpSession session,HttpServletRequest req,Integer id){
session.setAttribute("user",new User());
req.setAttribute("age", 18);
req.setAttribute("users",Arrays.asList(new User(),new User()));
//return "test2";
return "forward:/WEB-INF/test2.jsp";
}
@RequestMapping("/test1")
public String testData(HttpSession session,HttpServletRequest req,Integer id){
session.setAttribute("user",new User());
req.setAttribute("age", 18);
req.setAttribute("users",Arrays.asList(new User(),new User()));
//return "test2";
return "forward:/WEB-INF/test2.jsp";
}
JSP中取值
Model
model.addAttribute("name", "张三");
数据存入request域中但是,如果用HttpServletRequest存的话优先级要比model低,model会覆盖它
ModelAndView
代码
//modelandview 可以集中管理 跳转和数据
@RequestMapping("/test")
public ModelAndView testData(){//返回值类型为ModelAndView
//新建ModelAndView对象
ModelAndView mv = new ModelAndView();
// 设置视图名,即如何跳转
mv.setViewName("forward:/index.jsp");
// 增加数据
mv.addObject("age",18);
return mv;
}
//jsp中用EL表达式 取值即可
${requestScope.age}
@RequestMapping("/test")
public ModelAndView testData(){//返回值类型为ModelAndView
//新建ModelAndView对象
ModelAndView mv = new ModelAndView();
// 设置视图名,即如何跳转
mv.setViewName("forward:/index.jsp");
// 增加数据
mv.addObject("age",18);
return mv;
}
//jsp中用EL表达式 取值即可
${requestScope.age}
// 设置视图名,即如何跳转
mv.setViewName("forward:/index.jsp");
// 增加数据
mv.addObject("age",18);
mv.setViewName("forward:/index.jsp");
// 增加数据
mv.addObject("age",18);
数据都是存入request域中
@SessionAttributes
代码
@Controller
@SessionAttributes({"gender","name"}) // model中的 name和gender 会存入session中
public class UserController {
@RequestMapping("/hello")
public String hello(Model m){
m.addAttribute("gender",true); // 会存入session
mv.addObject("name","zhj"); // 会存入session
return "index";
}
@RequestMapping("/hello2")
public String hello(SessionStatus status){
// 移除通过SessionAttributes存入的session
status.setComplete();
return "index";
}
}
@SessionAttributes({"gender","name"}) // model中的 name和gender 会存入session中
public class UserController {
@RequestMapping("/hello")
public String hello(Model m){
m.addAttribute("gender",true); // 会存入session
mv.addObject("name","zhj"); // 会存入session
return "index";
}
@RequestMapping("/hello2")
public String hello(SessionStatus status){
// 移除通过SessionAttributes存入的session
status.setComplete();
return "index";
}
}
- @SessionAttributes({"gender","name"}) :model中的 name和gender 会存入session中
- SessionStatus 移除session
- SessionStatus 移除session
静态资源
静态资源的概念
图片,html,css,js
存在问题
静态资源没有url-patten,是直接访问不到的,之所以能访问到是因为tomcat有一个全局额servlet
org.apache.catalina.servlets.DefaultServlet,它的url-pattern是 "/",是全局默认的Servlet. 所以每个项目中不能匹配的静态资源的请求,有这个Servlet来处理即可。
但,在SpringMVC中DispatcherServlet也采用了 “/” 作为url-pattern, 则项目中不会再使用全局的Serlvet,则静态资源不能完成访问。
解决方案1
DispathcerServlet采用其他的url-pattern
此时,所有访问handler的路径都要以 action结尾!!
此时,所有访问handler的路径都要以 action结尾!!
<servlet-mapping>
<servlet-name>mvc9</servlet-name>
<url-pattern>*.action</url-pattern>
</servlet-mapping>
<servlet-name>mvc9</servlet-name>
<url-pattern>*.action</url-pattern>
</servlet-mapping>
解决方案2
DispathcerServlet的url-pattern依然采用 "/",但追加配置
<!--
额外的增加一个handler,且其requestMapping: "/**" 可以匹配所有请求,但是优先级最低
所以如果其他所有的handler都匹配不上,请求会转向 "/**" ,恰好,这个handler就是处理静态资源的
处理方式:将请求转会到tomcat中名为default的Servlet
-->
<mvc:default-servlet-handler/>
额外的增加一个handler,且其requestMapping: "/**" 可以匹配所有请求,但是优先级最低
所以如果其他所有的handler都匹配不上,请求会转向 "/**" ,恰好,这个handler就是处理静态资源的
处理方式:将请求转会到tomcat中名为default的Servlet
-->
<mvc:default-servlet-handler/>
解决方案3
- mapping是访问路径,location是静态资源存放的路径
- 将/html/** 中 /**匹配到的内容,拼接到 /hhh/后
http://..../html/a.html 访问 /hhh/a.html
- 将/html/** 中 /**匹配到的内容,拼接到 /hhh/后
http://..../html/a.html 访问 /hhh/a.html
<mvc:resources mapping="/html/**" location="/hhh/"/>
Json处理
jackjson
导入依赖
<!-- Jackson springMVC默认的Json解决方案选择是 Jackson,所以只需要导入jackson的jar,即可使用。-->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.9.8</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.9.8</version>
</dependency>
注解
@ResponseBody
//将handler的返回值,转换成json(jackson),并将json响应给客户端。
@RestController
Controller类上加了@RestController注解,等价于在类中的每个方法上都加了@ResponseBody
@RequestBody
@RequestBody, 接收Json参数
@PathVariable
通过 @PathVariable 可以将 URL 中占位符参数绑定到控制器处理方法的入参中:URL 中的 {xxx} 占位符可以通过
@PathVariable("xxx") 绑定到操作方法的入参中。
@PathVariable("xxx") 绑定到操作方法的入参中。
常用注解
日期格式化
@JsonFormat(pattern="yyyy-MM-dd HH:mm:ss",timezone = "GMT+8")
属性名修改
@JsonProperty("new_name")
属性忽略
@JsonIgnore
// 生成json时,忽略此属性
null和empty属性排除
Jackson 默认会输出null值的属性,如果不需要,可以排除。
@JsonInclude(JsonInclude.Include.NON_NULL) //null值 属性不输出
@JsonInclude(value= JsonInclude.Include.NON_EMPTY) // empty属性不输出( 空串,长度为0的集合,null值)
@JsonInclude(value= JsonInclude.Include.NON_EMPTY) // empty属性不输出( 空串,长度为0的集合,null值)
自定义序列化
@JsonSerialize(using = MySerializer.class) // 使用MySerializer输出某属性
FastJson
导入依赖
<!-- FastJson -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.54</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.54</version>
</dependency>
安装FastJson
<mvc:annotation-driven>
<!-- 安装FastJson,转换器 -->
<mvc:message-converters>
<bean class="com.alibaba.fastjson.support.spring.FastJsonHttpMessageConverter">
<!-- 声明转换类型:json -->
<property name="supportedMediaTypes">
<list>
<value>application/json</value>
</list>
</property>
</bean>
</mvc:message-converters>
</mvc:annotation-driven>
<!-- 安装FastJson,转换器 -->
<mvc:message-converters>
<bean class="com.alibaba.fastjson.support.spring.FastJsonHttpMessageConverter">
<!-- 声明转换类型:json -->
<property name="supportedMediaTypes">
<list>
<value>application/json</value>
</list>
</property>
</bean>
</mvc:message-converters>
</mvc:annotation-driven>
使用
@ResponseBody @RequestBody @RestController 使用方法不变
常用注解
日期格式化:@JSONField(format="yyyy/MM/dd")
属性名修改:@JSONField(name="birth")
忽略属性:@JSONField(serialize = false)
包含null值:@JSONField(serialzeFeatures = SerializerFeature.WriteMapNullValue) 默认会忽略所有null值,有此注解会输出null
@JSONField(serialzeFeatures = SerializerFeature.WriteNullStringAsEmpty) null的String输出为""
自定义序列化:@JSONField(serializeUsing = MySerializer2.class)
异常解析器
现有方案,分散处理
Controller中的每个Handler自己处理异常
此种处理方案,异常处理逻辑,分散在各个handler中,不利于集中管理
此种处理方案,异常处理逻辑,分散在各个handler中,不利于集中管理
异常解析器,统一处理
Controller中的每个Handler不再自己处理异常,而是直接throws所有异常。
定义一个“异常解析器” 集中捕获处理 所有异常
此种方案,在集中管理异常方面,更有优势!
定义一个“异常解析器” 集中捕获处理 所有异常
此种方案,在集中管理异常方面,更有优势!
声明异常解析器
<!-- 声明异常解析器 -->
<bean class="com.qf.exception.resolver.MyExResolver"></bean>
<bean class="com.qf.exception.resolver.MyExResolver"></bean>
拦截器
作用:抽取handler中的冗余功能
定义拦截器
执行顺序: preHandle--postHandle--afterCompletion
配置拦截路径
<mvc:interceptors>
<mvc:interceptor>
<mvc:mapping path="/inter/test1"/>
<mvc:mapping path="/inter/test2"/>
<mvc:mapping path="/inter/test*"/> <!-- test开头 -->
<mvc:mapping path="/inter/**"/> <!-- /** 任意多级任意路径 -->
<mvc:exclude-mapping path="/inter/a/**"/> <!--不拦截此路径-->
<bean class="com.qf.interceptor.MyInter1"></bean> <!--拦截器类-->
</mvc:interceptor>
</mvc:interceptors>
<mvc:interceptor>
<mvc:mapping path="/inter/test1"/>
<mvc:mapping path="/inter/test2"/>
<mvc:mapping path="/inter/test*"/> <!-- test开头 -->
<mvc:mapping path="/inter/**"/> <!-- /** 任意多级任意路径 -->
<mvc:exclude-mapping path="/inter/a/**"/> <!--不拦截此路径-->
<bean class="com.qf.interceptor.MyInter1"></bean> <!--拦截器类-->
</mvc:interceptor>
</mvc:interceptors>
上传
导入依赖
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>2.4</version>
</dependency>
<dependency>
<groupId>commons-fileupload</groupId>
<artifactId>commons-fileupload</artifactId>
<version>1.3.3</version>
<exclusions>
<exclusion>
<groupId>javax.servlet</groupId>
<artifactId>servlet-api</artifactId>
</exclusion>
</exclusions>
</dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>2.4</version>
</dependency>
<dependency>
<groupId>commons-fileupload</groupId>
<artifactId>commons-fileupload</artifactId>
<version>1.3.3</version>
<exclusions>
<exclusion>
<groupId>javax.servlet</groupId>
<artifactId>servlet-api</artifactId>
</exclusion>
</exclusions>
</dependency>
表单
<form action="${pageContext.request.contextPath }/upload/test1" method="post"
enctype="multipart/form-data">
file: <input type="file" name="source"/> <br>
<input type="submit" value="提交"/>
</form>
enctype="multipart/form-data">
file: <input type="file" name="source"/> <br>
<input type="submit" value="提交"/>
</form>
上传解析器
<!-- 上传解析器
id必须是:“multipartResolver”
-->
<bean id="multipartResolver"
class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
<!-- 最大可上传的文件大小 单位:byte 超出后会抛出MaxUploadSizeExceededException异常,可以异常解析器捕获 -->
<property name="maxUploadSize" value="1048576"></property>
</bean>
id必须是:“multipartResolver”
-->
<bean id="multipartResolver"
class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
<!-- 最大可上传的文件大小 单位:byte 超出后会抛出MaxUploadSizeExceededException异常,可以异常解析器捕获 -->
<property name="maxUploadSize" value="1048576"></property>
</bean>
handler
public String hello1(String username,MultipartFile source,HttpSession session) {
验证文件大小
前端验证
<script>
function submit2004(){
var size = document.getElementById("up2004").files[0].size;
if(size/1024/1024>2){
alert("文件不能大于2M");
return;
}
document.forms[0].submit();
}
</script>
function submit2004(){
var size = document.getElementById("up2004").files[0].size;
if(size/1024/1024>2){
alert("文件不能大于2M");
return;
}
document.forms[0].submit();
}
</script>
后端验证
<!-- 上传解析器 -->
<bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
<!-- 最大值给一个 很大的数字,让上传解析器不再限制大小 -->
<property name="maxUploadSize" value="2097152000000"/>
</bean>
<bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
<!-- 最大值给一个 很大的数字,让上传解析器不再限制大小 -->
<property name="maxUploadSize" value="2097152000000"/>
</bean>
下载
<a href="${pageContext.request.contextPath}/download/test1?name=Koala.jpg">下载</a>
代码看笔记吧,太多了,看懂就行
验证码
作用
防止别人写个死循环暴力破解网站,现在基本上验证码都有县城的导入就行
导入jar
<dependency>
<groupId>com.github.penggle</groupId>
<artifactId>kaptcha</artifactId>
<version>2.3.2</version>
<exclusions>
<exclusion>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
</exclusion>
</exclusions>
</dependency>
<groupId>com.github.penggle</groupId>
<artifactId>kaptcha</artifactId>
<version>2.3.2</version>
<exclusions>
<exclusion>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
</exclusion>
</exclusions>
</dependency>
声明验证码组件
在web.xml中声明验证码
REST
开发风格
是一种开发风格,遵从此风格开发软件,符合REST风格,则RESTFUL。
两个核心要求
每个资源都有唯一的标识(URL)
不同的行为,使用对应的http-method
访问标识
http://localhost:8989/xxx/users
所有用户
http://localhost:8989/xxx/users/1
查询用户1
http://localhost:8989/xxx/users/1/orders
查询用户1下的所有订单
请求方式
GET
http://localhost:8989/xxx/users
查询所有用户
PUT
http://localhost:8989/xxx/users
在所有用户中修改一个
POST
http://localhost:8989/xxx/users
在所有用户中增加一个
DELETE
http://localhost:8989/xxx/users/1
删除用户1
GET
http://localhost:8989/xxx/users/1
查询用户1
GET
http://localhost:8989/xxx/users/1/orders
查询用户1的所有订单
POST
http://localhost:8989/xxx/users/1/orders
在用户1的所有订单中增加一个
表示用法
GET查询所有或者单个
POST事增加一个
PUT用于更新/修改
DELETE用于删除
使用
定义Rest风格的 Controller
@RequestMapping(value="/users",method = RequestMethod.GET)
等价
@GetMapping("/users")
等价
@GetMapping("/users")
跨域请求
域的理解
域:协议+IP+端口
Ajax跨域问题
Ajax发送请求时,不允许跨域,以防用户信息泄露。
当Ajax跨域请求时,响应会被浏览器拦截(同源策略),并报错。即浏览器默认不允许ajax跨域得到响应内容。
互相信任的域之间如果需要ajax访问,(比如前后端分离项目中,前端项目和后端项目之间),则需要额外的设置才可正常请求。
解决方案
- 允许其他域访问
- 在被访问方的Controller类上,添加注解
- 在被访问方的Controller类上,添加注解
// spring5之后需要添加 allowCredentials = "true"
@CrossOrigin(value = "http://localhost:8080",allowCredentials = "true") //允许此域发请求访问
@CrossOrigin(value = "http://localhost:8080",allowCredentials = "true") //允许此域发请求访问
SpringMVC执行流程
前端请求到web.xml也即是前端控制器,然后通过前端控制器去找到requestHandlerMapping访问路径的handler,并将其封装成handler对象交给相对应的HandlerAdapters去执行handler对象,执行之后会返回事ModelAndView,这时候也是交给前端控制器去进行下一步的执行
ViewResolver 会讲返回的modelandview解析出一个view对象(前端会根据modelandview去调用我们在mvc.xml中配置的视图解析器,然后将其拼接成.jsp的形式,返回一个view拼好的),并通过转发或重定向响应给前端页面
前端页面渲染视图一个请求就完事了
注意,在执行handler对象之前会先执行拦截器,根据拦截器返回的false或者true,来进行是不是要执行下一步的handler
大体流程
前端页面内发送请求---》前端控制器(web.xml)获取一个handler对象,并执行得到一个modelAndView---》并返回到前端控制器并通过MVC.xml中的视图解析器获得一个view,并转发或重定向到前端页面--->前端页面进行渲染
HandlerMapping
识别当前请求路径,找到对应的Hanlder,
返回HandlerChain。其中包含: handler+ Interceptor
识别当前请求路径,找到对应的Hanlder,
返回HandlerChain。其中包含: handler+ Interceptor
HandlerAdapter
执行HanderChain,依次执行拦截器和hanler.
并完成请求参数封装,和响应中的Json格式转换。
最终返回ModelAndView
执行HanderChain,依次执行拦截器和hanler.
并完成请求参数封装,和响应中的Json格式转换。
最终返回ModelAndView
ViewResolver
根据ModelAndView,解析出一个View对象
根据ModelAndView,解析出一个View对象
View
完成请求的转发或重定向
完成请求的转发或重定向
JSP
视图层,渲染数据到html中,并将渲染完毕的html内容,
输出给浏览器,完成响应
视图层,渲染数据到html中,并将渲染完毕的html内容,
输出给浏览器,完成响应
Spring和SpringMVC整合
整合思路
此时项目中有两个工厂
- DispatcherServlet 启动的springMVC工厂==负责生产C及springMVC自己的系统组件
- ContextLoaderListener 启动的spring工厂==负责生产其他所有组件
- springMVC的工厂会被设置为spring工厂的子工厂,可以随意获取spring工厂中的组件
- 整合过程,就是累加:代码+依赖+配置。然后将service注入给controller即可
- ContextLoaderListener 启动的spring工厂==负责生产其他所有组件
- springMVC的工厂会被设置为spring工厂的子工厂,可以随意获取spring工厂中的组件
- 整合过程,就是累加:代码+依赖+配置。然后将service注入给controller即可
整合技巧
两个工厂不能有彼此侵入,即,生产的组件不能有重合。
SpringMVC
<!-- 告知SpringMVC 哪些包中 存在 被注解的类
use-default-filters=true 凡是被 @Controller @Service @Repository注解的类,都会被扫描
use-default-filters=false 默认不扫描包内的任何类, 只扫描include-filter中指定的类
只扫描被@Controller注解的类
-->
use-default-filters=true 凡是被 @Controller @Service @Repository注解的类,都会被扫描
use-default-filters=false 默认不扫描包内的任何类, 只扫描include-filter中指定的类
只扫描被@Controller注解的类
-->
<context:component-scan base-package="com.zhj" use-default-filters="false">
<context:include-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
</context:component-scan>
<context:include-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
</context:component-scan>
Spring
<!-- 告知Spring
唯独不扫描@Controller注解的类 -->
<context:component-scan base-package="com.zhj" use-default-filters="true">
<context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
</context:component-scan>
唯独不扫描@Controller注解的类 -->
<context:component-scan base-package="com.zhj" use-default-filters="true">
<context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
</context:component-scan>
Quartz
引言
简介
Quartz :http://www.quartz-scheduler.org/
是一个定时任务调度框架。比如你遇到这样的问题:
- 想在30分钟后,查看订单是否支付,未支付则取消订单
- 想在每月29号,信用卡自动还款
- ...
- 想定时在某个时间,去做某件事(任务)。
- 想在30分钟后,查看订单是否支付,未支付则取消订单
- 想在每月29号,信用卡自动还款
- ...
- 想定时在某个时间,去做某件事(任务)。
Quartz是要做定时任务的调度,设置好触发时间规则,以及相应的任务(Job)即可。
Quartz使用
导入依赖
<dependencies>
<!--Quartz任务调度-->
<!-- https://mvnrepository.com/artifact/org.quartz-scheduler/quartz -->
<dependency>
<groupId>org.quartz-scheduler</groupId>
<artifactId>quartz</artifactId>
<version>2.2.3</version>
</dependency>
</dependencies>
<!--Quartz任务调度-->
<!-- https://mvnrepository.com/artifact/org.quartz-scheduler/quartz -->
<dependency>
<groupId>org.quartz-scheduler</groupId>
<artifactId>quartz</artifactId>
<version>2.2.3</version>
</dependency>
</dependencies>
定义Job
实现Job类
代码
//创建scheduler,调度器
Scheduler scheduler = StdSchedulerFactory.getDefaultScheduler();
//定义一个Trigger,触发条件类
Trigger trigger = TriggerBuilder.newTrigger()
.withIdentity("trigger1", "group1") //定义name/group
.startNow()//一旦加入scheduler,立即生效,即开始计时
.withSchedule(SimpleScheduleBuilder.simpleSchedule()
.withIntervalInSeconds(1) //每隔一秒执行一次
.repeatForever()) //一直执行,直到结束时间
.endAt(new GregorianCalendar(2019,7,15,16,7,0).getTime())//设置结束时间
.build();
//定义一个JobDetail
//定义Job类为HelloQuartz类,这是真正的执行逻辑所在
JobDetail job = JobBuilder.newJob(HelloQuartz.class)
.withIdentity("job1","group1") //定义name/group
.build();
//调度器 中加入 任务和触发器
scheduler.scheduleJob(job, trigger);
//启动任务调度
scheduler.start();
Scheduler scheduler = StdSchedulerFactory.getDefaultScheduler();
//定义一个Trigger,触发条件类
Trigger trigger = TriggerBuilder.newTrigger()
.withIdentity("trigger1", "group1") //定义name/group
.startNow()//一旦加入scheduler,立即生效,即开始计时
.withSchedule(SimpleScheduleBuilder.simpleSchedule()
.withIntervalInSeconds(1) //每隔一秒执行一次
.repeatForever()) //一直执行,直到结束时间
.endAt(new GregorianCalendar(2019,7,15,16,7,0).getTime())//设置结束时间
.build();
//定义一个JobDetail
//定义Job类为HelloQuartz类,这是真正的执行逻辑所在
JobDetail job = JobBuilder.newJob(HelloQuartz.class)
.withIdentity("job1","group1") //定义name/group
.build();
//调度器 中加入 任务和触发器
scheduler.scheduleJob(job, trigger);
//启动任务调度
scheduler.start();
配置
# 名为:quartz.properties,放置在classpath下,如果没有此配置则按默认配置启动
# 指定调度器名称,非实现类
org.quartz.scheduler.instanceName = DefaultQuartzScheduler
# 指定线程池实现类
org.quartz.threadPool.class = org.quartz.simpl.SimpleThreadPool
# 线程池线程数量
org.quartz.threadPool.threadCount = 10
# 优先级,默认5
org.quartz.threadPool.threadPriority = 5
# 非持久化job
org.quartz.jobStore.class = org.quartz.simpl.RAMJobStore
# 指定调度器名称,非实现类
org.quartz.scheduler.instanceName = DefaultQuartzScheduler
# 指定线程池实现类
org.quartz.threadPool.class = org.quartz.simpl.SimpleThreadPool
# 线程池线程数量
org.quartz.threadPool.threadCount = 10
# 优先级,默认5
org.quartz.threadPool.threadPriority = 5
# 非持久化job
org.quartz.jobStore.class = org.quartz.simpl.RAMJobStore
核心类说明
Scheduler:调度器。所有的调度都是由它控制,是Quartz的大脑,所有任务都是由它来管理
Job:任务,想定时执行的事情(定义业务逻辑)
JobDetail:基于Job,进一步包装。其中关联一个Job,并为Job指定更详细的属性,比如标识等
Trigger:触发器。可以指定给某个任务,指定任务的触发机制。
Trigger
SimpleTrigger
以一定的时间间隔(单位是毫秒)执行的任务。
指定起始和截止时间(时间段)
指定时间间隔、执行次数
CronTrigger 【重点】
适合于更复杂的任务,它支持类型于Linux Cron的语法(并且更强大)。
- 指定Cron表达式即可
- 指定Cron表达式即可
示例
// 每天10:00-12:00,每隔2分钟执行一次
CronTrigger trigger = TriggerBuilder.newTrigger()
.withIdentity("t1", "g1")
.withSchedule(CronScheduleBuilder.cronSchedule("0 0/2 10-12 * * ?")).build();
CronTrigger trigger = TriggerBuilder.newTrigger()
.withIdentity("t1", "g1")
.withSchedule(CronScheduleBuilder.cronSchedule("0 0/2 10-12 * * ?")).build();
Cron表达式组成
表达式组成:"秒 分 时 日 月 星期几 [年]" ,其中"年" 是可选的,一般不指定。
- 如:"10 20 18 3 5 ?"代表"5月3日18点20分10秒,星期几不确定 "
- 如:"10 20 18 3 5 ?"代表"5月3日18点20分10秒,星期几不确定 "
Cron表达式符号
星号(*)
可用在所有字段中,表示对应时间域的每一个时刻,例如, 在分钟字段时,表示“每分钟”
问号(?)
该字符只在日期和星期字段中使用,它通常指定为“不确定值”
减号(-)
表达一个范围,如在小时字段中使用“10-12”,则表示从10到12点,即10,11,12
逗号(,)
表达一个列表值,如在星期字段中使用“MON,WED,FRI”,则表示星期一,星期三和星期五
斜杠(/)
x/y表达一个等步长序列,x为起始值,y为增量步长值。如在分钟字段中使用0/15,则表示为0,15,30和45秒,而5/15在分钟字段 中表示5,20,35,50
井号(#)
该字符只用在星期字段中,"4#2"代表第二个星期3,“5#4”代表第4个星期四
L
该字符只在日期和星期字段中使用,代表“Last”的意思,但它在两个字段中意思不同。
如果L用在星期字段里,则表示星期六,等同于7
L出现在星期字段里,而且在前面有一个数值x,则表示“这个月的最后一个周x”,例如,6L表示该月的最后星期五
L在日期字段中,表示这个月份的最后一天,如一月的31号,非闰年二月的28号
W
该字符只能出现在日期字段里,是对前导日期的修饰,表示离该日期最近的工作日
例如15W表示离该月15号最近的工作日,如果该月15号是星期六,则匹配14号星期五;如果15日是星期日,则匹配16号星期一;如果15号 是星期二,那结果就是15号星期二;但必须注意关联的匹配日期不能够跨月
LW组合
在日期字段可以组合使用LW,它的意思是当月的最后一个工作日
Spring整合Quartz 【重点】
导入依赖
<properties>
<springframework.version>5.1.6.RELEASE</springframework.version>
<quartz.version>2.2.3</quartz.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context-support</artifactId>
<version>${springframework.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-tx</artifactId>
<version>${springframework.version}</version>
</dependency>
<dependency>
<groupId>org.quartz-scheduler</groupId>
<artifactId>quartz</artifactId>
<version>${quartz.version}</version>
</dependency>
</dependencies>
</project>
<springframework.version>5.1.6.RELEASE</springframework.version>
<quartz.version>2.2.3</quartz.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context-support</artifactId>
<version>${springframework.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-tx</artifactId>
<version>${springframework.version}</version>
</dependency>
<dependency>
<groupId>org.quartz-scheduler</groupId>
<artifactId>quartz</artifactId>
<version>${quartz.version}</version>
</dependency>
</dependencies>
</project>
定义Job
定义一个Job类
配置
调度器 SchedulerFactoryBean
触发器 CronTriggerFactoryBean
JobDetail JobDetailFactoryBean
触发器 CronTriggerFactoryBean
JobDetail JobDetailFactoryBean
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<!--
Spring整合Quartz进行配置遵循下面的步骤:
1:定义工作任务的Job
2:定义触发器Trigger,并将触发器与工作任务绑定
3:定义调度器,并将Trigger注册到Scheduler
-->
<!-- 1:定义任务的bean ,这里使用JobDetailFactoryBean,也可以使用MethodInvokingJobDetailFactoryBean ,配置类似-->
<bean name="lxJob" class="org.springframework.scheduling.quartz.JobDetailFactoryBean">
<!-- 指定job的名称 -->
<property name="name" value="job1"/>
<!-- 指定job的分组 -->
<property name="group" value="job_group1"/>
<!-- 指定具体的job类 -->
<property name="jobClass" value="com.qf.quartz.MyJob"/>
</bean>
<!-- 2:定义触发器的bean,定义一个Cron的Trigger,一个触发器只能和一个任务进行绑定 -->
<bean id="cronTrigger" class="org.springframework.scheduling.quartz.CronTriggerFactoryBean">
<!-- 指定Trigger的名称 -->
<property name="name" value="trigger1"/>
<!-- 指定Trigger的名称 -->
<property name="group" value="trigger_group1"/>
<!-- 指定Tirgger绑定的JobDetail -->
<property name="jobDetail" ref="lxJob"/>
<!-- 指定Cron 的表达式 ,当前是每隔5s运行一次 -->
<property name="cronExpression" value="*/5 * * * * ?" />
</bean>
<!-- 3.定义调度器,并将Trigger注册到调度器中 -->
<bean id="scheduler" class="org.springframework.scheduling.quartz.SchedulerFactoryBean">
<property name="triggers">
<list>
<ref bean="cronTrigger"/>
</list>
</property>
<!-- 添加 quartz 配置,如下两种方式均可 -->
<!--<property name="configLocation" value="classpath:quartz.properties"></property>-->
<property name="quartzProperties">
<value>
# 指定调度器名称,实际类型为:QuartzScheduler
org.quartz.scheduler.instanceName = MyScheduler
# 指定连接池
org.quartz.threadPool.class = org.quartz.simpl.SimpleThreadPool
# 连接池线程数量
org.quartz.threadPool.threadCount = 11
# 优先级
org.quartz.threadPool.threadPriority = 5
# 不持久化job
org.quartz.jobStore.class = org.quartz.simpl.RAMJobStore
</value>
</property>
</bean>
</beans>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<!--
Spring整合Quartz进行配置遵循下面的步骤:
1:定义工作任务的Job
2:定义触发器Trigger,并将触发器与工作任务绑定
3:定义调度器,并将Trigger注册到Scheduler
-->
<!-- 1:定义任务的bean ,这里使用JobDetailFactoryBean,也可以使用MethodInvokingJobDetailFactoryBean ,配置类似-->
<bean name="lxJob" class="org.springframework.scheduling.quartz.JobDetailFactoryBean">
<!-- 指定job的名称 -->
<property name="name" value="job1"/>
<!-- 指定job的分组 -->
<property name="group" value="job_group1"/>
<!-- 指定具体的job类 -->
<property name="jobClass" value="com.qf.quartz.MyJob"/>
</bean>
<!-- 2:定义触发器的bean,定义一个Cron的Trigger,一个触发器只能和一个任务进行绑定 -->
<bean id="cronTrigger" class="org.springframework.scheduling.quartz.CronTriggerFactoryBean">
<!-- 指定Trigger的名称 -->
<property name="name" value="trigger1"/>
<!-- 指定Trigger的名称 -->
<property name="group" value="trigger_group1"/>
<!-- 指定Tirgger绑定的JobDetail -->
<property name="jobDetail" ref="lxJob"/>
<!-- 指定Cron 的表达式 ,当前是每隔5s运行一次 -->
<property name="cronExpression" value="*/5 * * * * ?" />
</bean>
<!-- 3.定义调度器,并将Trigger注册到调度器中 -->
<bean id="scheduler" class="org.springframework.scheduling.quartz.SchedulerFactoryBean">
<property name="triggers">
<list>
<ref bean="cronTrigger"/>
</list>
</property>
<!-- 添加 quartz 配置,如下两种方式均可 -->
<!--<property name="configLocation" value="classpath:quartz.properties"></property>-->
<property name="quartzProperties">
<value>
# 指定调度器名称,实际类型为:QuartzScheduler
org.quartz.scheduler.instanceName = MyScheduler
# 指定连接池
org.quartz.threadPool.class = org.quartz.simpl.SimpleThreadPool
# 连接池线程数量
org.quartz.threadPool.threadCount = 11
# 优先级
org.quartz.threadPool.threadPriority = 5
# 不持久化job
org.quartz.jobStore.class = org.quartz.simpl.RAMJobStore
</value>
</property>
</bean>
</beans>
操作
启动任务
工厂启动,调度器启动,任务调度开始
public static void main(String[] args) throws InterruptedException, SchedulerException {
// 工厂启动,任务启动,工厂关闭,任务停止
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
}
// 工厂启动,任务启动,工厂关闭,任务停止
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
}
任务操作
删除任务
scheduler.deleteJob(JobKey.jobKey("job1","job_group1"));
暂停、恢复
scheduler.pauseJob(JobKey.jobKey("job1","job_group1"));// 暂停工作
Thread.sleep(3000);
scheduler.resumeJob(JobKey.jobKey("job1","job_group1"));// 恢复工作
Thread.sleep(3000);
scheduler.resumeJob(JobKey.jobKey("job1","job_group1"));// 恢复工作
批量操作
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
System.out.println("=============");
StdScheduler scheduler = (StdScheduler) context.getBean("scheduler");
System.out.println(scheduler.getClass());
Thread.sleep(3000);
GroupMatcher<JobKey> group1 = GroupMatcher.groupEquals("group1");
scheduler.pauseJobs(group1); // 暂停组中所有工作
Thread.sleep(2000);
scheduler.resumeJobs(group1); // 恢复组中所有工作
System.out.println("=============");
StdScheduler scheduler = (StdScheduler) context.getBean("scheduler");
System.out.println(scheduler.getClass());
Thread.sleep(3000);
GroupMatcher<JobKey> group1 = GroupMatcher.groupEquals("group1");
scheduler.pauseJobs(group1); // 暂停组中所有工作
Thread.sleep(2000);
scheduler.resumeJobs(group1); // 恢复组中所有工作
0 条评论
下一页
为你推荐
查看更多