SpringFramework
一、介绍
Spring 是最受欢迎的企业级 Java 应用程序开发框架,数以百万的来自世界各地的开发人员使用 Spring 框架来创建性能好、易于测试、可重用的代码。Spring 框架是一个开源的 Java 平台,它最初是由 Rod Johnson 编写的,并且于 2003 年 6 月首次在 Apache 2.0 许可下发布。Spring 是轻量级的框架,其基础版本只有 2 MB 左右的大小。Spring 框架的核心特性是可以用于开发任何 Java 应用程序,但是在 Java EE 平台上构建 web 应用程序是需要扩展的。 Spring 框架的目标是使 J2EE 开发变得更容易使用,通过启用基于 POJO 编程模型来促进良好的编程实践。
1、Spring 框架主要的好处
-
Spring 可以使开发人员使用 POJOs 开发企业级的应用程序。只使用 POJOs 的好处是你不需要一个 EJB 容器产品,比如一个应用程序服务器,但是你可以选择使用一个健壮的 servlet 容器,比如 Tomcat 或者一些商业产品。
-
Spring 在一个单元模式中是有组织的。即使包和类的数量非常大,你只要担心你需要的,而其它的就可以忽略了。
-
Spring 不会让你白费力气做重复工作,它真正的利用了一些现有的技术,像ORM 框架、日志框架、JEE、Quartz 和 JDK 计时器,其他视图技术。
-
测试一个用 Spring 编写的应用程序很容易,因为环境相关的代码被移动到这个框架中。此外,通过使用 JavaBean-style POJOs,它在使用依赖注入注入测试数据时变得更容易。
-
Spring 的 web 框架是一个设计良好的 web MVC 框架,它为比如 Structs 或者其他工程上的或者不怎么受欢迎的 web 框架提供了一个很好的供替代的选择。
-
Spring 对JavaEE开发中非常难用的一些API(JDBC、JavaMail、远程调用等),都提供了封装,使这些API应用难度大大降低。
-
轻量级的 IOC 容器往往是轻量级的,例如,特别是当与 EJB 容器相比的时候。这有利于在内存和 CPU 资源有限的计算机上开发和部署应用程序。
-
Spring提供了一致的事务管理接口,可向下扩展到(使用一个单一的数据库,例如)本地事务并扩展到全局事务(例如,使用 JTA)。
2、spring 两大核心特性
2.1、IOC依赖注入(DI)
Spring 最认同的技术是控制反转的依赖注入(DI)模式。控制反转(IoC)是一个通用的概念,它可以用许多不同的方式去表达,依赖注入仅仅是控制反转的一个具体的例子。
当编写一个复杂的 Java 应用程序时,应用程序类应该尽可能的独立于其他的 Java 类来增加这些类可重用可能性,当进行单元测试时,可以使它们独立于其他类进行测试。依赖注入(或者有时被称为配线)有助于将这些类粘合在一起,并且在同一时间让它们保持独立。
到底什么是依赖注入?让我们将这两个词分开来看一看。这里将依赖关系部分转化为两个类之间的关联。例如,类 A 依赖于类 B。现在,让我们看一看第二部分,注入。所有这一切都意味着类 B 将通过 IoC 被注入到类 A 中。
依赖注入可以以向构造函数传递参数的方式发生,或者通过使用 setter 方法 post-construction。由于依赖注入是 Spring 框架的核心部分,所以我将在一个单独的章节中利用很好的例子去解释这一概念。
2.2、面向方面的程序设计(AOP):
Spring 框架的一个关键组件是面向方面的程序设计(AOP)框架。一个程序中跨越多个点的功能被称为横切关注点,这些横切关注点在概念上独立于应用程序的业务逻辑。有各种各样常见的很好的关于方面的例子,比如日志记录、声明性事务、安全性,和缓存等等。
在 OOP 中模块化的关键单元是类,而在 AOP 中模块化的关键单元是方面。AOP 帮助你将横切关注点从它们所影响的对象中分离出来,然而依赖注入帮助你将你的应用程序对象从彼此中分离出来。
Spring 框架的 AOP 模块提供了面向方面的程序设计实现,可以定义诸如方法拦截器和切入点等,从而使实现功能的代码彻底的解耦出来。使用源码级的元数据,可以用类似于.Net属性的方式合并行为信息到代码中。我将在一个独立的章节中讨论更多关于 Spring AOP 的概念。
3、Spring 框架结构
3.1、体系结构图
Spring 有可能成为所有企业应用程序的一站式服务点,然而,Spring 是模块化的,允许你挑选和选择适用于你的模块,不必要把剩余部分也引入。Spring 框架提供约 20 个模块,可以根据应用程序的要求来使用。
spring-core模块提供了框架的基本组成部分,包括 IoC 和依赖注入功能。
spring-beans 模块提供 BeanFactory,工厂模式的微妙实现,它移除了编码式单例的需要,并且可以把配置和依赖从实际编码逻辑中解耦。
context模块建立在由core和 beans 模块的基础上建立起来的,它以一种类似于JNDI注册的方式访问对象。Context模块继承自Bean模块,并且添加了国际化(比如,使用资源束)、事件传播、资源加载和透明地创建上下文(比如,通过Servelet容器)等功能。Context模块也支持Java EE的功能,比如EJB、JMX和远程调用等。ApplicationContext接口是Context模块的焦点。spring-context-support提供了对第三方库集成到Spring上下文的支持,比如缓存(EhCache, Guava, JCache)、邮件(JavaMail)、调度(CommonJ, Quartz)、模板引擎(FreeMarker, JasperReports, Velocity)等。
spring-expression模块提供了强大的表达式语言,用于在运行时查询和操作对象图。它是JSP2.1规范中定义的统一表达式语言的扩展,支持set和get属性值、属性赋值、方法调用、访问数组集合及索引的内容、逻辑算术运算、命名变量、通过名字从Spring IoC容器检索对象,还支持列表的投影、选择以及聚合等。
JDBC 模块提供了JDBC抽象层,它消除了冗长的JDBC编码和对数据库供应商特定错误代码的解析。
ORM 模块提供了对流行的对象关系映射API的集成,包括JPA、JDO和Hibernate等。通过此模块可以让这些ORM框架和spring的其它功能整合,比如前面提及的事务管理。
OXM 模块提供了对OXM实现的支持,比如JAXB、Castor、XML Beans、JiBX、XStream等。
JMS 模块包含生产(produce)和消费(consume)消息的功能。从Spring 4.1开始,集成了spring-messaging模块。。
事务模块为实现特殊接口类及所有的 POJO 支持编程式和声明式事务管理。(注:编程式事务需要自己写beginTransaction()、commit()、rollback()等事务管理方法,声明式事务是通过注解或配置由spring自动处理,编程式事务粒度更细)
Web 模块提供面向web的基本功能和面向web的应用上下文,比如多部分(multipart)文件上传功能、使用Servlet监听器初始化IoC容器等。它还包括HTTP客户端以及Spring远程调用中与web相关的部分。。
Web-MVC 模块为web应用提供了模型视图控制(MVC)和REST Web服务的实现。Spring的MVC框架可以使领域模型代码和web表单完全地分离,且可以与Spring框架的其它所有功能进行集成。
Web-Socket 模块为 WebSocket-based 提供了支持,而且在 web 应用程序中提供了客户端和服务器端之间通信的两种方式。
Web-Portlet 模块提供了用于Portlet环境的MVC实现,并反映了spring-webmvc模块的功能。
AOP 模块提供了面向方面的编程实现,允许你定义方法拦截器和切入点对代码进行干净地解耦,从而使实现功能的代码彻底的解耦出来。使用源码级的元数据,可以用类似于.Net属性的方式合并行为信息到代码中。
Aspects 模块提供了与 AspectJ 的集成,这是一个功能强大且成熟的面向切面编程(AOP)框架。
Instrumentation 模块在一定的应用服务器中提供了类 instrumentation 的支持和类加载器的实现。
Messaging 模块为 STOMP 提供了支持作为在应用程序中 WebSocket 子协议的使用。它也支持一个注解编程模型,它是为了选路和处理来自 WebSocket 客户端的 STOMP 信息。
测试模块支持对具有 JUnit 或 TestNG 框架的 Spring 组件的测试。
二、IOC&DI
1、IoC 容器
Spring 容器是 Spring 框架的核心。容器将创建对象,把它们连接在一起,配置它们,并管理他们的整个生命周期从创建到销毁。Spring 容器使用依赖注入(DI)来管理组成一个应用程序的组件。这些对象被称为 Spring Beans。
Spring通过阅读配置元数据提供的指令,容器知道对哪些对象进行实例化,配置和组装。配置元数据可以通过 XML,Java 注释或 Java 代码来表示。下图是 Spring 如何工作的高级视图。 Spring IoC 容器利用 Java 的 POJO 类和配置元数据来生成完全配置和可执行的系统或应用程序。
2、Spring 各种IOC 容器
容器类型-接口 | 实现类 | 描述 |
---|---|---|
ApplicationContext 容器 | FileSystemXmlApplicationContext | 该容器从 XML 文件中加载已被定义的 bean;你需要提供给构造器 XML 文件的完整路径 |
ClassPathXmlApplicationContext | 该容器从 XML 文件中加载已被定义的 bean。在这里,你不需要提供 XML 文件的完整路径,只需正确配置 CLASSPATH 环境变量即可,因为,容器会从 CLASSPATH 中搜索 bean 配置文件。 | |
WebXmlApplicationContext | 该容器会在一个 web 应用程序的范围内加载在 XML 文件中已被定义的 bean |
三、基于XML配置
1、注册一个Bean到容器
开发流程:
①创建工程 并在pom文件中添加依赖
<dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>4.3.14.RELEASE</version> </dependency>
②创建pojo类 Car.java
package com.xingxue.spring.domain; public class Car { private String brand; //品牌 private String carName; //型号名称 private String carType; // 类型 private Double price // 售价 //此处省略...sets and gets }
③创建spring的配置文件 applicationContext.xml 位于 classpath 下
<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"> <!--注册向IOC中注册一个Bean--> <bean name="mycar" class="com.xingxue.spring.domain.Car"> <!--注入属性:底层调用set方法--> <property name="brand" value="宝马"/> <property name="carName" value="X5"/> <property name="carType" value="SUV"/> <property name="price" value="400000"/> </bean> </beans>
④创建容器并测试
@Test public void test1(){ ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml"); Object car= ctx.getBean("mycar"); System.out.println(car); }
2、bean 的作用域
当在 Spring 中定义一个 bean 时,你必须声明该 bean 的作用域的选项。例如,为了强制 Spring 在每次需要时都产生一个新的 bean 实例,你应该声明 bean 的作用域的属性为 prototype。同理,如果你想让 Spring 在每次需要时都返回同一个bean实例,你应该声明 bean 的作用域的属性为 singleton。
作用域 | 描述 |
---|---|
singleton | 在spring IoC容器仅存在一个Bean实例,Bean以单例方式存在,默认值 |
prototype | 每次从容器中调用Bean时,都返回一个新的实例,即每次调用getBean()时,相当于执行newXxxBean() |
request | 每次HTTP请求都会创建一个新的Bean,该作用域仅适用于WebApplicationContext环境 |
session | 同一个HTTP Session共享一个Bean,不同Session使用不同的Bean,仅适用于WebApplicationContext环境 |
global-session | 一般用于Portlet应用环境,该运用域仅适用于WebApplicationContext环境 |
<bean name="mycar2" class="com.xingxue.spring.domain.Car" scope="prototype"> <!--注入属性:底层调用set方法--> <property name="brand" value="宝马"/> <property name="carName" value="X5"/> <property name="carType" value="SUV"/> <property name="price" value="400000"/> </bean>
这里只讨论 singleton prototype ,其他三个需要在使用web环境下有效
3、bean 的生命周期
生命周期指bean床创建到初始化到销毁的一个过程,spring中可以监听到这个过程
<bean name="mycar3" class="com.xingxue.spring.domain.Car" scope="prototype" init-method="begin" destroy-method="end"> <!--注入属性:底层调用set方法--> <property name="brand" value="宝马"/> <property name="carName" value="X5"/> <property name="carType" value="SUV"/> <property name="price" value="400000"/> </bean>
init-method="begin" 监听bean初始化 begin为bean 中的一个方法
destroy-method="end" 监听bean销毁 end为bean 中的一个方法
4、依赖注入
当注册一个bean时,bean的属性需要赋值,为这些属性赋值就是为其依赖赋值,赋值的过程就是依赖注入的过程spring中有两种方式。
-
属性set方法注入(调用Bean中提供的set方法)
<!-- 向ioc容器中注册了一个bean --> <bean id="car" class="com.xingxue.spring.pojo.Car" scope="prototype" destroy-method="destory" init-method="init"> <!--为bean属性赋值--> <property name="brand" value="宝马"/> <property name="carName" value="X5"/> <property name="type" value="SUV" /> <property name="price" value="500000"/> </bean> <bean id="p1" class="com.xingxue.spring.pojo.Person"> <property name="name" value="谭仓龙"/> <property name="age" value="24"/> <property name="car" ref="car"> </bean>
这里使用的是外部 bean, 如果只想用一次可以使用内部 bean
<bean id="p1" class="com.xingxue.spring.pojo.Person"> <property name="name" value="谭仓龙"/> <property name="age" value="24"/> <!--<property name="car" ref="car">--> <property name="car"> <bean class="com.xingxue.spring.pojo.Car"> <property name="brand" value="Audi"/> <property name="type" value="B级车"/> <property name="carName" value="a6l" /> <property name="price" value="300000"/> </bean> </property> </bean>
Spring中简单的数据类型 如 8大基本数据类型及其包装类和String 类型可以通过 value直接赋值,但如果是自定义的类型,需要使用 ref 引用赋值,或者使用内部bean赋值。
List[数组] Set Map Properties 类型注入
①List
<bean id="st" class="com.xingxue.spring.pojo.Student"> <property name="name" value="王红"/> <property name="hobby"> <list> <value>Maven 入门到精通</value> <value>Mybatis</value> <value>LOL</value> </list> </property> </bean>
②Set
<bean id="st2" class="com.xingxue.spring.pojo.Student"> <property name="name" value="杨询"/> <property name="skills"> <set> <ref bean="car"></ref> <value>扔石头</value> <value>打劫</value> </set> </property> </bean>
③Map
<bean id="st4" class="com.xingxue.spring.pojo.Student"> <property name="name" value="李心浩"/> <property name="maps"> <map> <entry key="m1" value="范冰冰"/> <entry key="m2" value="成龙"/> <entry key="m3" value="摩登兄弟"/> </map> </property> </bean>
④Properties
<bean id="st4" class="com.xingxue.spring.pojo.Student"> <property name="name" value="李心浩"/> <property name="maps"> <map> <entry key="m1" value="范冰冰"/> <entry key="m2" value="成龙"/> <entry key="m3" value="摩登兄弟"/> </map> </property> </bean>
补充: 读取properties文件中的数据赋值给 javaBean的属性。
db.properties文件
user=root password=admin driver=com.mysql.jdbc.Driver url=jdbc:mysql:///xxx
dbutis.java
public class DButils { private String user; private String password; private String url; private String driver; // 此处省略 get set方法 }
spring 配置文件
<!-- 注册一个bean 读取外部propeties 文件 --> <bean class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer" > <property name="location" value="db.properties"/> </bean> <bean class="com.xingxue.spring.pojo.DButils" id="dbutis"> <property name="user" value="${user}"/> <property name="url" value="${url}"/> <property name="password" value="${password}"/> <property name="driver" value="${driver}"/> </bean>
${ } :spring EL 表达式
-
构造器注入
public class CarServiceImpl implements CarService { //注入如 Dao 对象 CarDao carDao; public CarServiceImpl(CarDao carDao){ this.carDao=carDao; } @Override public List<Car> list() throws Exception { System.out.println("服务层执行 调用Dao层........"); return carDao.selectAll(); } }
<bean class="com.xingxue.spring.dao.impl.CarDaoImpl" id="carDao"/> <bean class="com.xingxue.spring.service.impl.CarServiceImpl" id="carService"> <constructor-arg name="carDao" ref="carDao"/> </bean>
5、自动装配
-
byType
<bean class="com.xingxue.spring.pojo.Person" autowire="byType"> <property name="name" value="赵浩"/> <property name="age" value="40"/> </bean>
autowire="byType" : 开启自动装配,根据l类型装配,IOC 容器把选择类型一致的Bean注入,当有多个bean同时满足注入条件,可以使用 autowire-candidate="false" 禁用默认满足条件的bean(加了这个属性的bean放弃注入)
-
byName
<bean class="com.xingxue.spring.pojo.Person" autowire="byName"> <property name="name" value="赵浩"/> <property name="age" value="40"/> </bean>
autowire="byName" : 开启自动装配,根据名字装配,IOC容器会把 现有Bean名字跟被装配属性名字一致Bean 自动注入
-
constructor
<bean class="com.xingxue.spring.pojo.Person" autowire="constructor"> <property name="name" value="赵浩"/> <property name="age" value="40"/> </bean>
和byType类似,根据构造器参数类型注入
-
no
autowire="no" 就是不注入
四、基于注解配置
xml配置
<?xml version="1.0" encoding="UTF-8" ?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.3.xsd"> <!-- 包扫描 --> <context:component-scan base-package="com.xingxue.spring"/> <!-- 开启注解配置--> <context:annotation-config/> </beans>
java代码
控制器
@Controller public class CarController { // 自动注入服务层依赖 @Autowired private CarService carService; public void getAll() throws Exception{ System.out.println("调用CarService"); carService.list(); } public void setCarService(CarService carService) { this.carService = carService; } }
服务层
@Service public class CarServiceImpl implements CarService { // 自动注入如 持久层 依赖 @Autowired private CarDao carDao; @Override public List<Car> list() throws Exception { System.out.println("服务层执行 调用Dao层........"); return carDao.selectAll(); } public void setCarDao(CarDao carDao) { this.carDao = carDao; } }
持久层
@Repository public class CarDaoImpl implements CarDao { @Override public List<Car> selectAll() throws Exception { System.out.println("连接数据库查询到了全部数据.........."); return null; } }
各种注解:
@Component : 通用注解,表示一个组件,没有业务含义
@Controller: 控制器层专门注解,表示控制器
@Service: 服务层注解,表示业务逻辑
@Repository:持久层注解,表示持久层
@Autowired:自动注入依赖
五、Aop编程
AOP面向方面,是一种编程思想,是对面向对象(OOP)的完善和补充,弥补了OOP横向切入编程能力,AOP的主要作用是把非业务逻辑代码和业务逻辑代码分离。用于处理 日志,事务,权限验证,数据校验等场景。SprinpAOP默认使用JDK对象代理,当被代理对象没有接口时使用cglib动态代理技术。
1、动态代理
动态代理:首先aop实现的底层技术就是代理技术,代理技术是一种软件设计模式,叫代理模式,代理模式分按用途分可以有很多种,但是按照创建方式分为,静态代理和动态代理,静态代理,要创建N个代理类,动态代理动态生成代理对象,无需手动创建代理类,JDK也提供了动态代理机制。
以下设计静态代理示例代码:
public class IGirl { public void cook() ; } public class SimpleGirl implments IGirl{ public void cook() { System.out.println("做饭...."); } }
class ProxyGirl implments IGirl { private Girl relGril; public ProxyGirl(Girl girl){ this.relGril=girl; } public void cookie(){ System.out.println("买菜...."); this.relGril.cook(); } }
以下是动态代理的示例代码:
public interface IFly { public void fly(); }
public class Birds implements IFly { @Override public void fly() { System.out.println("鸟儿飞........"); } }
public class Plane implements IFly { @Override public void fly() { System.out.println("飞机飞.........."); } }
public class MyInvocationHandler implements InvocationHandler { //把被代理对对象传入 private IFly relobj; public MyInvocationHandler( IFly obj ){ this.relobj = obj; } @Override public Object invoke(Object proxy, Method method, Object[] args) { System.out.println("----------- 前置 增强 ---------------"); Object rs= null; try { //核心业务 rs = method.invoke(relobj , args ); System.out.println("----------返回 增强------------"); } catch (Exception e) { System.out.println("----------异常 增强------------"); } System.out.println("----------- 后置 增强 --------------"); return rs; } }
2、使用SpringAOP( 注解配置 )
① 引入依赖
<dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>4.3.14.RELEASE</version> </dependency> <dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjweaver</artifactId> <version>1.8.9</version> </dependency>
② 编写 切面
@Component @Aspect public class LogAspect { @Before("execution( * com.xingxue.spring.aop.SimpleCaculate.add(..) )") public void logBefore(JoinPoint joinPoint){ System.out.println( "开始执行"+joinPoint.getSignature()+ System.currentTimeMillis() ); } @After("execution(* com.xingxue.spring.aop.SimpleCaculate.sub(..))") public void logAfter( JoinPoint joinPoint ){ System.out.println("结束执行"+joinPoint.getSignature()+ System.currentTimeMillis()); } }
③ 编写 接口和实现类
package com.xingxue.spring.aop; /** * 计算器接口 */ public interface Icaculate { public int add(int a, int b ); public int sub(int a, int b); }
编写 实现类
package com.xingxue.spring.aop; import org.springframework.stereotype.Component; /** * 计算器实现类 */ @Component public class SimpleCaculate implements Icaculate { @Override public int add(int a, int b) { return a+b; } @Override public int sub(int a, int b) { return a-b; } }
④ 配置xml
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xmlns:aop="http://www.springframework.org/schema/aop" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.3.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.3.xsd"> <!--开启包扫描--> <context:component-scan base-package="com.xingxue.spring.aop"/> <!--开启注解配置--> <context:annotation-config/> <!-- 开启动态代理--> <aop:aspectj-autoproxy /> </beans>
⑤测试
@Test public void test(){ ApplicationContext ctx = new ClassPathXmlApplicationContext("aop.xml"); Icaculate cc= ctx.getBean(Icaculate.class); cc.add(1,1); cc.sub(1,1); }
开始执行int com.xingxue.spring.aop.Icaculate.add(int,int)1536031653004结束执行int com.xingxue.spring.aop.Icaculate.sub(int,int)1536031653004
3、关于Aop的几个概念
-
Aspect(切面):通常是一个类,里面可以定义切入点和通知
-
JointPoint(连接点):程序执行过程中明确的点,一般是方法的调用(方法执行前 抛异常 返回数据 )
-
Advice(通知):AOP在特定的切入点上执行的增强处理,有before,after,afterReturning,afterThrowing,around
-
Pointcut(切入点):就是带有通知的连接点,在程序中主要体现为书写切入点表达式
-
AOP代理:AOP框架创建的对象,代理就是目标对象的加强。Spring中的AOP代理可以使JDK动态代理,也可以是CGLIB代理,前者基于接口,后者基于子类
4、aop中的五种通知
// @Before: 前置通知,目标方法运行前执行 @Before("execution( * com.xingxue.spring.aop.SimpleCaculate.add(..) )") public void logBefore(JoinPoint point){ System.out.println(new Date()+"开始执行"+ point.getSignature().getName()+"参数为:"+ Arrays.toString(point.getArgs())); }
// @ After:后置通知,目标方法无论执行成功与否,都会通知 @After("execution( * com.xingxue.spring.aop.SimpleCaculate.add(..) )") public void logAfter(JoinPoint point){ System.out.println(new Date()+"结束执行"+ point.getSignature().getName()+"参数为:"+ Arrays.toString(point.getArgs())); }
// @AfterReturning:返回通知,目标方法执行没有异常,返回了数据,会通知 @AfterReturning(value = "execution( * com.xingxue.spring.aop.SimpleCaculate.add(..) )", returning ="rs") public void logAfterReturn( JoinPoint point ,int rs ){ System.out.println(new Date()+"返回通知:"+ rs); }
// @AfterThrowing : 异常通知,目标方法执行失败,抛出异常则通知 @AfterThrowing(value = "execution( * com.xingxue.spring.aop.SimpleCaculate.add(..) )", throwing ="ex") public void logAfterReturn( JoinPoint point ,Exception ex ){ System.out.println(new Date()+"异常通知:"+ ex); }
// @Around : 环绕通知,是上述几个通知的总合,各种通知环绕目标方法周围 @Around("execution( * com.xingxue.spring.aop.SimpleCaculate.add(..) )") public Object arround(ProceedingJoinPoint point ){ Object rs=null; System.out.println("前置通知"); try { rs = point.proceed( point.getArgs() ); System.out.println("返回通知"); } catch (Throwable throwable) { System.out.println("异常通知"); throwable.printStackTrace(); } System.out.println("后置后置"); return rs; }
JoinPoint :连接点,获得连接点信息,如方法签名,参数 等信息,ProceedingJoinPoint 也是连接点,但是只能是环绕通知使用。
5、切点表达式
1、切点表达式
-
用于表示切入点的表达式, (scope) 包名.类名.方法名(参数类型,...)
-
scope: 方法访问权限: public private protected ,通常使用 * 表示任意
-
包名:不解释,实际情况下可能是多级,如com.xingxue.service.impl ,通常简写 com.xingxue.. ; .. 表示省略多级
-
类名:不解释,如 UserService 通常使用 * 表示任意类名 ,com.xingxue.service.*
-
方法名:不解释,如save 通常使用 * 表示任意方法名,如 com.xingxue.service.UserService.*
-
参数:不解释,如 int,int 通常使用 .. 表示任意多个参数 ,public com.xingxue.service.UserService.save(..)
-
最终通常写为 : * com.xingxue.service . . * . * ( . . )
2、切入点函数
中心思想是重用切点表达式,把表达式跟函数进行绑定,函数不需要实现
@Pointcut(value ="execution( * com.xingxue.spring..*.*(..) )") public static void beforePintcut(){ }
@Before(value = "beforePintcut()") public void logBefore(JoinPoint point){ System.out.println(new Date()+"开始执行"+ point.getSignature().getName()+"参数为:"+ Arrays.toString(point.getArgs())); }
跨类使用 切点
@Before(value = "com.xingxue.spring.aop.LogAspect.beforePintcut()") public void numValidate(JoinPoint point){ Object[] args= point.getArgs(); if( Integer.parseInt(args[0].toString() ) >0 && Integer.parseInt(args[1].toString() ) >0 ){ System.out.println("符合要求"); }else{ System.out.println("参数有误"); } }
6、切面的优先级
同一个目标切点多个切面,可以配置优先级,跳转执行顺序 @Order(n) n越小优先级越高,反之越低。
@Component @Aspect @Order(2) public class LogAspect { // 日志切面 }
@Component @Aspect @Order(1) public class ValidateAspect { // 数据验证切面 }
7、集合log4j处理日志
log4j依赖
<dependency> <groupId>log4j</groupId> <artifactId>log4j</artifactId> <version>1.2.17</version> </dependency>
log4j配置文件(参考网上)
log4j.rootLogger=debug,stdout,info,debug,warn,error #console log4j.appender.stdout=org.apache.log4j.ConsoleAppender log4j.appender.stdout.layout=org.apache.log4j.PatternLayout log4j.appender.stdout.layout.ConversionPattern= [%d{yyyy-MM-dd HH:mm:ss a}]:%p %l%m%n #info log log4j.logger.info=info log4j.appender.info=org.apache.log4j.DailyRollingFileAppender log4j.appender.info.DatePattern='_'yyyy-MM-dd'.log' log4j.appender.info.File=d:/logs/log_info.log log4j.appender.info.Append=true log4j.appender.info.Threshold=INFO log4j.appender.info.layout=org.apache.log4j.PatternLayout log4j.appender.info.layout.ConversionPattern=%d{yyyy-MM-dd HH:mm:ss a} [Thread: %t][ Class:%c >> Method: %l ]%n%p:%m%n
Logger logger = Logger.getLogger(LogAspect.class); logger.info("xxxx");
8、使用xml配置aop
<!--注册bean--> <bean id="caculate" class="com.xingxue.spring.aopxml.SimpleCaculate"/> <!--注册切面--> <bean id="log" class="com.xingxue.spring.aopxml.LogAspect"/> <!-- aop 配置 --> <aop:config> <!--配置切点--> <aop:pointcut id="pointcut" expression="execution(* com.xingxue.spring.aopxml..*.*(..))" /> <!--配置切面 oder:优先级 ref:引用声明的切面--> <aop:aspect order="1" ref="log" > <!--配置通知,method:切面中的方法 pointcut-ref:引用声明切点 --> <aop:before method="logBefore" pointcut-ref="pointcut"/> <aop:after method="logAfter" pointcut-ref="pointcut"/> </aop:aspect> </aop:config>
六、Spring-jdb支持
Spring 对jdbc 提供支持,简化jdbc开发流程,类似于Dbutils,使用Spring-jdbc 适用于简单的数据访问操作,当使用ORM框架对应当前系统比较麻烦的时候,可以采取spring-jdbc。spring-jdbc提供了一个操作数据的简单工具JdbcTemplate。
使用流程
① 添加依赖
<dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>4.3.14.RELEASE</version> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>5.1.46</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-jdbc</artifactId> <version>4.3.14.RELEASE</version> </dependency> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid</artifactId> <version>1.1.9</version> </dependency>
② 数据库连接信息
#mysql connection information jdbc.user=root jdbc.password=root jdbc.url=jdbc:mysql:///mybatis jdbc.driverClassName=com.mysql.jdbc.Driver
③ spring 配置
<!-- 引入外部配置文件(自动注册 PropertyPlaceholderConfigurer Bean) --> <context:property-placeholder location="classpath*:db.properties"/> <!--配置数据源--> <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource"> <property name="username" value="${jdbc.user}"/> <property name="password" value="${jdbc.password}"/> <property name="url" value="${jdbc.url}"/> <property name="driverClassName" value="${jdbc.driverClassName}"/> </bean> <!-- 注册jdbctemplate --> <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate"> <property name="dataSource" ref="dataSource"/> </bean>
④测试
---------------------------查询多个对象--------------------------- query(String sql , RowMapper mapper , Object...args):List<T> 说明: sql: 执行的sql语句 mapper: 映射规则RoMapper是接口,通常使用 BeanPropertyRowMapper 实现,作用是把字段转换到 Bean的属性 args: 可变参数,用于替换sql语句中的?,没有?可不填 ----------------------------------------------------------------- @Test public void test(){ JdbcTemplate tp = (JdbcTemplate) ctx.getBean("jdbcTemplate"); String sql = "select * from tb_employee "; // BeanPropertyRowMapper: 查询字段映射到 对象属性 List<EmpModel> list = tp.query(sql, new BeanPropertyRowMapper<>(EmpModel.class) ); System.out.println(list); } ---------------------------查询单个对象--------------------------- queryForObject(String sql , RowMapper mapper , Object...args):T 说明: sql: 执行的sql语句 mapper: 映射规则RoMapper是接口,通常使用 BeanPropertyRowMapper 实现,作用是把字段转换到 Bean的属性 args: 可变参数,用于替换sql语句中的?没有?可不填 ------------------------------------------------------------------ //@Test public void test3(){ JdbcTemplate tp = (JdbcTemplate) ctx.getBean("jdbcTemplate"); String sql = "select * from tb_employee where emp_no=? "; // BeanPropertyRowMapper: 查询字段映射到 对象属性 EmpModel model= tp.queryForObject(sql,new BeanPropertyRowMapper<>(EmpModel.class),2); System.out.println(model); } ---------------------------查询某个字段--------------------------- queryForObject(String sql , Class<T> cls , Object...args):T 说明: sql: 执行的sql语句 cls: Class 表示类类型,这里表示需要把查询的字段 转换成 什么类型返回 args: 可变参数,用于替换sql语句中的?没有?可不填 ------------------------------------------------------------------ @Test public void test4(){ JdbcTemplate tp = (JdbcTemplate) ctx.getBean("jdbcTemplate"); String sql = "select count(*) from tb_employee"; Integer count= tp.queryForObject(sql, Integer.class ); System.out.println(count); } ---------------------------查询多行记录 返回List<Map>--------------------------- queryForList(String sql ,Object...args):List<Map> 说明: sql: 执行的sql语句 args: 可变参数,用于替换sql语句中的?没有?可不填 ------------------------------------------------------------------------------- @Test public void test6(){ JdbcTemplate tp = (JdbcTemplate) ctx.getBean("jdbcTemplate"); String sql ="select * from tb_employee e, tb_department d where e.dept_no = d.dept_no"; List<Map<String, Object>> maps = tp.queryForList(sql); System.out.println(maps); } ---------------------------查询单行记录 返回Map--------------------------- queryForMap(String sql ,Object...args):Map 说明: sql: 执行的sql语句 args: 可变参数,用于替换sql语句中的?没有?可不填 ------------------------------------------------------------------------- @Test public void test6(){ JdbcTemplate tp = (JdbcTemplate) ctx.getBean("jdbcTemplate"); String sql ="select * from tb_employee e, tb_department d where e.dept_no = d.dept_no a nd e.emp_no=?"; List<Map<String, Object>> maps = tp.queryForMap(sql,1); System.out.println(maps); } ---------------------------更新操作(insert delete update)------------------------- update(String sql , Object..args): int 说明: sql: 执行的sql语句 args: 可变参数,用于替换sql语句中的?没有?可不填 ------------------------------------------------------------------------------------- @Test public void test7(){ JdbcTemplate tp = (JdbcTemplate) ctx.getBean("jdbcTemplate"); String sql ="insert into tb_employee values(?,?,?,?,?)"; int row= tp.update(sql,111,"小东","123323","重庆",11); System.out.println(row); } ------------------------批量更新操作(insert delete update)--------------------------- batchUpdate(sql,params):int[] 说明: sql: 执行的sql语句 args: 可变参数,用于替换sql语句中的?没有?可不填 @Test public void test8(){ JdbcTemplate tp = (JdbcTemplate) ctx.getBean("jdbcTemplate"); String sql ="insert into tb_employee values(?,?,?,?,?)"; List<Object[]> params = new ArrayList<Object[]>(); params.add( new Object[]{222,"a","123232","aaaa",11 } ); params.add( new Object[]{333,"a","123232","aaaa",11 } ); params.add( new Object[]{444,"a","123232","aaaa",11 } ); int[] rows= tp.batchUpdate(sql,params); System.out.println(Arrays.toString(rows)); }
七、Spring事务管理
1、事务概念
事务(Transaction)是并发控制的单位,是用户定义的一个操作序列。这些操作要么都做,要么都不做,是一个不可分割的工作单位。通过事务,数据库能将逻辑相关的一组操作绑定在一起,以便服务器保持数据的完整性。
2、事务特点
-
原子性(Atomicity):事务是一个原子操作,由一系列动作组成。事务的原子性确保动作要么全部完成,要么完全不起作用。
-
一致性(Consistency):一旦事务完成(不管成功还是失败),系统必须确保它所建模的业务处于一致的状态,而不会是部分完成部分失败。在现实中的数据不应该被破坏。
-
隔离性(Isolation):可能有许多事务会同时处理相同的数据,因此每个事务都应该与其他事务隔离开来,防止数据损坏。
隔离级别: 读完未提交,读到被人没有提交的数据,产生脏读 读已提交,只能读到已经提交的数据 , Oracle采取这个级别,但会出现不可重复读 可重复读 ,在同一个事务一个事务中,读取读取得到一致的效果,MySQL默认的隔离级别 序列化读, 当一个事务操作过程中,其他事务等待,序列化读会锁表,效率低,数据一致性高。
-
持久性(Durability):一旦事务完成,无论发生什么系统错误,它的结果都不应该受到影响,这样就能从任何系统崩溃中恢复过来。通常情况下,事务的结果被写到持久化存储器中。
3、Spring 事务管理
Spring 把事务处理流程做了一致性的抽象(PlatformTransactionManager接口),具体的事务管理交由各种事务管理器提供实现,类似于JDBC接口规范和各种数据库实现。
4、Spring声明式事务(注解)
①pom依赖
<dependency> <groupId>org.springframework</groupId> <artifactId>spring-tx</artifactId> <version>4.3.14.RELEASE</version> </dependency>
② 持久层 接口
public interface DeptDao { public Integer add(DeptModel model) throws Exception; } public interface EmpDao { public Integer add(EmpModel model) throws Exception; }
持久层 实现
@Repository public class DeptDaoImpl implements DeptDao { @Autowired private JdbcTemplate jdbcTemplate; @Override public Integer add(DeptModel model) throws Exception { String sql = "insert into tb_department(dept_no,dept_name,dept_loc) values ( ?, ?, ?)"; int row= jdbcTemplate.update(sql,model.getDeptNo(),model.getDeptName(),model.getDeptLoc()); return row; } } @Repository public class EmpDaoImpl implements EmpDao { @Autowired private JdbcTemplate jdbcTemplate; @Override public Integer add(EmpModel model) throws Exception { String sql = "insert into tb_employee values( ?,?,?,?,? )"; int row= jdbcTemplate.update(sql, model.getEmpNo(),model.getEmpName(),model.getEmpTel(),model.getEmpAddress(),model.getDeptNo()); return row; } }
③服务层 接口
public interface EmpService { //保存 员工 public Integer saveEmp(EmpModel empModel, DeptModel deptModel) throws Exception; }
④服务层 实现
@Service("empService") public class EmpServiceImpl implements EmpService { @Autowired private EmpDao empDao; @Autowired private DeptDao deptDao; @Override @Transactional public Integer saveEmp(EmpModel empModel, DeptModel deptModel) throws Exception { int row1= deptDao.add(deptModel); int row2= empDao.add(empModel); return row1+row2; } }
⑤ xml 配置
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:tx="http://www.springframework.org/schema/tx" xmlns:context="http://www.springframework.org/schema/context" xmlns:aop="http://www.springframework.org/schema/aop" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-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/tx http://www.springframework.org/schema/tx/spring-tx-4.3.xsd"> <!--包扫描--> <context:component-scan base-package="com.xingxue.spring"/> <!--注解配置--> <context:annotation-config/> <!-- 引入外部配置文件(自动注册 PropertyPlaceholderConfigurer Bean) --> <context:property-placeholder location="classpath*:db.properties"/> <!--配置数据源--> <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource"> <property name="username" value="${jdbc.user}"/> <property name="password" value="${jdbc.password}"/> <property name="url" value="${jdbc.url}"/> <property name="driverClassName" value="${jdbc.driverClassName}"/> </bean> <!-- 注册jdbctemplate --> <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate"> <property name="dataSource" ref="dataSource"/> </bean> <!-- 配置事务管理器 --> <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="dataSource"></property> </bean> <!-- 开启事务注解配置 --> <tx:annotation-driven transaction-manager="transactionManager"/> </beans>
xml依赖配置
<dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjweaver</artifactId> <version>1.8.13</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-aop</artifactId> <version>4.3.14.RELEASE</version> </dependency>
xml 配置
<!--Aop 声明式事务配置 --> <aop:config> <aop:pointcut id="pointcut" expression="execution( * com.xingxue.spring.service..*.*(..) )"/> <aop:advisor advice-ref="txAdvice" pointcut-ref="pointcut"/> </aop:config> <!--事务通知--> <tx:advice transaction-manager="transactionManager" id="txAdvice"> <tx:attributes> <tx:method name="*"/> <tx:method name="get*" read-only="true"/> <tx:method name="find*" read-only="true"/> </tx:attributes> </tx:advice>