Spring
1、简介
1.1、什么是Spring
- Spring是一个轻量级的控制反转(IOC)和面向切面(AOP)的容器框架。
- 它是为了解决企业应用开发的复杂性而创建的
- Spring:春天------>给软件行业带来了春天!
- 2002,首次推出了Spring框架的雏形: interface 21框架!
- Spring框架即以interface 21框架为基础,经过重新设计;并不断丰富其内涵,于2004年3月24日,发布了1.0正式版。
- Rod Johnson , Spring Framework创始人,著名作者。很难想象Rod Johnson的学历,真的让好多人大吃一惊,他是悉尼大学的博士,然而他的专业不是计算机,而是音乐学。
- spring理念:使现有的技术更加容易使用,
- Spring是一个非入侵式的框架,Spring应用中的对象不依赖于Spring的特定类。
1.2、控制反转(IOC)
-
IOC是一种编程思想,由主动的编程变成被动的接收.
-
控制: 创建对象,对象的属性赋值,对象之间的关系管理。
-
反转: 把原来的开发人员管理,创建对象的权限转移给代码之外的容器实现。 ==由容器代替开发人员管理对象。创建对象、给属性赋值。==程序本身不创建对象,而变成被动的接收对象.
-
促进了松耦合
-
DI 是IOC的技术实现
-
DI(Dependency Injection) :依赖注入, 只需要在程序中提供要使用的对象名称就可以, 至于对象 赋值,查找都由容器内部实现
-
ApplicationContext容器,会在容器对象初始化时,将其中的所有对象一次性全部装配好。以后代码中若要使用到这些对象,只需从内存中直接获取即可。执行效率较高。但占用内存
1.2.1、IOC理论推导
-
改进前
//UserDao接口 public interface Userdao { void getUser(); }//UserDaoImpl实现类 public class UserDaoImpl implements Userdao { @Override public void getUser() { System.out.println("成功获取"); } }//UserService接口 public interface UserService { void getUser(); }//UserServiceImpl实现类 public class UserServiceImpl implements UserService { Userdao userdao = new UserDaoImpl(); @Override public void getUser() { userdao.getUser(); } }在我们之前的业务中,用户的需求可能会影响我们原来的代码,我们需要根据用户的需求去修改原代码!
如果程序代码量十分大,修改一次的成本代价十分昂贵!
用户没有选择的控制权利、代码耦合度强、代码不灵活!
-
改进后
使用set方法实现传值,已经发生了革命性的变化!
//UserServiceImpl实现类 public class UserServiceImpl implements UserService { private Userdao userdao ; public void setUserdao(Userdao userdao) { this.userdao = userdao; } @Override public void getUser() { userdao.getUser(); } }-
之前,程序是主动创建对象!控制权在程序猿手上!
-
使用了set注入后,程序不再具有主动性,而是变成了被动的接受对象!
这种思想,从本质上解决了问题,我们程序猿不用再去管理对象的创建了。
-
1.3、面向切面(AOP)
- 允许通过分离应用的业务逻辑与系统级服务(例如审计(auditing)和事务(transaction)管理)进行内聚性的开发
- Aspect: 切面,给你的目标类增加的功能,就是切面。 像上面用的日志,事务都是切面
- 面向切面编程是从动态角度考虑程序运行过程,可以使用jdk,cglib两种代理方式。
1.4、组成

1.5、扩展
在Spring的官网有这个介绍:现代化的Java开发!说白就是基于Spring的开发!

-
Spring Boot
-
一个快速开发的脚手架。
-
基于SpringBoot可以快速的开发单个微服务。
-
约定大于配置!
-
-
Spring Cloud
- SpringCloud是基于SpringBoot实现的
因为现在大多数公司都在使用SpringBoot进行快速开发,学习SpringBoot的前提,需要完全掌握Spring及
SpringMVC!承上启下的作用!
官方文档:https://docs.spring.io/spring-framework/docs/current/reference/html/
下载地址:https://docs.spring.io/spring-framework/docs/4.3.9.RELEASE/spring-framework-reference/
Github:https://github.com/spring-projects/spring-framework
2、Spring的第一个程序
思路:搭建环境——>导入Spring——>编写代码——>测试
2.1、搭建环境
- 新建项目
- 新建一个普通的maven项目
- 删除src目录(作为父)
- 导入maven依赖
<dependencies>
<!-- 会导入依赖的jar包 -->
<!-- https://mvnrepository.com/artifact/org.springframework/spring-webmvc -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>5.3.5</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.springframework/spring-jdbc -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>5.3.5</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.12</version>
<scope>provided</scope>
</dependency>
</dependencies>
资源扫描器插件
<!--资源扫描插件-->
<build>
<resources>
<resource>
<directory>src/main/java</directory>
<includes>
<include>**/*.properties</include>
<include>**/*.xml</include>
</includes>
<filtering>false</filtering>
</resource>
</resources>
</build>
2.2、编写代码
-
定义dao层、service层、Controller层
//UserDao接口 public interface Userdao { void getUser(); }//UserDaoImpl实现类 public class UserDaoImpl implements Userdao { @Override public void getUser() { System.out.println("成功获取"); } }//UserService接口 public interface UserService { void getUser(); }//UserServiceImpl实现类 public class UserServiceImpl implements UserService { private Userdao userdao ; public void setUserdao(Userdao userdao) { this.userdao = userdao; } @Override public void getUser() { userdao.getUser(); } }
2.3、配置Spring
-
编写Spring的配置文件
<?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"> <bean id="userDao" class="com.bjpowernode.dao.impl.UserDaoImpl"/> <bean id="userService" class="com.bjpowernode.service.impl.UserServiceImpl"> <property name="userdao" ref="userDao"/> </bean> </beans>
2.4、测试
-
测试
//通过容器获取对象 public class test { @Test public void getUser(){ ApplicationContext context = new ClassPathXmlApplicationContext("ApplicationContext.xml"); UserServiceImpl userService = context.getBean("userService", UserServiceImpl.class); userService.getUser(); } }
3、Spring配置
3.1、别名
<!--别名,如果添加别名,通过别名也可以获取到这个对象-->
<alias name="wife" alias="WIFE"/>
3.2、Bean配置
<!--
id : bean的唯一标识符,也就是相当于我们学的对象名
class : bean对象所对应的全限定名:包名+类型
name : 也是别名,而且name 可以同时取多个别名
-->
<bean id="wife" class="com.rui.entity.Wife" name="WIFE Wife">
<property name="name" value="小红"/>
<property name="age" value="19"/>
</bean>
3.3、import
这个import,一般用于团队开发使用,他可以将多个配置文件,导入合并为一个
假设,现在项目中有多个人开发,这三个人复制不同的类开发,不同的类需要注册在不同的bean中,我们可以利用import将所有人的spring配置文件合并为一个总的!
<!--
spring-dao.xml
spring-service.xml
spring-controller.xml
applicationContext.xml
-->
<import resource="classpath:spring-dao.xml"/>
<import resource="classpath:spring-service.xml"/>
<import resource="classpath:spring-controller.xml"/>
可以使用通配符
<import resource="classpath*:spring-*.xml"/>
使用的时候,直接使用总的配置就可以了
3.4、bean的作用范围

1.单例模式(Spring默认机制),只创建一个实例!
<bean id="wife" class="com.rui.entity.Wife" scope="singleton">
<property name="name" value="小红"/>
<property name="age" value="19"/>
</bean>
2.原型模式:每次从容器中get的时候,都会产生一个新对象!
<bean id="hubby" class="com.rui.entity.Hubby" scope="prototype">
<property name="name" value="小明"/>
<property name="age" value="21"/>
<property name="wife" ref="wife"/>
</bean>
3.其余的request、session、application、这些个只能在web开发中使用到!
4、IOC
4.1、什么是IOC
IOC是一种编程思想,由主动的编程变成被动的接收。DI (依赖注入)是IOC的技术实现
实体类
package com.rui.entity;
//丈夫
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Hubby {
private String name;
private int age;
private Wife wife;
}
//妻子
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Wife {
private String name;
private String age;
}
4.2、基于XML的DI
-
set注入(掌握)
通过setter方法传入被调用者的实例
-
简单类型
<bean id="wife" class="com.rui.entity.Wife"> <property name="name" value="小红"/> <property name="age" value="19"/> </bean> -
引用类型:当指定bean的某属性值为另一bean的实例时,通过ref指定它们间的引用关系。ref的值必须为某bean的id值
<bean id="hubby" class="com.rui.entity.Hubby" > <property name="name" value="小明"/> <property name="age" value="21"/> <property name="wife" ref="wife"/> </bean>
-
-
constructor注入
在构造调用者实例的同时,完成被调用者的实例化
-
按照下标位置
<bean id="wife" class="com.rui.entity.Wife"> <constructor-arg index="0" value="小红"/> <constructor-arg index="1" value="19"/> </bean> <bean id="hubby" class="com.rui.entity.Hubby"> <constructor-arg index="0" value="小明"/> <constructor-arg index="1" value="21"/> <constructor-arg index="2" ref="wife"/> </bean> -
按照参数名
<bean id="wife" class="com.rui.entity.Wife"> <constructor-arg name="name" value="小红"/> <constructor-arg name="age" value="19"/> </bean> <bean id="hubby" class="com.rui.entity.Hubby"> <constructor-arg name="name" value="小明"/> <constructor-arg name="age" value="21"/> <constructor-arg name="wife" ref="wife"/> </bean>
-
-
p c标签
用于简单的属性赋值
xmlns:p="http://www.springframework.org/schema/p"
xmlns:c="http://www.springframework.org/schema/c"
<bean id="wife" class="com.rui.entity.Wife"
p:name="小红"
p:age="19"/>
<bean id="hubby" class="com.rui.entity.Hubby"
p:name="小明"
p:age="21"
p:wife-ref="wife"/>
<bean id="wife" class="com.rui.entity.Wife" c:name="小红" c:age="19"/>
<bean id="hubby" class="com.rui.entity.Hubby" c:name="小明" c:age="21" c:wife-ref="wife"/>
<bean id="wife" class="com.rui.entity.Wife" c:_0="小红" c:_1="19"/>
<bean id="hubby" class="com.rui.entity.Hubby" c:_0="小明" c:_1="21" c:_2-ref="wife"/>
注意点:p命名和c命名空间不能直接使用,需要导入xml约束!
-
自动注入
对于引用类型属性的注入,也可不在配置文件中显示的注入。可以通过为标签设置autowire属性值,为引用类型属性进行隐式自动注入(默认是不自动注入引用类型属性)
-
根据名称自动注入
当配置文件中被调用者bean的id值与代码中调用者bean类的属性名相同时
<bean id="hubby" class="com.rui.entity.Hubby" autowire="byName"> <property name="name" value="小明"/> <property name="age" value="21"/> </bean> -
根据类型自动注入
配置文件中被调用者bean的class属性指定的类,要与代码中调用者bean类的某引用类型属性类型同源。。但这样的同源的被调用bean只能有一个。
<bean id="hubby" class="com.rui.entity.Hubby" autowire="byType"> <property name="name" value="小明"/> <property name="age" value="21"/> </bean>
-
4.3、基于注解的DI
-
在Spring配置文件中声明组件扫描器
<context:component-scan base-package="com.rui.entity"/>-
可以指定多个包(三种方式)
-
声明多个组件扫描器
<context:component-scan base-package="com.rui.entity"/> <context:component-scan base-package="com.rui.dao"/> -
使用分隔符(“,”,“;”)
<context:component-scan base-package="com.rui.entity,com.rui.dao"/> -
指定父包名【但不建议使用顶级的父包,扫描的路径比较多,导致容器启动时间变慢】
<context:component-scan base-package="com.rui"/>
-
-
@Component用于普通的java实现类
定义Bean,需要在类上使用注解@Component,该注解的value属性用于指定该bean的id值
- @Component不指定value属性,bean的id是类名的首字母小写
@Component("wife")
public class Wife {
private String name;
private String age;
}
@Component
public class Hubby {
private String name;
private int age;
private Wife wife;
}
- 简单类型属性注入:@Value
@Component("wife")
public class Wife {
@Value("小红")
private String name;
@Value("19")
private String age;
}
- @Autowired自动注入
@Autowired还有一个属性required,默认值为true,表示当匹配失败后,会终止程序运行。若将其值设置为false,则匹配失败,将被忽略,未匹配的属性值为null
- byType
@Component
public class Hubby {
@Value("小明")
private String name;
@Value("21")
private int age;
@Autowired
private Wife wife;
}
- byName:@Autowired与@Qualifier
@Component
public class Hubby {
@Value("小明")
private String name;
@Value("21")
private int age;
@Autowired
@Qualifier("wife")
private Wife wife;
}
-
JDK注解@Resource自动注入
-
若不带任何参数,采用默认按名称的方式注入,按名称不能注入bean,则会按照类型进行Bean的匹配注入
@Component public class Hubby { @Value("小明") private String name; @Value("21") private int age; @Resource private Wife wife; } -
指定其name属性,则name的值即为按照名称进行匹配的Bean的id
@Component public class Hubby { @Value("小明") private String name; @Value("21") private int age; @Resource(name="hubby") private Wife wife; } -
@Repository 用于对DAO实现类进行注解
-
@Service 用于对Service实现类进行注解
-
@Controller 用于对Controller实现类进行注解
-
@Repository,@Service,@Controller是对@Component注解的细化,标注不同层的对象。
4.4、基于javaConfig
纯java代码配置代替XML配置,属性赋值通过注解方式
//这个也会spring容器托管,注册到容器中,因为他本来就是一个@component
@Configuration
@ComponentScan("com.rui.entity")
@Import(springEntity.class
public class applicationContext {
@Bean
public Hubby hubby(){
return new Hubby();
}
//注册一个bean ,就相当于我们之前写的一个bean标签
//这个方法的名字,就相当于bean标签中的id属性
//这个方法的返回值,就相当于bean标签中的cLass属性
@Bean
public Wife wife(){
return new Wife();
}
@Bean("dataSource","data")
public DataSource getDataSource(){
DataSource dataSource = new DataSource();
dataSource.setUserId("zhangsan");
dataSource.setPassword("123456");
dataSource.setUrl("www");
return dataSource;
}
}
测试:
@Test
public void getHubby(){
ApplicationContext context = new AnnotationConfigApplicationContext(applicationContext.class);
Hubby hubby = context.getBean("hubby", Hubby.class);
Wife wife = context.getBean("wife", Wife.class);
System.out.println(hubby);
System.out.println(wife);
}
4.5、注解与XML的对比
- xml 与注解:
- xml更加万能,适用于任何场合!维护简单方便。
- 注解不是自己类使用不了,维护相对复杂!
- xml与注解最佳实践:
- xml 用来管理bean;
- 注解只负责完成属性的注入;
- 我们在使用的过程中,只需要注意一个问题:必须让注解生效,就需要开启注解的支持
5、代理模式
代理模式:
-
静态代理
-
动态代理

5.1、静态代理
角色分析:
- 抽象角色:一般会使用接口或者抽象类来解决
- 真实角色︰被代理的角色
- 代理角色︰代理真实角色,代理真实角色后,我们一般会做一些附属操作
- 客户:访问代理对象的人!
代码实现:
//出租
public interface Rent {
void rent();
}
//出租户
public class Host implements Rent {
@Override
public void rent() {
System.out.println("出租户:出租房子,价格由出租户自定!");
}
}
//中介
public class Proxy implements Rent {
private Host host;
public Proxy() {
}
public Proxy(Host host) {
this.host = host;
}
public void setHost(Host host) {
this.host = host;
}
@Override
public void rent() {
viewRoom();
host.rent();
charge();
}
public void viewRoom(){
System.out.println("带客户浏览房子");
}
public void charge(){
System.out.println("收取中介费");
}
}
测试:
//租户
@Test
public void user() {
Host host = new Host();
Proxy proxy = new Proxy(host);
proxy.rent();
}
代理模式的好处:
- 可以使真实角色的操作更加纯粹!不用去关注一些公共的业务·公共也就交给代理角色!实现了业务的分工!
- 公共业务发生扩展的时候,方便集中管理!
缺点:
- 静态代理类需要实现目标类(被代理类)的接口,并实现其方法,造成了代码的大量冗余。
- 静态代理只能对某个固定接口的实现类进行代理服务,其灵活性不强。
5.2、动态代理
- 动态代理和静态代理角色一样
- 动态代理的代理类是动态生成的,不是我们直接写好的!
- 动态代理分为两大类:基于接口的动态代理,基于类的动态代理
- 基于接口—JDK动态代理【我们在这里使用】
- 基于类: cglib
- java字节码实现: javasist
需要了解两个类:Proxy:代理,InvocationHandler:调用处理程序
代码实现:
//出租
public interface Rent {
void rent();
}
//出租户
public class Host implements Rent {
@Override
public void rent() {
System.out.println("出租户:出租房子,价格由出租户自定!");
}
}
//动态代理处理
public class ProxyInvocationHandler<T> implements InvocationHandler {
// 真实对象
private T target;
public ProxyInvocationHandler(T target) {
this.target = target;
}
public void setTarget(T target) {
this.target = target;
}
public T getProxy(){
return (T) Proxy.newProxyInstance(target.getClass().getClassLoader(),
target.getClass().getInterfaces(),
this);
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
viewRoom();
Object result = method.invoke(target, args);
charge();
return result;
}
public void viewRoom(){
System.out.println("带客户浏览房子");
}
public void charge(){
System.out.println("收取中介费");
}
}
测试:
//租户
@Test
public void getProxy(){
Host host = new Host();
ProxyInvocationHandler<Host> proxyInvocationHandler = new ProxyInvocationHandler(host);
Rent proxy = proxyInvocationHandler.getProxy();
proxy.rent();
}
动态代理的好处:
- 可以使真实角色的操作更加纯粹!不用去关注一些公共的业务
- 公共也就就交给代理角色!实现了业务的分工!
- 公共业务发生扩展的时候,方便集中管理!
- 动态代理实现了只需要将被代理对象作为参数传入代理类就可以获取代理类对象,从而实现类代理,具有较强的灵活性。
- 动态代理的服务内容不需要像静态代理一样写在每个代码块中,只需要写在invoke()方法中即可,降低了代码的冗余度。
6、AOP
6.1、什么是AOP
AOP(Aspect Oriented Programming),意为:面向切面编程,可通过运行期动态代理实现程序功能的统一维护的一种技术。AOP是Spring框架中的一个重要内容。利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。

6.2、AOP的关键词
-
切面(Aspect):切面泛指交叉业务逻辑。上例中的事务处理、日志处理就可以理解为切面。实际就是对主业务逻辑的一种增强。
-
连接点(JoinPoint):连接点指可以被切面织入的具体方法。
-
切入点(Pointcut):切入点指声明的一个或多个连接点的集合。通过切入点指定一组方法。
-
目标对象(Target):目标对象指将要被增强的对象。即包含主业务逻辑的类的对象。
-
通知(Advice):通知表示切面的执行时间,Advice也叫增强。
换个角度来说,是目标方法执行之前执行,还是之后执行等。切入点定义切入的位置,通知定义切入的时间
-
横切关注点:跨越应用程序多个模块的方法或功能。
即是,与我们业务逻辑无关的,但是我们需要关注的部分,就是横切关注点。如日志,安全,缓存,事务等等.……
6.3、使用Spring实现AOP
在Spring中使用AOP开发时,一般使用AspectJ的实现方式
AspectJ中常用的通知有五种类型:
(1)前置通知
(2)后置通知
(3)环绕通知
(4)异常通知
(5)最终通知
-
切入点表达式
execution**(访问权限? **方法返回值 方法声明(参数) 异常类型?),带有?的可以省
//其他地方使用时,execution格式:pointcut()
@Pointcut("execution(* com.rui.dao.impl.*.*(..))")
public void pointcut(){}
-
导入依赖
<dependency> <groupId>org.springframework</groupId> <artifactId>spring-aspects</artifactId> <version>5.2.5.RELEASE</version> </dependency>
三种方式
-
基于XML文件实现
- 实现接口(使用Spring API 接口)
//前置通知 @Component public class Log implements MethodBeforeAdvice { @Override public void before(Method method, Object[] agrs, Object target) throws Throwable { System.out.println(target.getClass().getName() +"中的"+ method.getName()+"方法开始执行"); } }- 配置切面
<!--配置AOP:需要导入约束--> <aop:config> <!--切入点 expression:切入点表达式--> <aop:pointcut id="pointcut" expression="execution(* com.rui.dao.impl.*.*(..))"/> <!--通知、增强--> <aop:advisor advice-ref="log" pointcut-ref="pointcut"/> </aop:config> -
自定义类实现
- 编写通知类
@Component public class aop { public void before(){ System.out.println("方法执行"); } public void after(){ System.out.println("方法执行结束"); } }- 配置切面
<aop:config> <aop:aspect ref="aop"> <aop:pointcut id="pointcut" expression="execution(* com.rui.dao.impl.*.*(..))"/> <aop:before method="before" pointcut-ref="pointcut"/> <aop:after method="after" pointcut-ref="pointcut"/> </aop:aspect> </aop:config> -
基于注解实现
- 定义切面类
@Component @Aspect public class aop { @Before(value = "execution(* com.rui.dao.impl.*.*(..))") public void log(JoinPoint joinPoint){ System.out.println(joinPoint.getSourceLocation()); } }- 开启AOP注解的驱动支持
<!-- proxy-target-class="true:设置动态代理的实现方式为cglib 默认为false,jdk动态代理 --> <aop:aspectj-autoproxy />
6.4、通知类型注解的使用
- 前置通知
@Before(value = "execution(* com.rui.dao.impl.*.*(..))")
public void before(){
System.out.println("方法执行前!!!");
}
特点:
1.在目标方法之前先执行的
2.不会改变目标方法的执行结果
3.不会影响目标方法的执行。
- 后置通知
@AfterReturning(value="pointcut()",returning="result")
public void afterReturning(Object result){
System.out.println("执行结果:" + result);
}
特点:
1.在目标方法之后执行的。
2.能够获取到目标方法的返回值,可以根据这个返回值做不同的处理功能
Object res = doOther();
该注解的returning属性就是用于指定接收方法返回值的变量名的
- 环绕通知
@Around(value="pointcut()")
public Object around(ProceedingJoinPoint pjp)throws Throwable{
Object result = null;
//增强功能
System.out.println( "环绕通知:在目标方法之前执行的,开启事务");
//执行目标方法的调用,等同于method. invoke(target, args )
result = pjp.proceed();
//增强功能
System.out.println("环绕通知:在目标方法之后执行的,处理事务");
//返回目标方法的执行结果
return result ;
}
特点:
1.它是功能最强的通知
2.在目标方法的前和后都能增强功能。
3.控制目标方法是否被调用执行
4.修改原来的目标方法的执行结果。影响最后的调用结果
环绕通知: 经常做事务, 在目标方法之前开启事务,执行目标方法, 在目标方法之后提交事务
被注解为环绕增强的方法要有返回值,Object类型。
并且方法可以包含一个ProceedingJoinPoint类型的参数。
接口ProceedingJoinPoint其有一个proceed()方法,用于执行目标方法。
若目标方法有返回值,则该方法的返回值就是目标方法的返回值。
最后,环绕增强方法将其返回值返回。该增强方法实际是拦截了目标方法的执行。
- 异常通知
@AfterThrowing(value="pointcut()" ,throwing="ex")
public void myAfterThrowing(Throwable ex){
System.out.println("异常通知:在目标方法抛出异常时执行的,异常原因: "+ex. getMessage());
}
特点:
1.在目标方法抛出异常时执行的
2.可以做异常的监控程序, 监控目标方法执行时是不是有异常。
如果有异常,可以发送邮件,短信进行通知
该注解的throwing属性用于指定所发生的异常类对象。
被注解为异常通知的方法可以包含一个参数Throwable,参数名称为throwing指定的名称,表示发生的异常对象。
- 最终通知
@After(value="pointcut()")
public void after(){
System.out.println("资源关闭!!!");
}
特点:
1.总是会执行
2.在目标方法之后执行的
-
JoinPoint:业务方法,要加入切面功能的业务方法
作用:可以在通知方法中获取方法执行时的信息, 例如方法名称,方法的实参-
这个JoinPoint参数的值是由框架赋予, 必须是第一个位置的参数。
-
通过该参数,可获取切入点表达式、方法签名、目标对象等。
-
所有的通知方法均可包含一个JoinPoint类型参数
-
6.4、通知的执行顺序


注:异常通知和环绕通知好像不能一起用(一起用,异常通知似乎不起作用)
7、整合Mybatis
7.1、集成步骤
-
MySQL创建数据库
-
CREATE DATABASE `mybatis`; USE mybatis; CREATE TABLE user( `id` int(10) PRIMARY KEY, `name` VARCHAR(255) NOT NULL, `password` VARCHAR(255) NOT NULL )ENGINE=INNODB DEFAULT CHARSET=utf8; INSERT INTO user() VALUES (1," lisi","li12345"), (2,"wangwu","wang12345"), (3,"zhangsan","zhang12345"); -
maven依赖
-
<dependencies> <!--mybatis--> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis</artifactId> <version>3.5.2</version> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>5.1.47</version> </dependency> <!--Spring--> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-webmvc</artifactId> <version>5.3.5</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-jdbc</artifactId> <version>5.3.5</version> </dependency> <!--测试单元--> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.12</version> </dependency> <!--Lombok--> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <version>1.18.12</version> <scope>provided</scope> </dependency> <!--数据源--> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid</artifactId> <version>1.2.5</version> </dependency> <!--Spring集成Mybatis--> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis-spring</artifactId> <version>2.0.6</version> </dependency> </dependencies> <!--资源扫描插件--> <build> <resources> <resource> <directory>src/main/java</directory> <includes> <include>**/*.properties</include> <include>**/*.xml</include> </includes> <filtering>false</filtering> </resource> </resources> </build> -
定义实体类
-
@Data public class User { private int id; private String name; private String password; } -
定义Dao接口
-
public interface UserDao { User getUser(@Param("id") int id); List<User> selectUsers(); } -
定义映射文件mapper
-
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> <mapper namespace="com.rui.dao.UserDao"> <select id="getUser" resultType="user"> select * from user where id=#{id} </select> <select id="selectUsers" resultType="user"> select * from user </select> </mapper> -
定义mybatis主配置文件
-
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-config.dtd"> <configuration> <!--开启日志--> <settings> <setting name="logImpl" value="STDOUT_LOGGING"/> </settings> <!--定义别名--> <typeAliases> <package name="com.rui.entity"/> </typeAliases> </configuration>
Spring配置文件
<?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
https://www.springframework.org/schema/context/spring-context.xsd">
<!--开启组件扫描器-->
<context:component-scan base-package="com.rui.entity"/>
<!--配置数据源-->
<context:property-placeholder location="classpath:jdbc.properties" />
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource"
init-method="init" destroy-method="close">
<property name="url" value="${jdbc.url}" />
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}" />
<property name="maxActive" value="${jdbc.maxActive}" />
</bean>
<!--定义sqlSessionFactory对象-->
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="dataSource" />
<property name="configLocation" value="classpath:mybatis.xml" />
</bean>
<!--定义Mapper扫描配置器-->
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="sqlSessionFactoryBeanName" value="sqlSessionFactory" />
<property name="basePackage" value="com.rui.dao"/>
</bean>
</beans>
测试
@Test
public void getUser(){
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
UserDao userDao = context.getBean("userDao", UserDao.class);
userDao.getUser(1);
userDao.selectUsers();
}
方式一:
定义Mapper扫描配置器,自动生成包下所有mapper文件对应的bean对象
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="sqlSessionFactoryBeanName" value="sqlSessionFactory" />
<property name="basePackage" value="com.rui.dao"/>
</bean>
方式二:
定义SqlSessionTemplate的Bean对象,只能构造注入进行赋值!
<bean id="sqlSession" class="org.mybatis.spring.SqlSessionTemplate">
<constructor-arg index="0" ref="sqlSessionFactory"/>
</bean>
<bean id="userDaoImpl" class="com.rui.dao.impl.UserDaoImpl">
<property name="sqlSession" ref="sqlSession"/>
</bean>
定义dao层接口的实现类,定义SqlSessionTemplate属性及set方法
public class UserDaoImpl implements UserDao {
private SqlSessionTemplate sqlSession;
public void setSqlSession(SqlSessionTemplate sqlSession) {
this.sqlSession = sqlSession;
}
@Override
public User getUser(int id) {
return sqlSession.getMapper(UserDao.class).getUser(id);
}
@Override
public List<User> selectUsers() {
return sqlSession.getMapper(UserDao.class).selectUsers();;
}
}
第二种的优化方式
public class UserDaoImpl extends SqlSessionDaoSupport implements UserDao {
@Override
public User getUser(int id) {
return getSqlSession().getMapper(UserDao.class).getUser(id);
}
@Override
public List<User> selectUsers() {
return getSqlSession().getMapper(UserDao.class).selectUsers();
}
}
<bean id="userDaoImpl" class="com.rui.dao.impl.UserDaoImpl">
<property name="sqlSessionFactory" ref="sqlSessionFactory"/>
</bean>
注:第一种方式,不需要在mybatis主配置文件中声明mapper文件的位置,相反第二种需要。
8、声明式事务
8.1、什么是事务
-
把一组业务当成一个业务来做;要么都成功,要么都失败!
-
事务在项目开发中,十分的重要,涉及到数据的一致性问题,不能马虎!
-
确保完整性和一致性;
8.2、事务ACID原则
- 原子性
- 一致性
- 隔离性
- 多个业务可能操作同一个资源,防止数据损坏
- 持久性
- 事务一旦提交,无论系统发生什么问题,结果都不会再被影响,被持久化的写到存储器中!
8.3、事务的隔离级别
| 级别 | 描述 |
|---|---|
| DEFAULT | 采用 DB 默认的事务隔离级别。MySql 的默认为 REPEATABLE_READ; Oracle默认为 READ_COMMITTED。 |
| READ_UNCOMMITTED | 读未提交。未解决任何并发问题。 |
| READ_COMMITTED | 读已提交。解决脏读,存在不可重复读与幻读。 |
| REPEATABLE_READ | 可重复读。解决脏读、不可重复读,存在幻读 |
| SERIALIZABLE | 串行化。不存在并发问题。 |
8.4、事务的超时时间
表示一个方法最长的执行时间,如果方法执行时超过了时间,事务就回滚。
单位是秒, 整数值, 默认是 -1.
8.5、事务的传播行为
控制业务方法是不是有事务的, 是什么样的事务的。
| 事务行为 | 说明 |
|---|---|
| PROPAGATION_REQUIRED | 支持当前事务,假设当前没有事务。就新建一个事务 |
| PROPAGATION_SUPPORTS | 支持当前事务,假设当前没有事务,就以非事务方式运行 |
| PROPAGATION_REQUIRES_NEW | 新建事务,假设当前存在事务。把当前事务挂起 |
| PROPAGATION_MANDATORY | 支持当前事务,假设当前没有事务,就抛出异常 |
| PROPAGATION_NOT_SUPPORTED | 以非事务方式运行操作。假设当前存在事务,就把当前事务挂起 |
| PROPAGATION_NEVER | 以非事务方式运行,假设当前存在事务,则抛出异常 |
| PROPAGATION_NESTED | 如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则执行与PROPAGATION_REQUIRED类似的 |
7个传播行为,表示你的业务方法调用时,事务在方法之间是如果使用的。
8.6、实现事务管理
方式一:Spring的事务注解管理事务
@Transactional属性
| 属性 | 类型 | 描述 |
|---|---|---|
| value | String | 可选的限定描述符,指定使用的事务管理器 |
| propagation | enum: Propagation | 可选的事务传播行为设置 |
| isolation | enum: Isolation | 可选的事务隔离级别设置 |
| readOnly | boolean | 读写或只读事务,默认读写 |
| timeout | int (in seconds granularity) | 事务超时时间设置 |
| rollbackFor | Class对象数组,必须继承自Throwable | 导致事务回滚的异常类数组 |
| rollbackForClassName | 类名数组,必须继承自Throwable | 导致事务回滚的异常类名字数组 |
| noRollbackFor | Class对象数组,必须继承自Throwable | 不会导致事务回滚的异常类数组 |
| noRollbackForClassName | 类名数组,必须继承自Throwable | 不会导致事务回滚的异常类名字数组 |
-
事务管理器接口(PlatformTransactionManager)
有两个常用的实现类:
DataSourceTransactionManager:使用JDBC或MyBatis进行数据库操作时使用。
HibernateTransactionManager:使用Hibernate进行持久化数据时使用。
-
Spring的回滚方式
Spring事务的默认回滚方式是:发生运行时异常和error时回滚,
发生受查(编译)异常时提交。不过,对于受查异常,
程序员也可以手工设置其回滚方式
-
使用@Transactional的步骤
适合中小项目使用的, 注解方案。
1.需要声明事务管理器对象
<!--1. 声明事务管理器-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<!--连接的数据库, 指定数据源-->
<property name="dataSource" ref="dataSource" />
</bean>
2. 开启事务注解驱动
<!--2. 开启事务注解驱动,告诉spring使用注解管理事务,创建代理对象
transaction-manager:事务管理器对象的id
-->
<tx:annotation-driven transaction-manager="transactionManager" />
- 在类上或方法上声明注解
@Override
@Transactional(
propagation = Propagation.REQUIRED,
isolation = Isolation.DEFAULT,
readOnly = false,
rollbackFor =
NullPointerException.class
)
public int userTest() {
User user = new User(12,"王维","wang12345");
userDao.insertUser(user);
userDao.delectUser(11);
return 0;
}
- 测试
@Test
public void getUser(){
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
UserService userService = context.getBean("userService",UserService.class);
System.out.println(userService.getClass().getName());
userService.userTest();
}
//jdk动态代理,代理对象为接口类型,不能直接转为目标类类型。
@Transactional若用在方法上,只能用于public方法上。
对于其他非public方法,如果加上了注解@Transactional,虽然Spring不会报错,但不会将指定事务织入到该方法中。
因为Spring会忽略掉所有非public方法上的@Transaction注解
若@Transaction注解在类上,则表示该类上所有的方法均将在执行时织入事务
方式二:AspectJ的AOP配置管理事务
适合大型项目,有很多的类,方法,需要大量的配置事务,使用aspectj框架功能,在spring配置文件中声明类,方法需要的事务。这种方式业务方法和事务配置完全分离
实现步骤: 都是在xml配置文件中实现
-
要使用的是aspectj框架,需要加入依赖
<dependency> <groupId>org.springframework</groupId> <artifactId>spring-aspects</artifactId> <version>5.2.5.RELEASE</version> </dependency> -
声明事务管理器对象
<!--1. 声明事务管理器--> <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <!--连接的数据库, 指定数据源--> <property name="dataSource" ref="dataSource" /> </bean> -
配置aop:指定哪些哪类要创建代理。
声明方法需要的事务类型(配置方法的事务属性【隔离级别,传播行为,超时】)
为事务通知设置相关属性。用于指定要将事务以什么方式织入给哪些方法。
<!--事务通知(切面) --> <tx:advice id= "userAdvice" transaction-manager="transactionManager"> <tx:attributes> <tx:method name= "insert*" propagation= "REQUIRED" isolation= "DEFAULT" rollback-for="java.lang.NullPointerException"/> <tx:method name= "delectUser" propagation= "REQUIRED" isolation= "DEFAULT" rollback-for="java.lang.Exception"/> <tx:method name= "*" propagation="SUPPORTS"/> </tx:attributes> </tx:advice> -
配置增强器
指定将配置好的事务通知,织入给谁
<!--配置aop:通知应用的切入点--> <aop:config> <aop:pointcut id="servicePt" expression="execution(* *..service..*.*(..))"/> <!--声明增强器:通知和切入点的组合--> <aop:advisor advice-ref="userAdvice" pointcut-ref="servicePt" /> </aop:config>
对于Spring事务不起作用
1、首先使用如下代码 确认你的bean 是代理对象吗?
必须是Spring定义(通过XML或注解定义都可以)的Bean才接受事务。
直接new出来的对象添加事务是不起作用的。
可以通过以下方式判断是否是代理对象:
AopUtils.isAopProxy(Object object)
AopUtils.isCglibProxy(Object object) //cglib
AopUtils.isJdkDynamicProxy(Object object) //jdk动态代理
2、入口函数必须是public,否则事务不起作用。这一点由Spring的AOP特性决定的。
3、切入点配置错误。
4、如果你使用了springmvc,可能是context:component-scan重复扫描引起的
5、如使用mysql且引擎是MyISAM造成的(因为不支持事务),改成InnoDB即可
原文链接:https://blog.youkuaiyun.com/szwangdf/article/details/41516239
9、Spring与web
在Web项目中使用Spring框架,首先要解决在web层(这里指Servlet)中获取到Spring容器的问题。只要在web层获取到了Spring容器,便可从容器中获取到Service对象
Spring的监听器
ContextLoaderListener
若将Spring容器的创建时机,放在ServletContext初始化时,就可以保证Spring容器的创建只会执行一次,也就保证了Spring容器在整个应用中的唯一性
实现步骤
- 导入依赖
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
<version>5.2.5.RELEASE</version>
</dependency>
- 注册监听器ContextLoaderListener
- 指定Spring配置文件的位置
<!--
注册监听器ContextLoaderListener
监听器被创建对象后,会读取/WEB-INF/spring.xml
为什么要读取文件:因为在监听器中要创建ApplicationContext对象,需要加载配置文件。
/WEB-INF/applicationContext.xml就是监听器默认读取的spring配置文件路径
可以修改默认的文件位置,使用context-param重新指定文件的位置
配置监听器:目的是创建容器对象,创建了容器对象, 就能把spring.xml配置文件中的所有对象都创建好。
用户发起请求就可以直接使用对象了。
-->
<context-param>
<!-- contextConfigLocation:表示配置文件的路径 -->
<param-name>contextConfigLocation</param-name>
<!--自定义配置文件的路径-->
<param-value>classpath:spring.xml</param-value>
</context-param>
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
-
获取Spring容器对象
-
直接从ServletContext中获取
//从监听器ContextLoaderListener的源码分析可知容器对象在ServletContext的中存放的key为 //WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE String key = WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE; WebApplicationContext applicationContext = getServletContext().getAttribute(key); -
通过WebApplicationContextUtils获取
WebApplicationContext applicationContext = null; ServletContext sc = getServletContext(); applicationContext = WebApplicationContextUtils.getRequiredWebApplicationContext(sc);
-
174万+

被折叠的 条评论
为什么被折叠?



