1.spring介绍
Spring是一个开放源代码的设计层面框架,他解决的是业务逻辑层和其他各层的松耦合问题,因此它将面向接口的编程思想贯穿整个系统应用。Spring是于2003 年兴起的一个轻量级的Java 开发框架,由Rod Johnson创建。简单来说,Spring是一个分层的JavaSE/EE full-stack(一站式) 轻量级开源框架。
1.1 spring 特点
1.方便解耦,简化开发
通过Spring提供的IoC容器,我们可以将对象之间的依赖关系交由Spring进行控制,避免硬编码所造成的过度程序耦合。有了Spring,用户不必再为单实例模式类、属性文件解析等这些很底层的需求编写代码,可以更专注于上层的应用。
2.AOP编程的支持
通过Spring提供的AOP功能,方便进行面向切面的编程,许多不容易用传统OOP实现的功能可以通过AOP轻松应付。
3.声明式事务的支持
在Spring中,我们可以从单调烦闷的事务管理代码中解脱出来,通过声明式方式灵活地进行事务的管理,提高开发效率和质量。
4.方便程序的测试
可以用非容器依赖的编程方式进行几乎所有的测试工作,在Spring里,测试不再是昂贵的操作,而是随手可做的事情。例如:Spring对Junit4支持,可以通过注解方便的测试Spring程序。
5.方便集成各种优秀框架
Spring不排斥各种优秀的开源框架,相反,Spring可以降低各种框架的使用难度,Spring提供了对各种优秀框架(如Struts,Hibernate、Hessian、Quartz)等的直接支持。
6.降低Java EE API的使用难度
1.2 spring组织架构
ORM- object relation mapping 对象关系映射
OXM-Object xml mapping 对象xml映射
JMS - Java消息服务(Java Message Service ,JMS)
WebSocket protocol 是HTML5一种新的协议。它实现了浏览器与服务器全双工通信(full-duplex)。一开始的握手需要借助HTTP请求完成。Socket是传输控制层协议,WebSocket是应用层协议。
Portlet是一种Web组件-就像servlets-是专为将合成页面里的内容聚集在一起而设计的。通常请求一个portal页面会引发多个portlets被调用。每个portlet都会生成标记段,并与别的portlets生成的标记段组合在一起嵌入到portal页面的标记内
spring全家桶:spring,Spring Data、Spring MVC、Spring Boot、Spring Cloud(微服务)
1.3 spring下载
Spring官网:http://spring.io
Spring资源地址:http://repo.spring.io/release/org/springframework/spring
1.4 spring的核心模块
- - spring-core:依赖注入IOC与DI的最基本实现
- - spring-beans:Bean工厂与bean的装配
- - spring-context:spring的context上下文即IoC容器
- - spring-context-support
- - spring-expression:spring表达式语言
2.spring中的IOC
IOC是 Inverse of Control 的简写,意思是控制反转。是降低对象之间的耦合关系的设计思想。 DI是Dependency Injection的缩写,意思是依赖注入,说的是创建对象实例时,同时为这个对象注入它所依赖的属性
2.1. 实现过程
步骤1:添加jar包
<!-- Spring的核心工具包-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>5.0.8.RELEASE</version>
</dependency>
<!--在基础IOC功能上提供扩展服务,还提供许多企业级服务的支持,有邮件服务、
任务调度、远程访问、缓存以及多种视图层框架的支持-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.0.8.RELEASE</version>
</dependency>
<!-- Spring IOC的基础实现,包含访问配置文件、创建和管理bean等 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-beans</artifactId>
<version>5.0.8.RELEASE</version>
</dependency>
<!-- Spring context的扩展支持,用于MVC方面 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context-support</artifactId>
<version>5.0.8.RELEASE</version>
</dependency>
<!-- Spring表达式语言 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-expression</artifactId>
<version>5.0.8.RELEASE</version>
</dependency>
步骤2:创建配置文件applicationContext.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>
步骤3:在配置文件中创建对象
<bean id="对象名" class="类的完整路径">
<property name="属性名" ref="对象的id值"></property>
</bean>
步骤4:测试类中加载配置文件,获得对象
ApplicationContext app=new ClassPathXmlApplicationContext("spring.xml");
Users users=(Users)app.getBean("u1");
2.2 bean标签的属性介绍
注:
- spring中默认是单例模式创建对象
- singleton:单例模式
- prototype:多例模式
2.3 对象创建的方式
(1)无参构造:默认调用无参构造
(2)有参构造
public Person(String name , Car car){
this.name = name;
this.car = car;
System.out.println("Person的有参构造方法:"+name+car);
}
<bean name="person" class="com.xzk.spring.bean.Person">
<constructor-arg name="name" value="rose"/>
<constructor-arg name="car" ref="car"/>
</bean>
(3)静态方法创建对象
//静态工厂模式
public class PersonFactory {
public static Person createPerson(){
System.out.println("静态工厂创建Person");
return new Person();
}
}
<bean name="pf" class="com.xzk.PersonFactory" factory-method="createPerson" />
(4)非静态工厂方法
public class Users{
public Person createPerson1(){
System.out.println("非静态工厂创建Person");
return new Person();
}
}
<bean id="u2" class="com.bean.Users"></bean>
<bean id="u3" factory-method="createPerson1" factory-bean="u2"></bean>
2.4 springBean的生命周期
Bean 生命周期的整个执行过程描述如下
- 1)根据配置情况调用 Bean 构造方法或工厂方法实例化 Bean。
- 2)利用依赖注入完成 Bean 中所有属性值的配置注入。
- 3)如果 Bean 实现了 BeanNameAware 接口,则 Spring 调用 Bean 的 setBeanName() 方法传入当前Bean 的 id 值。
- 4)如果 Bean 实现了 BeanFactoryAware 接口,则 Spring 调用 setBeanFactory() 方法传入当前工厂实例的引用。
- 5)如果 Bean 实现了 ApplicationContextAware 接口,则 Spring 调用 setApplicationContext() 方法传入当前 ApplicationContext 实例的引用。
- 6)如果 BeanPostProcessor 和 Bean 关联,则 Spring 将调用该接口的预初始化方法postProcessBeforeInitialzation() 对 Bean 进行加工操作,此处非常重要,Spring 的 AOP 就是利用它实现的。
- 7)如果 Bean 实现了 InitializingBean 接口,则 Spring 将调用 afterPropertiesSet() 方法。初始化bean的时候执行,可以针对某个具体的bean进行配置。afterPropertiesSet 必须实现 InitializingBean接口。实现 InitializingBean接口必须实afterPropertiesSet方法。
- 8)如果在配置文件中通过 init-method 属性指定了初始化方法,则调用该初始化方法。
- 9)如果 BeanPostProcessor 和 Bean 关联,则 Spring 将调用该接口的初始化方法postProcessAfterInitialization()。此时,Bean 已经可以被应用系统使用了。
- 10)如果在 中指定了该 Bean 的作用范围为 scope="singleton",则将该 Bean 放入 Spring IoC 的缓存池中,将触发 Spring 对该 Bean 的生命周期管理;如果在 中指定了该 Bean 的作用范围为scope="prototype",则将该 Bean 交给调用者,调用者管理该 Bean 的生命周期,Spring 不再管理该Bean。
- 11)如果 Bean 实现了 DisposableBean 接口,则 Spring 会调用 destory() 方法将 Spring 中的 Bean销毁;如果在配置文件中通过 destory-method 属性指定了 Bean 的销毁方法,则 Spring 将调用该方法对 Bean 进行销毁。
3.DI注入值
分类:一种是调取属性的set方法赋值,第二种使用构造方法赋值
3.1 set注入值
3.1.1 基本属性类型值注入
<property name="name" value="jeck" />
3.1.2 引用属性类型值注入
<property name="car" ref="car"></property>
3.2 构造注入:
3.2.1 可以通过name属性,按照参数名赋值
public Person(String name , Car car){
this.name = name;
this.car = car;
System.out.println("Person的有参构造方法:"+name+car);
}
<bean name="person" class="com.xzk.spring.bean.Person">
<constructor-arg name="name" value="rose"/>
<constructor-arg name="car" ref="car"/>
</bean>
3.2.2 可以通过index属性,按照参数索引注入
<bean name="person2" class="com.xzk.spring.bean.Person">
<constructor-arg name="name" value="helen" index="0"></constructor-arg>
<constructor-arg name="car" ref="car" index="1"></constructor-arg>
</bean>
3.2.3 使用type注入
public Person(Car car, String name) {
super();
System.out.println("Person(Car car, String name)");
this.name = name;
this.car = car;
}
public Person(Car car, Integer name) {
super();
System.out.println("Person(Car car, Integer name)");
this.name = name + "";
this.car = car;
}
<bean name="person2" class="com.xzk.spring.bean.Person">
<constructor-arg name="name" value="988" type="java.lang.Integer">
</constructor-arg>
<constructor-arg name="car" ref="car" ></constructor-arg>
</bean>
3.3 spel spring表达式
<bean name="car" class="com.xzk.spring.bean.Car" >
<property name="name" value="mime" />
<property name="color" value="白色"/>
</bean>
<!--利用spel引入car的属性 -->
<bean name="person1" class="com.xzk.spring.bean.Person" p:car-ref="car">
<property name="name" value="#{car.name}"/>
<property name="age" value="#{person.age}"/>
</bean>
3.4 p命名空间注入值
使用p:属性名 完成注入,走set方法
- - 基本类型值: p:属性名="值"
- - 引用类型值: P:属性名-ref="bean名称"
实现步骤:配置文件中 添加命名空间p
xmlns:p="http://www.springframework.org/schema/p"
实例:
<bean id="u6" class="com.entity.Users" p:age="30" p:name="李四" p:studentref="stu1"></bean>
3.5 复杂类型注入
Object[],list,set,map,java.util.Properties
<bean id="u1" class="com.xzk.bean.Users"></bean>
<bean id="t1" class="com.xzk.bean.Teacher">
<!-- 数组变量注入 -->
<property name="objects">
<list>
<value>张三1</value>
<value>123</value>
<value>abc</value>
<!--引用类型-->
<ref bean="u1"></ref>
</list>
</property>
<!-- 集合变量赋值-->
<property name="list">
<list>
<value>张三2</value>
<value>1234</value>
<!--集合变量内部包含集合-->
<list>
<value>集合中的集合1</value>
<value>集合中的集合2</value>
<value>集合中的集合3</value>
</list>
<ref bean="u1"></ref>
</list>
</property>
<!--set赋值 -->
<property name="set">
<set>
<value>张三3</value>
<value>12345</value>
<value>abcde</value>
<ref bean="u1"></ref>
</set>
</property>
<!--map赋值 -->
<property name="map">
<map>
<entry key="班长" value="刘能"></entry>
<entry key="校花" value="谢大脚"></entry>
<entry key="user" value-ref="u1"></entry>
</map>
</property>
<!-- properties赋值 -->
<property name="properties">
<props>
<prop key="username">root</prop>
<prop key="password">1234</prop>
</props>
</property>
</bean>
部分类型获取数据
3.6 自动注入(由程序自动给属性赋值)
autowire:
- no 不自动装配(默认值)
- byName 属性名=id名 ,调取set方法赋值
- byType 属性的类型和id对象的类型相同,当找到多个同类型的对象时报错,调取set方法赋值
- constructor 构造方法的参数类型和id对象的类型相同,当没有找到时,报错。调取构造方法赋值
示例:
<bean id="service" class="service.impl.UserServiceImpl" autowire="constructor"></bean>
<bean id="uservice" class="com.xzk.service.impl.UsersServiceImpl" autowire="byType">
配置全局自动装配:
<beans default-autowire="constructor/byName/byType/no">
4.注解实现IOC
使用注解不需要get/set方法
4.1 配置文件中添加约束
参考文件位置:
spring-framework-5.0.8.RELEASE\docs\spring-framework-reference\html\xsdconfiguration.html
xmlns:context="http://www.springframework.org/schema/context"
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd
4.2 配置注解扫描:指定扫描包下所有类中的注解,扫描包时,会扫描包所有的子孙包
<!--扫描包设置-->
<context:component-scan base-package="com.xzk.spring.bean"></context:component-scan>
4.3 注解
4.3.1 添加在类名上
@Component("对象名")
@Service("person") // service层
@Controller("person") // controller层
@Repository("person") // dao层
@Scope(scopeName="singleton") //单例对象
@Scope(scopeName="prototype") //多例对象
4.3.2 添加在属性上:
@Value("属性值")
private String name;
@Autowired //如果一个接口类型,同时有两个实现类,则报错,此时可以借助@Qualifier("beanname")
@Qualifier("bean name")
private Car car;
//说明:@Resource 是java的注释,但是Spring框架支持,@Resource指定注入哪个名称的对象
//@Resource(name="对象名") == @Autowired + @Qualifier("name")
@Resource(name="baoma")
private Car car;
4.3.3 添加在方法上
@PostConstruct //等价于init-method属性
public void init(){
System.out.println("初始化方法");
}
@PreDestroy //等价于destroy-method属性
public void destroy(){
System.out.println("销毁方法");
}
5.Aop介绍
AOP(Aspect Oriented Programming)即面向切面编程。即在不改变原程序的基础上为代码段增加新的功能。应用在权限认证、日志、事务。
AOP的作用在于分离系统中的各种关注点,将核心关注点和横切关注点分离开来。
6.AOP的实现机制
- - JDK 的动态代理:针对实现了接口的类产生代理。InvocationHandler接口
- - CGlib 的动态代理:针对没有实现接口的类产生代理,应用的是底层的字节码增强的技术 生成当前类的子类对象,MethodInterceptor接口
6.1 JDK动态代理实现
1. 创建接口和对应实现类
public interface UserService {
public void login();
}
//实现类
public class UserServiceImpl implements UserService {
public void login(){}
}
2. 创建动态代理类,实现InvocationHandler接口
public class Dai implements InvocationHandler {
private UsersService usersService; //目标对象
public Dai(UsersService usersService) {
this.usersService= usersService;
}
//定义需要代理类做的事情
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//本方法中的其他输出输入增强
//proxy 代理方法被调用的代理实例
System.out.println("方法触发了");
//执行被代理类 原方法
Object invoke = method.invoke(usersService, args);
System.out.println("执行完毕了");
return invoke;
}
}
测试
public class Test1 {
public static void main(String[] args) {
//测试JDK动态代理技术
UserService us = new UserServiceImpl();
//又代理对象调取方法
Dai dai = new Dai(usersDao);
//生成代理对象,这里不能转换成一个实际的类,必须是接口类型
UserService uservice = (UserService)Proxy.newProxyInstance(UserService.getClass().getClassLoader(), UserService.getClass().getInterfaces(), dai);
//由代理对象调取方法
uservice.login();
}
}
测试结果: 在调用接口方法的前后都会添加代理类的方法!
6.2 CGlib实现代理
- > 使用JDK创建代理有一个限制,它只能为接口创建代理实例.这一点可以从Proxy的接口方法newProxyInstance(ClassLoader loader,Class [] interfaces,InvocarionHandler h)中看的很清楚
- > 第二个入参 interfaces就是需要代理实例实现的接口列表.
- > 对于没有通过接口定义业务方法的类,如何动态创建代理实例呢? JDK动态代理技术显然已经黔驴技穷,CGLib作为一个替代者,填补了这一空缺.
- > CGLib采用底层的字节码技术,可以为一个类创建子类,在子类中采用方法拦截的技术拦截所有父类方法的调用并顺势织入横切逻辑
添加依赖包:
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>3.2.5</version>
</dependency>
1.创建普通类
public class Users{
public void login(){}
}
2. 创建创建CGLib代理器
class CgProxy implements MethodInterceptor {
public Object intercept(Object o, Method method, Object[] objects,MethodProxy methodProxy) throws Throwable {
System.out.println("输出语句1");
//参数:Object为由CGLib动态生成的代理类实例,Method为上文中实体类所调用的被代理的方法
//引用,Object[]为参数值列表,MethodProxy为生成的代理类对方法的代理引用。
Object obj= methodProxy.invokeSuper(o,objects);
System.out.println("输出语句2");
return obj;
}
}
测试
public static void main(String[] args) {
//1.创建真实对象
Users users = new Users();
//2.创建代理对象
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(users.getClass());
enhancer.setCallback(new CglibProxy());
Users o = (Users) enhancer.create();//代理对象
o.login();
}
结论:spring同时使用了这两种方式,底层会自行判断应该使用哪种
6.3 两种代理方式的区别:
- 1、jdk动态代理生成的代理类和委托类实现了相同的接口;
- 2、cglib动态代理中生成的字节码更加复杂,生成的代理类是委托类的子类,且不能处理被final关键字修饰的方法;
- 3、jdk采用反射机制调用委托类的方法,cglib采用类似索引的方式直接调用委托类方法;
7.spring中使用aop
(1)添加jar包
<dependencies>
<dependency>
<groupId>aopalliance</groupId>
<artifactId>aopalliance</artifactId>
<version>1.0</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.8.13</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>5.0.8.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
<version>5.0.8.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>5.0.8.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.0.8.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-beans</artifactId>
<version>5.0.8.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context-support</artifactId>
<version>5.0.8.RELEASE</version>
</dependency>
</dependencies>
(2)添加项目原有的调取过程
(3)创建增强类(本质上就是一个普通类)
//前置通知:目标方法运行之前调用 aop:before
//后置通知(如果出现异常不会调用):在目标方法运行之后调用 aop:after-returning
//环绕通知:在目标方法之前和之后都调用 aop:around
//最终通知(无论是否出现 异常都会调用):在目标方法运行之后调用 aop:after
//异常增强:程序出现异常时执行(要求:程序代码中不要处理异常) aop:after-throwing
public class MyAop {
//前置增强-调取目标方法之前执行
public void before(JoinPoint joinPoint){
System.out.println("日志开始");
System.out.println(new Date()+"切点对象信息:"+joinPoint.getTarget().getClass().getSimpleName());
System.out.println("方法信息:"+joinPoint.getSignature());
System.out.println("参数信息:"+joinPoint.getArgs());
}
//后置增强-调取目标方法之后执行
public void after(){
System.out.println("日志结束");
}
//环绕增强增强-调取目标方法前后都执行,先于前置增强开始,先于后置增强结束
public void myaroud(ProceedingJoinPoint joinPoint){
System.out.println("环绕开始");
try {
joinPoint.proceed(); //调取目标方法
} catch (Throwable throwable) {
throwable.printStackTrace();
}
System.out.println("环绕结束");
}
//异常
public void myex(){
System.out.println("异常增强");
}
//最终增强-无论是否有异常都要执行
public void aftera(){
System.out.println("最终增强");
}
}
特殊的前置增强:
public class BeforeAop implements MethodBeforeAdvice {
public void before(Method method, Object[] objects, Object o) throws Throwable {
System.out.println("特殊的前置增强");
System.out.println("目标方法:"+method.getName());
System.out.println("参数:"+objects);
System.out.println("对象:"+o);
}
}
(4)添加aop命名空间
xmlns:aop="http://www.springframework.org/schema/aop"
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-4.2.xsd
(5)设置配置文件
<!--1.创建目标类对象-->
<bean id="udao" class="com.xzk.dao.impl.UsersDaoImpl"></bean>
<bean id="uservice" class="com.xzk.service.impl.UsersServiceImpl">
<property name="usersDao" ref="udao"></property>
</bean>
<!---2.创建增强类的对象-->
<bean id="myaop" class="com.xzk.util.MyAop"></bean>
<--特殊前置增强对象-->
<bean id="beforeaop" class="com.xzk.util.BeforeAop"></bean>
<!--3.建立增强类和目标方法之间的关系-->
<aop:config>
<aop:pointcut id="mypc" expression="execution(* com.xzk.service.*.*(..))">
</aop:pointcut>
<!--特殊的前置增强-->
<aop:advisor advice-ref="beforeaop" pointcut-ref="mypc"></aop:advisor>
<!---<aop:aspect ref="myaop">
<!--前置增强-->
<aop:before method="before" pointcut-ref="mypc"></aop:before>
<!--后置增强-->
<aop:after-returning method="after" pointcut-ref="mypc"></aop:after-returning>
<!--环绕增强-->
<!--<aop:around method="myaroud" pointcut-ref="mypc"></aop:around>-->
<!--异常增强-->
<aop:after-throwing method="myex" pointcut-ref="mypc"></aop:after-throwing>
<!--最终增强-->
<aop:after method="aftera" pointcut-ref="mypc"></aop:after>
</aop:aspect>--->
</aop:config>
注意:(1)环绕增强需要使用ProceedingJoinPoint 作为参数(2)注意标签顺序
8.切入点方法的定义
表达式匹配规则举例:
public * addUser(com.pb.entity.User):“*”表示匹配所有类型的返回值。
示例:
public int addUser(User u);
public String addUser(User u);
public void *(com.pb.entity.User):“*”表示匹配所有方法名。
示例:
public void selectUser(User u);
public void a(User u);
public void addUser (..):“..”表示匹配所有参数个数和类型。
示例:
public void addUser(int a)
public void addUser(int b,int c)
* com.pb.service.*.*(..):匹配com.pb.service 包下所有类的所有方法。
示例:
public void com.pb.service.A.a();
public String com.pb.service.B.a();
* com.pb.service..*(..):匹配com.pb.service 包及子包下所有类的所有方法
9.如何获取切入点信息
通过JoinPoint对象获取信息:
public void before(JoinPoint joinPoint){
System.out.println("日志开始");
System.out.println(new Date()+"切点对象信息:"+joinPoint.getTarget().getClass().getSimpleName());
System.out.println("方法信息:"+joinPoint.getSignature());
System.out.println("参数信息:"+joinPoint.getArgs()[0]);
}
10.特殊的前置增强-->Advisor前置增强实现
步骤
- 1.创建增强类,要求该类实现MethodBeforeAdvice接口
- 2.修改applicationContext.xml文件
- (1)创建增强类对象
- (2)定义增强和切入点的关系:
<aop:config> <!-- 表达式是被切入的方法的表达式 --> <aop:pointcut expression="execution(* biz.impl.*.*(..))" id="mypoint"/> <aop:advisor advice-ref="增强类对象的id" pointcut-ref="切入点对象的id"/> </aop:config>
public class BeforeAop implements MethodBeforeAdvice { public void before(Method method, Object[] objects, Object o) throws Throwable { System.out.println("特殊的前置增强"); System.out.println("目标方法:"+method.getName()); System.out.println("参数:"+objects); System.out.println("对象:"+o); } }
11.使用了AspectJ依赖注解开发
spring AOP的注解方式:
注:
- 1)增强类也需要创建对象(使用@Component)
- 2)要启动扫描spring注解包的代码:
<context:component-scan base-package="com.xzk"></context:component-scan>
<1> 除了启动spring的注解之外 还要启动aspectJ的注解方式
<!--扫描注解包-->
<context:component-scan base-package="xzk"></context:component-scan>
<!--启用aop注解-->
<aop:aspectj-autoproxy/>
<2> 在切面类(增强类)上添加:@Aspect
<3> 定义一个任意方法
@Pointcut("execution(* com.*.*(..))")
public void anyMethod(){}
为什么要定义一个任意方法??? 因为@Pointcut 要求要放在一个方法上
<5>用法:
@Component //创建增强类对象
@Aspect //标记是增强类
public class MyAop {
//定义切点,由于注解必须修饰方法,所以这里需要定义一个空方法
@Pointcut("execution( * xzk.service.*.*(..))")
public void abc(){}
//前置增强-调取目标方法之前执行
@Before("abc()")
public void before(JoinPoint joinPoint){
System.out.println("日志开始-前置增强");
// System.out.println(new Date()+"切点对象信息:"+joinPoint.getTarget().getClass().getSimpleName());
// System.out.println("方法信息:"+joinPoint.getSignature());
// System.out.println("参数信息:"+joinPoint.getArgs());
}
//后置增强-调取目标方法之后执行
@AfterReturning("abc()")
public void after(){
System.out.println("日志结束--后置增强");
}
@Around("abc()")
public void myaroud(ProceedingJoinPoint joinPoint){
System.out.println("环绕开始");
try {
joinPoint.proceed(); //调取目标方法
} catch (Throwable throwable) {
throwable.printStackTrace();
}
System.out.println("环绕结束");
}
//异常
@AfterThrowing("abc()")
public void myex(){
System.out.println("异常增强");
}
//最终增强-无论是否有异常都要执行
@After("abc()")
public void aftera(){
System.out.println("最终增强");
}
}
@Repository("udao")
public class UsersDaoImpl implements UsersDao {
public void test1() {
System.out.println("usersDao--TEST1");
}
}
@Service("uservice")
public class UsersServiceImpl implements UsersService {
@Resource(name="udao")
private UsersDao usersDao;
public void test2() {
System.out.println("userservice-test2");
}
public void test1() {
System.out.println("usersService---TEST1");
//System.out.println(9/0);
usersDao.test1();
}
}
public static void main(String[] args) {
//需求:在service层调取test1()方法时做增强
ApplicationContext applicationContext=new ClassPathXmlApplicationContext("application.xml");
UsersService usersService=(UsersService) applicationContext.getBean("uservice");
// usersService.test1();
usersService.test1();
}
注解方式中注解的顺序问题
- 1.没有异常情况下
- 环绕开始。。。。
- 前置增强开始执行
- insert-----------
- 环绕结束。。。。
- 最终增强
- 后置增强开始执行
- 相对顺序固定,注解换位置时不影响结果顺序
- 2.有异常
- 前置增强开始执行
- insert-----------
- 最终增强
- 异常增强
- 注意:不要使用环绕增强,使用的话,异常增强不执行
aop的应用场景:事务底层实现,日志,权限控制,mybatis中sql绑定,性能检测
12.Spring-JDBC 数据访问
12.1 使用spring-jdbc操作数据库
主要内容:学习使用JdbcTemplate API和 如何使用Spring管理 JdbcTemplate
步骤1. 引入jar包
<dependencies>
<!--jdbc依赖包-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>5.0.8.RELEASE</version>
</dependency>
<!--Mysql驱动包-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.46</version>
</dependency>
<!--事务的依赖包-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-tx</artifactId>
<version>5.0.8.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.0.8.RELEASE</version>
</dependency>
<!--数据库连接池依赖包-->
<dependency>
<groupId>com.mchange</groupId>
<artifactId>c3p0</artifactId>
<version>0.9.5.2</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.8.13</version>
</dependency>
</dependencies>
步骤2.测试
public static void main(String[] args) throws Exception {
//1.创建数据库连接池对象
ComboPooledDataSource dataSource=new ComboPooledDataSource();
dataSource.setDriverClass("com.mysql.jdbc.Driver");
dataSource.setJdbcUrl("jdbc:mysql://localhost:3306/hunan?useUnicode=true&characterEncoding=utf-8");
dataSource.setUser("root");
dataSource.setPassword("123456");
//2.创建jdbcTemplate对象
JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource);
//3.调取方法 增删改用update ,查询用query方法
int update = jdbcTemplate.update("insert into student(studentid,studentno,stuname) values(2,'stu001','王萌')");
System.out.println("执行成功,i="+update);
}
12.2. Spring管理JdbcTemplate
注意: 可以自己在RoleDaoImpl中添加 JdbcTemplate变量,如果不自动装载记得添加变量的set方法,
标准的操作,我们可以让RoleDaoImpl 继承 JdbcDaoSupport
示例:
public class RoleDaoImpl extends JdbcDaoSupport implements RoleDao {
public void save(Role role) {
String sql = "INSERT INTO role (rname,alias) value (?,?) ;";
getJdbcTemplate().update(sql,role.getRname(),role.getAlias());
}
}
配置文件:需要创建数据源和给RoleDaoImpl中的jdbcTemplate赋值
<bean name="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource"
p:jdbcUrl="jdbc:mysql://localhost:3306/xzk"
p:driverClass="com.mysql.jdbc.Driver"
p:user="root"
p:password="111"
/>
<!-- bean jdbcTemplate -->
<bean name="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="dataSource"/>
</bean>
<bean name="roleDao" class="com.xzk.spring.dao.impl.RoleDaoImpl">
<property name="jdbcTemplate" ref="jdbcTemplate"/>
</bean>
12.3 创建db.properties属性文件
内容:
driver=com.mysql.jdbc.Driver
url=jdbc:mysql://localhost:3306/hunan?useUnicode=true&characterEncoding=utf-8
uname=root
password=123456
修改配置文件:
<context:property-placeholder location="classpath:jdbc.properties">
</context:property-placeholder>
<bean id="ds" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<property name="driverClass" value="${driver}"/>
<property name="jdbcUrl" value="${url}"/>
<property name="user" value="${uname}"/>
<property name="password" value="${password}"/>
</bean>
<bean id="studentDao" class="com.xzk.dao.impl.StudentDaoImpl">
<property name="dataSource" ref="ds" ></property>
</bean>
关于属性文件中username=root报错的问题:username与spring框架参数重名,默认使用框架参数,要么改为uname,要么如下设置:
<context:property-placeholder location="db.properties"
system-properties-mode="FALLBACK"/>
- FALLBACK --- 默认值,不存在时覆盖
- NEVER --- 不覆盖
12.4 crud
JdbcTemplate常用方法:
- JdbcTemplate.update(sql,ArgsObj....); //多个参数值时,可以使用对象数组
- //DQL 查询单个
- jdbcTemplate.queryForObject(String var1, RowMapper var2, Object... var3);
- RowWapper 将结果封装的处理器; 得到Result解析成实体类对象即可!
- //DQL 查询多个
- jdbcTemplate.query(String var1, RowMapper var2, Object... var3); //查询所有
- getJdbcTemplate().query(sql,new BeanPropertyRowMapper<类名>(类名.class));//查询(单行+多行)
实例:
处理查询结果的自定义方法
(也可以自定义一个类,该类实现RowMapper接口的方式处理结果)
private Users chuliresult(ResultSet resultSet){
Users users=new Users();
try {
users.setUsername(resultSet.getString("username"));
users.setPassword(resultSet.getString("password"));
users.setUserid(resultSet.getInt("userid"));
} catch (SQLException e) {
e.printStackTrace();
}
return users;
}
单行查询
public Student findbyid(int id) {
String sql="select * from student where studentid=?";
Student stu = getJdbcTemplate().queryForObject(sql, new Object[]{id}, new RowMapper<Student>() {
public Student mapRow(ResultSet resultSet, int i) throws SQLException {
//和原来jdbc获取结果集之后的操作时一样的
Student student = new Student();
student.setStudentId(resultSet.getInt("studentid"));
student.setStuName(resultSet.getString("stuname"));
return student;
}
});
return stu;
}
多行查询
public List<Student> findall() {
String sql="select * from student";
List<Student> students = getJdbcTemplate().query(sql, new RowMapper<Student>() {
public Student mapRow(ResultSet resultSet, int i) throws SQLException {
//和原来jdbc获取结果集之后的操作时一样的
Student student = new Student();
student.setStudentId(resultSet.getInt("studentid"));
student.setStuName(resultSet.getString("stuname"));
return student;
}
});
return students;
}
实例:查询行数
String sql="select count(*) from users";
Integer integer = getJdbcTemplate().queryForObject(sql, Integer.class);
查询多列
Map<String, Object> map =
jdbcTemplate.queryForMap("select count(*),max(roleid),min(roleid) from role");
Set<Map.Entry<String, Object>> entrySet = map.entrySet();
Iterator<Map.Entry<String, Object>> iterator = entrySet.iterator();
while(iterator.hasNext()){
System.out.println(iterator.next());
}
查询单列值
String sql="select username from users";
List<String> list = getJdbcTemplate().queryForList(sql, String.class);
13.Spring事务管理
13.1 什么是事务(Transaction)
通过sql将逻辑相关的一组操作绑定在一起,以便服务器 保持数据的完整性(准确性)。
事务通常是以begin transaction开始,以commit或rollback结束。
事务执行的流程:开启事务->执行insert,update,delete->commit/rollback
设想网上购物的一次交易,其付款过程至少包括以下几步数据库操作:
1、更新客户所购商品的库存信息
2、保存客户付款信息--可能包括与银行系统的交互
3、生成订单并且保存到数据库中
4、更新用户相关信息,例如购物数量等等
数据库事务正是用来保证这种情况下交易的平稳性和可预测性的技术
13.2 为什么要使用事务?
- (1)为了提高性能
- (2)为了保持业务流程的完整性
- (3)使用分布式事务
13.3 事务的特性ACID
1 - 原子性(atomicity)
事务是数据库的逻辑工作单位,而且是必须是原子工作单位,对于其数据修改,要么全部执行,要么全
部不执行。
2、一致性(consistency)
事务在完成时,必须是所有的数据都保持一致状态。在相关数据库中,所有规则都必须应用于事务的修改,以保持所有数据的完整性。
3、隔离性(isolation)
一个事务的执行不能被其他事务所影响。企业级的数据库每一秒钟都可能应付成千上万的并发访问,因而带来了并发控制的问题。
4、持久性(durability)
一个事务一旦提交,事务的操作便永久性的保存在DB中。即使此时再执行回滚操作也不能撤消所做的
更改
13.4 事务的嵌套->传播行为propagation
事务的传播机制
- 事务的第一个方面是传播行为(propagation /,prɒpə'ɡeɪʃən/ behavior)。
- 当事务方法被另一个事务方法调用时,必须指定事务应该如何传播。
- 规定了事务方法和事务方法发生嵌套调用时事务如何进行传播
例如:方法可能继续在现有事务中运行,也可能开启一个新事务,并在自己的事务中运行。
Spring定义了七种传播行为:
propagtion_nested:
- 嵌套的事务可以独立于当前事务进行单独地提交或回滚。
- 如果当前事务不存在,那么其行为与PROPAGATION_REQUIRED一样。
注意:各厂商对这种传播行为的支持是有所差异的。可以参考资源管理器的文档来确认它们是否支持嵌套事务
事务传播行为失效的情况
spring事务是基于代理来实现的,所以:
- (1)private、final、static 方法无法被代理,所以添加事务无效
- (2)当绕过代理对象, 直接调用添加事务管理的方法时, 事务管理将无法生效。比如直接new出的对象。
- (3)在同一个类下,有2个方法,A、B,A没有事务,B有事务,但是A调用B时,方法B被标记的事务无效。 究其原因,因为此类的调用对象为代理对象,代理方法A调用真正的被代理方法A后,在被代理方法A中才会去调用方法B,此时this对象为被代理的对象,所以是不会通知到代理对象,也就变成了第二种情况,绕过了代理对象。所以无效
13.5 事务的隔离级别
MySQL数据库共定义了四种隔离级别:
- 1. Serializable(串行化):可避免脏读、不可重复读,幻读情况的发生。
- 2. Repeatable read(可重复读):可避免脏读、不可重复读情况的发生。
- 3. Read committed(读已提交):可避免脏读情况发生。
- 4. Read uncommitted(读未提交):最低级别,以上情况均无法保证。
Isolation.DEFAULT:为数据源的默认隔离级别(Read committed)
级别越高,数据越安全,但性能越低。
13.6 事务的实现
Spring XML配置声明事务
13.6.1. TransactionManager
在不同平台,操作事务的代码各不相同,因此spring提供了一个 TransactionManager 接口:
- - DateSourceTransactionManager 用于 JDBC 的事务管理
- - HibernateTransactionManager 用于 Hibernate 的事务管理
13.6.2 接口的定义
事务的属性介绍:这里定义了传播行为、隔离级别、超时时间、是否只读
package org.springframework.transaction;
public interface TransactionDefinition {
int PROPAGATION_REQUIRED = 0; //支持当前事务,如果不存在,就新建一个
int PROPAGATION_SUPPORTS = 1; //支持当前事务,如果不存在,就不使用事务
int PROPAGATION_MANDATORY = 2; //支持当前事务,如果不存在,就抛出异常
int PROPAGATION_REQUIRES_NEW = 3;//如果有事务存在,挂起当前事务,创建一个新的事物
int PROPAGATION_NOT_SUPPORTED = 4;//以非事务方式运行,如果有事务存在,挂起当前事务
int PROPAGATION_NEVER = 5;//以非事务方式运行,如果有事务存在,就抛出异常
int PROPAGATION_NESTED = 6;//如果有事务存在,则嵌套事务执行
int ISOLATION_DEFAULT = -1;//默认级别,MYSQL: 默认为REPEATABLE_READ级别
SQLSERVER: 默认为READ_COMMITTED
int ISOLATION_READ_UNCOMMITTED = 1;//读取未提交数据(会出现脏读, 不可重复读) 基本不使用
int ISOLATION_READ_COMMITTED = 2;//读取已提交数据(会出现不可重复读和幻读)
int ISOLATION_REPEATABLE_READ = 4;//可重复读(会出现幻读)
int ISOLATION_SERIALIZABLE = 8;//串行化
int TIMEOUT_DEFAULT = -1;//默认是-1,不超时,单位是秒
//事务的传播行为
int getPropagationBehavior();
//事务的隔离级别
int getIsolationLevel();
//事务超时时间
int getTimeout();
//是否只读
boolean isReadOnly();
String getName();
}
13.6.3 添加tx命名空间
xmlns:tx="http://www.springframework.org/schema/tx"
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx.xsd
13.6.4 添加事务相关配置
修改applicationContext.xml
实例:
<context:property-placeholder location="classpath:jdbc.properties"></context:property-placeholder>
<bean id="ds" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<property name="driverClass" value="${driver}"/>
<property name="jdbcUrl" value="${url}"/>
<property name="user" value="${uname}"/>
<property name="password" value="${password}"/>
</bean>
<bean id="studentDao" class="com.xzk.dao.impl.StudentDaoImpl">
<property name="dataSource" ref="ds" ></property>
</bean>
<!--声明式事务-->
<!--1.创建事务管理器对象-->
<bean id="mytx" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="ds"></property>
</bean>
<!--2.指定哪些方法需要进行事务管理-->
<tx:advice id="myad" transaction-manager="mytx">
<tx:attributes>
<!--name="insert*"指定需要添加事务的方法,propagation="REQUIRED"指定一种传播行为-->
<tx:method name="insert*" propagation="REQUIRED"/>
<tx:method name="update*" propagation="REQUIRED"/>
<tx:method name="delete*" propagation="REQUIRED"/>
</tx:attributes>
</tx:advice>
<!--3.使用面向切面的思想指定哪层需要事务管理,事务添加在service层最合理-->
<aop:config>
<!--指定哪一层添加切面-->
<aop:pointcut id="mypc" expression="execution(* com.xzk.dao.*.*(..))"></aop:pointcut>
<!--指定哪些方法添加切面,advice-ref 环绕增强添加事务-->
<aop:advisor advice-ref="myad" pointcut-ref="mypc"></aop:advisor>
</aop:config>
借鉴:
<!-- 平台事务管理器 -->
<bean id="transactionManager"
class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
<!-- 通知 -->
<tx:advice id="txAdvice" transaction-manager="transactionManager">
<tx:attributes>
<!-- 传播行为:propagation 不是必须的,默认值是REQUIRED -->
<!-- REQUIRED:如果有事务,则在事务中执行;如果没有事务,则开启一个新的事务 -->
<tx:method name="save*" propagation="REQUIRED" />
<!-- SUPPORTS:如果有事务,则在事务中执行;如果没有事务,则不会开启事务 -->
<tx:method name="find*" propagation="SUPPORTS" read-only="true" />
</tx:attributes>
</tx:advice>
<aop:config>
<aop:pointcut id="txPointCut" expression="execution(* com.service.*.*(..))" />
<aop:advisor advice-ref="txAdvice" pointcut-ref="txPointCut"/>
</aop:config>
- > isolation 设置隔离机制,不是必须的 默认值DEFAULT
- > timeout 不是必须的 默认值-1(永不超时) 表示事务超时的时间(以秒为单位)
- > read-only 不是必须的 默认值false不是只读的 表示事务是否只读?
- > rollback-for 不是必须的 表示将被触发进行回滚的 Exception(s);以逗号分开。
- 如:'com.ityhp.MyBusinessException,ServletException'
- > no-rollback-for 不是必须的 表示不被触发进行回滚的 Exception(s);以逗号分开。
- 如:'com.foo.MyBusinessException,ServletException'
- 需要包:
<dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjweaver</artifactId> <version>1.8.13</version> </dependency>
14. 使用注解方式添加事务
14.1 使用@Transactional注解-添加tx命名空间
配置xml
<!--注解事务-->
<!--1.创建事务管理器对象-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
<!--2。开启注解,① 对标注@Transactional注解的Bean进行加工处理,以织入事物管理切面 -->
<tx:annotation-driven transaction-manager="transactionManager" />
在默认情况, <tx:annotation-driven /> 中transaction-manager属性会自动使用名为"transactionManager" 的事务管理器.
所以,如果用户将事务管理器的id定义为 transactionManager , 则可以进一步将①处的配置简化为<tx:annotation-driven />.
14.2 @Transactional其他方面介绍
* 关于@Transactional的属性
基于@Transactional默认的属性.
- * 事务传播行为: PROPAGATION_REQUIRED.
- * 事务隔离级别: ISOLATION_DEFAULT.
- * 读写事务属性:读/写事务.
- * 超时时间:依赖于底层的事务系统默认值
- * 回滚设置:任何运行期异常引发回滚,任何检查型异常不会引发回滚.
默认值可能适应大部分情况,但是我们依然可以可以自己设定属性,具体属性表如下:
* 在何处标注@Transactional注解?
@Transactional注解可以直接用于接口定义和接口方法,类定义和类的public方法上.
但Spring建议在业务实现类上使用@Transactional注解,
当然也可以添加到业务接口上, 但是这样会留下一些容易被忽视的隐患,因为注解不能被继承,所以业务接口中标注的@Transactional注解不会被业务类实现继承.
14.3 使用不同的事务管理器
一般情况下,一个应用仅需要使用一个事务管理器.如果希望在不同的地方使用不同的事务管理,@Transactional注解同样支持!
实现代码:
@Transactional("事务管理器的名字") //此处添加指定事务管理器的名字
@Service("accountService")
public class AccountServiceImpl implements AccountService {}
对应事务查找事务管理器的名字应该在xml中进行定义!如下:
<!--声明一个事务管理器 -->
<bean id="transactionManager"
class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
<qualifier value="定义事务管理器的名字,可以被注解查找" />
</bean>