文章目录
1. Spring
Spring是轻量级的开源的JavaEE框架,用于解决企业应用开发的复杂性,其核心为IOC和AOP
- Spring是开源的、轻量级免费框架
- 控制反转(IOC)和面向切面编程(AOP)
- IOC 控制反转:把创建对象过程交给Spring管理
- AOP 面向切面:不修改源代码进行功能增强
- 支持事务处理,对框架整合的支持
Spring特点
- 方便解耦,简化开发
- AOP编程支持
- 方便程序的测试
- 方便和其他框架整合
- 方便进行事务操作
- 降低API开发难度
Spring有三个核心:控制翻转(loC)、依赖注入(DI)、面向切面编程(AOP)
2. IOC
2.1 概述
什么是IOC
- 控制反转,把对象创建和对象之间的调用过程,交给Spring管理
- 使用IOC目的:降低耦合度
2.2 IOC底层原理
- IOC思想基于IOC容器完成,IOC容器底层是对象工厂
- Spring提供IOC容器实现两种方式:(两个接口)
- BeanFactory:IOC容器基本实现,是Spring内部的使用接口
- 加载配置文件时不会创建对象,在使用对象时才创建
- ApplicationContext:BeanFactory的子接口,提供更强大的功能
- 加载配置文件时就创建对象
- BeanFactory:IOC容器基本实现,是Spring内部的使用接口
- ApplicationContext接口两个实现类
2.3 IOC操作 Bean 管理
2.3.1 Bean管理
Bean管理是两个操作
- Spring创建对象
- Spring注入属性
Bean管理操作
- 基于xml配置文件方式
- 基于注解方式
2.3.2 基于xml实现的Bean管理
1.基于xml方式创建对象
<bean id="user" class="com.cyan.User"></bean>
- 在spring配置文件中,使用bean标签,添加对应属性,即可创建对象
- 常见属性
属性 | 说明 |
---|---|
id | 唯一标识 |
class | 类全路径 |
-
创建对象时,默认使用无参构造方法完成对象创建
-
若使用有参构造有三种方式
方式1 下标赋值
<!--有参构造1 下标赋值-->
<bean id="userIndex" class="com.cyan.pojo.User">
<constructor-arg index="0" value="index"/>
</bean>
方式2 参数类型
<!--有参构造2 参数类型 不建议使用-->
<bean id="userType" class="com.cyan.pojo.User">
<constructor-arg type="java.lang.String" value="Type"/>
</bean>
方式3 参数名
<!--有参构造3 参数名-->
<bean id="userName" class="com.cyan.pojo.User">
<constructor-arg name="name" value="name"/>
</bean>
2.基于xml方式注入属性
DI
依赖注入,即注入属性
注入方式:1. 使用set方法注入 2. 使用有参构造参数注入
- 在spring配置文件中注入属性(set方法)
<bean id="address" class="com.cyan.pojo.Address">
<property name="address" value="南通"/>
</bean>
-
在spring配置文件中注入属性(有参构造方法)
- 1.创建类,定义属性,创建属性对应的构造方法
- 2.在spring配置文件中配置
public class Orders { private String oname; private String address; public Orders(String oname, String address) { this.oname = oname; this.address = address; } }
<bean id="oders" class="com.cyan.pojo.Oders"> <constructor-arg name="oname" value="电脑"></constructor-arg> <constructor-arg name="address" value="China"></constructor-arg> </bean>
3.c命名空间和p命名空间注入
<?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:p="http://www.springframework.org/schema/p"
xmlns:c="http://www.springframework.org/schema/c"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd">
<!--p命名空间注入可以直接注入属性值 property-->
<bean id="user" class="com.cyan.pojo.User" p:name="PName" p:age="18"/>
<!--c命名空间注入,通过构造器注入,constructor-->
<bean id="user2" class="com.cyan.pojo.User" c:age="18" c:name="CName"/>
</beans>
4.注入外部Bean
<!--service和dao对象创建-->
<bean id="userService" class="com.cyan.service.UserService">
<!--注入userDao对象
name:类里面属性名称
ref:创建userDao对象bean标签的id值
-->
<property name="userDao" ref="userDaoImpl">
</bean>
<bean id="userDaoImpl" class="com.cyan.dao.UserDaoImpl"></bean>
public class UserService {
private UserDao userDao;
public void setUserDao(UserDao userDao) {
this.userDao = userDao;
}
public void add() {
...
}
}
5.注入内部Bean和级联赋值
public class Dept {
private String dname;
public void setDname(String dname) {
this.dname = dname;
}
}
public class Emp {
private String ename;
private String gender;
private Dept dept;
public void setEname(String ename) {
this.ename = ename;
}
public void setGender(String gender) {
this.gender = gender;
}
public void setDept(Dept dept) {
this.dept = dept;
}
}
<!--内部bean-->
<bean id="emp" class="com.cyan.bean.Emp">
<!--设置普通属性-->
<property name="ename" value="cyan"></property>
<property name="gender" value="男"></property>
<!--设置对象类型属性-->
<property name="dept">
<bean id="dept" class="com.cyan.bean.Dept">
<property name="dname" value="销售部"></property>
</bean>
</property>
</bean>
<!--级联赋值-->
<bean id="emp" class="com.cyan.bean.Emp">
<!--设置普通属性-->
<property name="ename" value="cyan"></property>
<property name="gender" value="男"></property>
<!--级联赋值-->
<property name="dept" ref="dept"></property>
</bean>
<bean id="dept" class="com.cyan.bean.Dept">
<property name="dname" value="销售部"></property>
</bean>
6.xml注入集合属性
public class Student {
// 1.数组类型属性
private String[] courses;
// 2.list集合类型属性
private List<String> lists;
// 3.map集合类型属性
private Map<String, String> maps;
// 4.set集合类型
private Set<String> sets;
// 5.对象集合
private List<Course> courseList;
// Set 方法
...
}
<!--集合类型属性注入-->
<bean id="student" class="com.cyan.pojo.Student">
<!--数组类型属性注入-->
<property name="courses">
<array>
<value>Java</value>
<value>Spring</value>
</array>
</property>
<!--list类型属性注入-->
<property name="lists">
<list>
<value>Spring MVC</value>
<value>SpringBoot</value>
</list>
</property>
<!--map类型属性注入-->
<property name="maps">
<map>
<entry key="JAVA" value="java"></entry>
<entry key="SPRING" value="spring"></entry>
</map>
</property>
<!--set类型属性注入-->
<property name="sets">
<set>
<value>MySQL</value>
<value>Redis</value>
</set>
</property>
<!--注入list集合类型,值是对象-->
<property name="courseList">
<list>
<ref bean="course1"></ref>
<ref bean="course2"></ref>
</list>
</property>
</bean>
<!--创建多个Course对象-->
<bean id="course1" class="com.cyan.pojo.Course">
<property name="cname" value="Spring 5 Framework"></property>
</bean>
<bean id="course2" class="com.cyan.pojo.Course">
<property name="cname" value="Mybatis Framework"></property>
</bean>
7.把集合注入部分提取出来
在spring配置文件中引入命名空间util
xmlns:c="http://www.springframework.org/schema/util"
xsi:schemaLocation="http://www.springframework.org/schema/util
https://www.springframework.org/schema/util/spring-util.xsd"
使用util标签完成list集合注入
<!--1.提取list集合类型属性注入-->
<util:list id="bookList">
<value>var1</value>
<value>var2</value>
</util:list>
<!--2.提取list集合类型属性注入使用-->
<bean id="book" class="com.cyan.pojo.Book">
<property name="list" ref="bookList"></property>
</bean>
8.xml注入其他属性
1.普通值注入
<!--普通值注入-->
<property name="name" value="Cyan"/>
2.注入ref
<!--Bean注入 ref-->
<property name="address" ref="address"/>
3.Array注入
<!--数组注入-->
<property name="books">
<array>
<value>JAVA</value>
<value>C++</value>
<value>Python</value>
</array>
</property>
4.List注入
<!--List注入-->
<property name="hobbies">
<list>
<value>SING</value>
<value>CODE</value>
<value>SLEEP</value>
</list>
</property>
5.Map注入
<!--Map注入-->
<property name="card">
<map>
<entry key="Card" value="14725869"/>
</map>
</property>
6.Set注入
<!--Set注入-->
<property name="games">
<set>
<value>LOL</value>
</set>
</property>
7.NULL注入
<!--NULL注入-->
<property name="wife">
<null/>
</property>
8.Properties注入
<!--Properties注入-->
<property name="info">
<props>
<prop key="Sno">123456</prop>
</props>
</property>
9.属性值包含特殊符号
<!--属性值包含特殊符号-->
<property name="address">
<value>![CDATA[《南京》]]</value>
</property>
2.3.3 基于注解的Bean管理
1.注解
-
注解:代码特殊标记,格式,@注解名称(属性名称=属性值,…)
-
使用注解,注解可以作用在类上面,方法上面,属性上面
-
使用注解目的:简化xml配置
2.基于注解实现对象创建
- 引入spring-aop依赖
- 开启组件扫描
<!--引入context命名空间-->
<!--开启组件扫描-->
<!--如果扫描多个包,使用逗号隔开;扫描包上层目录-->
<context:component-scan base-package="com.cyan"></context:component-scan>
- 案例
<!--example 1
use-default-filters="false" 表示现在不使用默认filter,自己配置filter
context:include-filter,设置扫描的内容
-->
<context:component-scan base-package="com.cyan" use-default-fliters="false">
<context:include-filter type="annotation"
expression="org.springframework.stereotype.Controller"/>
</context:component-scan>
<!--example 2
下面配置扫描包所有内容
context:exclude-filter:设置不扫描的内容
-->
<context:component-scan base-package="com.cyan">
<context:exclude-filter type="annotation"
expression="org.springframework.stereotype.Controller"/>
</context:component-scan>
3.基于注解方式实现属性注入
@Autowired
:根据属性类型进行自动装配
@Service
public class UserService {
@Autowired
private UserDao userDao;
// method
}
@Repository
public class UserDaoImpl implements UserDao{
// method
}
@Qualifier
:根据属性名称进行注入[@Autowired和@Qualifier一起使用]
@Service
public class UserService {
@Autowired
@Qualifier(value="userDaoImpl1")
private UserDao userDao;
// method
}
@Repository(value="userDaoImpl")
public class UserDaoImpl implements UserDao{
// method
}
@Resource
:可以根据类型注入,可以根据名称注入
- 根据类型注入
@Service
public class UserService {
@Resource
private UserDao userDao;
// method
}
@Repository(value="userDaoImpl")
public class UserDaoImpl implements UserDao{
// method
}
- 根据名称注入
@Service
public class UserService {
@Resource(value="userDaoImpl")
private UserDao userDao;
// method
}
@Repository(value="userDaoImpl")
public class UserDaoImpl implements UserDao{
// method
}
@Value
:注入普通类型属性
@Value(value="var")
private String name;
4.完全注解开发
- 创建配置类,代替xml配置文件
@Configuration
@ComponentScan(basePackages={"com.cyan"})
public class SpringConfig {
}
- 编写测试类
public void test() {
// 加载配置类
ApplicationContext context = new AnnotationConfigApplicationContext(SpringConfig.class);
UserService service = context.getBean("userService",UserService.class);
}
2.3.4 Bean 类型
-
Spring有两种类型bean,一种普通bean,另一种FactoryBean
-
普通bean
:在配置文件中定义bean类型就是返回类型 -
FactoryBean
:在配置文件定义bean类型可以和返回类型不一样- 创建类,让该类作为工厂bean,实现接口FactoryBean
- 实现接口的方法,在实现的方法中定义返回的bean类型
// 创建类
public class MyBean implements FactoryBean {
// 定义返回bean
public Object getObject() throws Exception {
Course course = new Course();
course.setCname("Java");
return course;
}
public Class<?> getObjectType() {
}
public boolean isSingleton() {
}
}
public void test() {
ApplicationContext context = new ClassPathXmlApplicationContext("application-context.xml");
Course course = context.getBean("myBean", Course.class);
System.out.println(course);
}
<bean id="myBean" class="com.cyan.factorybean.MyBean">
</bean>
2.3.5 bean作用域
-
在Spring里面,默认情况下,bean是单实例
-
如何设置创建的Bean属于单实例还是多实例
- 在spring配置文件bean标签里面有属性scope [scope用于设置单实例还是多实例]
singleton
,表示单实例对象,默认值prototype
,表示多实例对象
singleton 和 prototype区别
- scope域中设置singleton表示单实例,设置prototype表示多实例
- scope域中设置值是singleton,加载spring配置文件时就会创建单实例对象
- scope域中设置值是prototype,不是在加载spring配置文件时创建对象,而是在调用getBean方法时创建多实例对象
2.3.6 bean的生命周期
生命周期
生命周期表示从对象创建到对象销毁的过程。
bean 生命周期
[五步版本]
- 通过构造器创建bean实例(无参数构造)
- 为bean的属性设置值和对其他bean引用(调用set方法)
- 调用bean的初始化方法(需要进行配置)
- bean可以使用了(对象获取到了)
- 当容器关闭时,调用bean的销毁方法(需要进行配置 )
[七步版本]
- 通过构造器创建bean实例(无参数构造)
- 为bean的属性设置值和对其他bean引用(调用set方法)
- 把bean实例传递bean后置处理器的方法[postProcessBeforeInitialization]
- 调用bean的初始化方法(需要进行配置)
- 把bean实例传递bean后置处理器的方法[postProcessAfterInitialization]
- bean可以使用了(对象获取到了)
- 当容器关闭时,调用bean的销毁方法(需要进行配置 )
2.3.7 自动装配
自动装配
根据指定装配规则(属性名称或属性类型),Spring自动将匹配的属性值注入
- 自动装配是Spring满足bean依赖的一种方式
- Spring会在上下文自动寻找,并自动给bean装配属性
Spring三种装配方式
- xml配置
- java配置
- 隐式自动装配bean
<bean id="cat" class="com.cyan.pojo.Cat"/>
<bean id="dog" class="com.cyan.pojo.Dog"/>
<bean id="person" class="com.cyan.pojo.Person" autowire="byName">
<property name="name" value="Name"/>
</bean>
- byName[属性名称]
- byType[属性类型]
注解实现自动装配
1.导入命名空间context
2.开启注解支持
如果@Autowired自动装配,无法通过一个注解完成,可以使用@Qualifier(value=“”)配合使用
@Resource和@Autowired区别
- 都用来自动装配,放在属性字段上
- @Autowired通过byType实现,要求对象存在
- @Resource通过byName实现,若找不到名字,通过byType实现
2.3.8 spring引入外部属性文件
外部属性文件
1.直接配置数据库信息
- 导入Druid包
- 配置连接池
<bean id="dataSource" class="com.alibaba.druid.pool.DriudDataSource">
<property name="driverClassName" value="com.mysql.jdbc.Driver"></property>
<property name="url" value="jdbc://localhost:3306/user_db"></property>
<property name="username" value="****"></property>
<property name="password" value="****"></property>
</bean>
2.引入外部属性文件配置数据库连接池
- 编写properties文件
driverClass=com.mysql.jdbc.Driver
url=jdbc://localhost:3306/user_db
username=xxxx
password=xxxx
- 将properties文件引入spring配置文件,同时引用
context
命名空间
<!--引入外部属性文件-->
<context:property-placeholder location="classpath:jdbc.properties"/>
<!--配置连接池-->
<bean id="dataSource" class="com.alibaba.druid.pool.DriudDataSource">
<property name="driverClassName" value="${driverClass}"></property>
<property name="url" value="${url}"></property>
<property name="username" value="${username}"></property>
<property name="password" value="${password}"></property>
</bean>
3. AOP
3.1 AOP基本概念
AOP是面向切面编程,利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发效率。通俗来讲:不通过修改源代码方式,在主干功能里面添加新功能。
3.2 AOP底层原理
AOP底层使用动态代理
动态代理使用情况
- 有接口情况,默认使用JDK动态代理
【创建接口实现类代理对象,增强类的方法】
- 没有接口情况,使用CDLIB动态代理
【创建子类的代理对象,增强类的方法】
AOP (JDK动态代理)
参数1,类加载器
参数2,增加方法所在的类,这类实现的接口,支持多个接口
参数3,实现该接口InvocationHandler,创建代理对象,写增强的方法
// java.lang.reflect.Proxy
static Object newProxyInstance(ClassLoader loader, 类<?>[] interfaces, InvocationHandler h)
返回指定接口的代理类的实例,该接口将方法调用分派给指定的调用处理程序。
案例实现
// 创建接口
public interface UserDao {
public int add(int a,int b);
public String update(String id);
}
// 实现接口
public class UserDaoImpl implements UserDao {
public int add(int a, int b) {
return a+b;
}
public String update(String id) {
return id;
}
}
// 创建代理类
public class JDKProxy {
public void test() {
Class[] interfaces = (UserDao.class);
UserDaoImpl userDao = new UserDaoImpl();
UserDao dao = (UserDao)Proxy.newProxyInstance(JDKProxy.class.getClassLoader, interfaces, new UserDaoProxy(userDao));
int result = dao.add(1,2);
System.out.println("result:"+result);
}
// 创建代理对象
class UserDaoProxy implements InvocationHandler {
// 传递要代理的对象
private Object obj;
public UserDaoProxy(Object obj) {
this.obj = obj;
}
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 方法前
System.out.println("在方法前执行"+method.getName()+":传递的参数"+Arrays.toString(args));
// 执行被增强的方法
Object res = method.invoke(obj,args);
// 方法后
System.out.println("在方法后执行"+obj);
return res;
}
}
}
3.3 AOP操作术语
连接点:类里面被增强的方法称为连接点
切入点:实际被真正增强的方法称为切入点
通知(增强):实际增强的逻辑部分称为通知
通知类型:前置通知 后置通知 环绕通知 异常通知 最终通知
切面:是动作,把通知应用到切入点过程
3.4 AOP 操作
Spring框架一般是基于AspectJ实现AOP操作,AspectJ不是Spring组成部分,独立AOP框架
-
在项目工程引入AOP依赖
-
切入点表达式
- 切入点表达式作用:知道对哪个类里面的方法进行增强
- 语法结构:
execution([权限修饰符][返回类型][类全路径][方法名称][参数列表]) 1.对com.cyan.dao.BookDao类的add进行增强 execution(* com.cyan.dao.BookDao.add(..)) 2.对com.cyan.dao.BookDao类的所有方法增强 execution(* com.cyan.dao.BookDao.*(..)) 2.对com.cyan.dao包里所有类的所有方法增强 execution(* com.cyan.dao.*.*(..))
3.4.1 基于xml实现AOP操作
1.创建代理类和被代理类
public class Book {
public void buy() {
}
}
public class BookProxy {
public void before() {
System.out.println("Before");
}
}
2.在spring配置文件中创建两个类对象
<!--创建对象-->
<bean id="book" class="com.cyan.Book"></bean>
<bean id="bookProxy" class="com.cyan.BookProxy"></bean>
3.在spring配置文件中配置切入点
<!--配置AOP增强-->
<aop:config>
<!--切入点-->
<aop:pointcut id="p" expression="execution(* com.cyan.Book.buy(..))"/>
<!--配置切面-->
<aop:before method="before" pointcut-ref="p"/>
</aop:config>
3.4.2 基于注解实现AOP操作
1.创建被代理类并定义方法
public class User {
public void add() {
// method
}
}
2.编写代理类[不同方法代表不同通知类型]
public class UserProxy {
// 前置通知
public void before() {
//method
}
}
3.配置通知[注意:要引入aop
命名空间]
- 开启注解扫描
<!--开启注解扫描-->
<context:component-scan base-package="com.cyan"></context:component-scan>
- 创建User和UserProxy对象
@Component
public class User {
@Component
public class UserProxy {
- 在代理类上面添加注解@Aspect
@Component
@Aspect
public class UserProxy {
- 在spring配置文件中开启生成代理对象
<!--开启AspectJ生成代理对象-->
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>
4.配置不同类型通知
- 在代理类里面,在作为通知方法上面添加通知类型注解,使用切入点表达式
@Component
@Aspect
public class UserProxy {
// 前置通知
@Before(value="execution(* com.cyan.User.add(..))")
public void before() {
System.out.print("Before method")
}
// 后置通知
@After(value="execution(* com.cyan.User.add(..))")
public void after() {
System.out.print("After method")
}
// 异常通知
@AfterThrowing(value="execution(* com.cyan.User.add(..))")
public void afterThrowing() {
System.out.print("AfterThrowing method")
}
// 环绕通知
@Around(value="execution(* com.cyan.User.add(..))")
public void around(ProceedingJoinPoint pj) throws Throwable{
System.out.print("Before around method");
pj.proceed();
System.out.print("After around method");
}
@AfterReturning(value="execution(* com.cyan.User.add(..))")
public void afterReturning() {
System.out.print("AfterReturning method")
}
}
5.代码测试
public void test() {
ApplicaiotnContext context = new ClassPathXmlApplicationContext("application-context.xml");
User user = context.getBean("user",User.class);
user.add();
}
6.[AOP 细节问题]
相同的切入点抽取
@Component
@Aspect
public class UserProxy {
// 相同切入点抽取
@Pointcut(value="execution(* com.cyan.User.add(..))")
public void pointcut() {
}
// 前置通知
@Before(value="pointcut()")
public void before() {
System.out.print("Before method")
}
}
有多个增强类对同一个方法增强,优先级设置
在增强类上添加注解@Oder(数字类型值),数字类型值越小优先级越高
@Component
@Aspect
@Oder(1)
public class UserProxy {
@Component
@Aspect
@Oder(2)
public class PersonProxy {
7.完全注解开发
@Configuration
@ComponentScan(basePackages={"com.cyan"})
@EnableAspectJAutoProxy(proxyTargetClass=true)
public class AopConfig {
}
8.AOP操作实现方式
<!-- https://mvnrepository.com/artifact/org.aspectj/aspectjweaver -->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.4</version>
<scope>runtime</scope>
</dependency>
1.使用Spring的API接口
public class BeforeLog implements MethodBeforeAdvice {
@Override
public void before(Method method, Object[] args, Object target) {
System.out.println(target.getClass().getName() + "'s " + method.getName() + "Invoked");
}
}
public class AfterLog implements AfterReturningAdvice {
@Override
public void afterReturning(Object returnValue, Method method, Object[] args, Object target) {
System.out.println("Invoked " + method.getName() + "Return " + returnValue);
}
}
<?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:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-3.1.xsd">
<!--注册bean-->
<bean id="userService" class="com.cyan.service.UserServiceImpl"/>
<bean id="beforeLog" class="com.cyan.log.BeforeLog"/>
<bean id="afterLog" class="com.cyan.log.AfterLog"/>
<!--方式1 使用API接口-->
<!--配置AOP-->
<aop:config>
<!--切入点-->
<aop:pointcut id="pointcut" expression="execution(* com.cyan.service.UserServiceImpl.*(..))"/>
<!--执行环绕增加-->
<aop:advisor advice-ref="beforeLog" pointcut-ref="pointcut"/>
<aop:advisor advice-ref="afterLog" pointcut-ref="pointcut"/>
</aop:config>
</beans>
2. 使用自定义AOP[切面定义]
<!--方式2 自定义类-->
<bean id="appPointcut" class="com.cyan.aop.AppPointcut"/>
<aop:config>
<!--自定义切面-->
<aop:aspect ref="appPointcut">
<!--切入点-->
<aop:pointcut id="point" expression="execution(* com.cyan.service.UserServiceImpl.*(..))"/>
<!--通知-->
<aop:before method="before" pointcut-ref="point"/>
<aop:after method="after" pointcut-ref="point"/>
</aop:aspect>
</aop:config>
3.使用注解实现AOP
@Aspect
public class AnnotationPointcut {
@Before("execution(* com.cyan.service.UserServiceImpl.*(..))")
public void before() {
System.out.println("方法执行前");
}
@After("execution(* com.cyan.service.UserServiceImpl.*(..))")
public void after() {
System.out.println("方法执行后");
}
//循环增强中,给定一个参数,代表获取处理切入的点
@Around("execution(* com.cyan.service.UserServiceImpl.*(..))")
public void around(ProceedingJoinPoint pj) throws Throwable {
System.out.println("环绕前");
//执行方法
Object proceed = pj.proceed();
System.out.println("环绕后");
}
}
4. JdbcTemplate
4.1 概述
Spring框架对Jdbc进行封装,使用它方便对数据库操作
4.2 使用JdbcTemplate
1.导入依赖
2.在spring配置文件配置数据库连接池
<!--数据库连接池-->
<bean class="com.alibaba.druid.pool.DruidDataSource" destroy-method="close">
<property name="url" value=""/>
<property name="username" value=""/>
<property name="password" value=""/>
<property name="driverClassName" value=""/>
</bean>
3.配置JdbcTemplate对象,注入DataSource
<!--JdbcTemplate对象-->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<!--注入DataSource-->
<property name="dataSource" ref="dataSource"></property>
</bean>
4.创建service和dao,在dao注入JdbcTemplate对象
@Service
public class BookService {
@Autowired
private BookDao bookDao;
}
@Repository
public class BookDao {
@AutoWired
private JdbcTemplate jdbcTemplate;
}
<!--开启组件扫描-->
<context:component-scan base-package="com.cyan"></context:component-scan>
5. 事务
5.1 概述
5.1.1 事务的概念
事务是数据库操作的最小工作单元,是作为单个逻辑工作单元执行的一系列操作;这些操作作为一个整体一起向系统提交,要么都执行、要么都不执行;
5.1.2 ACID特性
原子性(Atomicity)
事务中所有操作是不可分割的原子单位。事务中所有操作要么全部执行成功,要么全部执行失败。
一致性(Consistency)
事务执行后,数据库状态与其它业务规则保持一致。如转正业务,无论事务执行成功与否,参与转账的两个账号余额之和应该是不变的。
隔离性(Isolation)
隔离性是指在并发操作中,不同事务之间应该隔离开来,使每个并发中的事务不会相互干扰。
持久性(Durability)
一旦事务提交成功,事务中所有的数据操作都必须被持久化到数据库中,即使提交事务后,数据库马上崩溃,在数据库重启时,也必须你能保证通过某种机制恢复数据。
5.1.3 传播行为
事务的第一个方面是传播行为(propagation behavior)。当事务方法被另一个事务方法调用时,必须指定事务应该如何传播
5.1.3 隔离级别
隔离级别定义了一个事务可能受其他并发事务的程度。
脏读(Dirty reads)
脏读发生在一个事务读取了另一个事务改写但尚未提交的数据时。如果改写在稍后被回滚了,那么第一个事务获取的数据就是无效的。
不可重复读(Nonrepeatable read)
不可重复读发生在一个事务执行相同的查询两次或两次以上,但是每次都得到不同的数据时。这通常是因为另一个并发事务在两次查询期间进行了更新。
幻读(Phantom read)
幻读与不可重复读类似。它发生在一个事务(T1)读取了几行数据,接着另一个并发事务(T2)插入了一些数据时。在随后的查询中,第一个事务(T1)就会发现多了一些原本不存在的记录。
不可重复读与幻读的区别
- 不可重复读的重点是修改:同样的条件, 你读取过的数据, 再次读取出来发现值不一样了
- 幻读的重点在于新增或者删除:同样的条件, 第1次和第2次读出来的记录数不一样
- 从总的结果来看, 似乎不可重复读和幻读都表现为两次读取的结果不一致。但如果你从控制的角度来看, 两者的区别就比较大。 对于前者, 只需要锁住满足条件的记录。 对于后者, 要锁住满足条件及其相近的记录。
隔离级别 | 含义 |
---|---|
ISOLATION_DEFAULT | 使用后端数据库默认的隔离级别 |
ISOLATION_READ_UNCOMMITTED | 最低的隔离级别,允许读取尚未提交的数据变更,可能会导致脏读、幻读或不可重复读 |
ISOLATION_READ_COMMITTED | 允许读取并发事务已经提交的数据,可以阻止脏读,但是幻读或不可重复读仍有可能发生 |
ISOLATION_REPEATABLE_READ | 对同一字段的多次读取结果都是一致的,除非数据是被本身事务自己所修改,可以阻止脏读和不可重复读,但幻读仍有可能发生 |
ISOLATION_SERIALIZABLE | 最高的隔离级别,完全服从ACID的隔离级别,确保阻止脏读、不可重复读以及幻读,也是最慢的事务隔离级别,因为它通常是通过完全锁定事务相关的数据库表来实现的 |
5.2 引入事务应用场景
1.创建数据库表,添加记录
2.创建service,搭建dao,完成对象创建和注入
@Service
public class UserService{
@Autowired
private UserDao userDao;
}
public interface UserDao {
}
@Repository
public class UserDaoImpl implements UserDao {
@Autowired
private JdbcTemplate jdbcTemplate;
}
3.在dao创建两个方法,存钱和取钱的方法,在service创建转账的方法
public interface UserDao {
void add();
void reduce();
}
public class UserDaoImpl implements UserDao {
@Autowired
private JdbcTemplate jdbcTemplate;
public void reduce() {
String sql = "UPDATE t_account SET money=money-? WHERE username=?";
jdbcTemplate.update(sql,100,"Cyan");
}
public void add() {
String sql = "UPDATE t_account SET money=money+? WHERE username=?";
jdbcTemplate.update(sql,100,"Grees");
}
}
@Service
public class UserService{
@Autowired
private UserDao userDao;
public void account() {
userDao.reduce();
// 模拟异常
int i = 100 / 0;
userDao.add();
}
}
这里出现异常,不使用事务,会导致转账方成功,被转帐方收不到钱,没有做到一气呵成
4.使用事务解决
@Service
public class UserService{
@Autowired
private UserDao userDao;
public void account() {
try {
// 第一步, 开启事务
// 第二步,业务操作
userDao.reduce();
// 模拟异常
int i = 100 / 0;
userDao.add();
// 第三步,没有异常,提交事务
} catch(Exception w) {
// 第四步 出现异常,事务回滚
}
}
}
5.3 Spring 事务管理
一般来说,事务添加到三层架构的Service层
在Spring进行事务操作有两种事务管理方式
- 编程式事务管理
- 声明式事务管理
- 基于注解方式
- 基于xml配置文件方式
5.3.1 声明式事务管理
在Spring进行声明式事务管理,底层使用的是AOP
5.3.1.1 注解声明式事务管理
1.配置文件中配置事务管理器
<!--创建事务管理器-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<!--注入数据源-->
<property name="dataSource" ref="dataSource"></property>
</bean>
2.配置文件开启事务注解
- 在spring配置文件中,引入
tx
命名空间 - 开启事务注解
<!--开启事务注解-->
<tx:annotation-driven transaction-manager="transactionManager"></tx:annotation-driven>
3.service类上添加事务注解
@Service
@Transactional
public class UserService{
5.3.1.2 XML声明式事务管理
1.配置事务管理器
<!--创建事务管理器-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<!--注入数据源-->
<property name="dataSource" ref="dataSource"></property>
</bean>
2.配置通知
<!--配置通知-->
<tx:advice id="txadvice">
<!--配置事务参数-->
<tx:attributes>
<!--指定哪种规则的方法上面添加事务-->
<tx:method name="account" propagation="REQUIRED"/>
</tx:attributes>
</tx:advice>
3.配置切入点和切面
<!--配置切入点和切面-->
<aop:config>
<!--配置切入点-->
<aop:pointcut id="pt" expression="execution(* com.cyan.service.UserService.*(..))"/>
<!--配置切面-->
<aop:advisor advice-ref="txadvice" pointcut-ref="pt"/>
</aop:config>
5.3.1.3 完全注解开发声明式事务管理
创建配置类代替配置文件
@Configuration
@ComponentScan(basePackages={"com.cyan"})
@EnableTransactionManagement
public class TxConfig() {
// 创建数据库连接池
@Bean
public DruidDataSource getDruidDataSource() {
DruidDataSource dataSource = new DruidDataSource();
dataSource.setDriverClassName("");
dataSource.setUrl("");
dataSource.setUsername("");
dataSource.setPassword("");
return dataSource;
}
// 创建JdbcTemplate对象
@Bean
public JdbcTemplate getJdbcTemplate(DataSource dataSource) {
JdbcTemplate jdbcTemplate = new JdbcTemplat();
// 注入dataSource
jdbcTemplate.setDataSource(dataSource);
return jdbcTemplate;
}
}
5.3.2 声明式事务的参数配置
@Service
@Transaction(popagation=Propagation.REQUIRED,isolation=Isolation.REPEATABLE_READ)
public void UserService{
propagation
:事务传播行为
isolation
:事务隔离级别
timeout
:超时时间
事务需要在一定时间内提交,如果不提交进行回滚,默认值是-1,设置时间以秒单位进行计算
readOnly
:是否只读
-
读:查询操作,写:增删改
-
readOnly默认值false,表示可以增删改查
-
设置readOnly值为true,只能查询
rollbackFor
:回滚,设置出现哪些异常进行事务回滚
noRollbackFor
:设置出现哪些异常不进行事务回滚
6. Spring5 框架新功能
log4j2
1.整个Spring5框架的代码基于Java8
2.Spring5框架自带了通用的日志封装
3.Spring5框架整合Log4j2
- 引入依赖
- 创建log4j2.xml
<?xml version="1.0" encoding="UTF-8"?>
<!--日志级别以及优先级排序:OFF>FATAL>ERROR>WARN>INFO>DEBUG>ALL-->
<Configuration status="WARN">
<!--先定义Appender-->
<Appenders>
<!--输出日志信息到控制台-->
<Console name="Console" target="SYSTEM_OUT">
<!--控制日志输出格式-->
<PatternLayout pattern="%d{HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n"/>
</Console>
</Appenders>
<!--然后定义logger,只有定义了logger并引入appender,appender才生效-->
<!--root:用于指定项目的根日志,如果没有单独指定Logger,则会使用root作为默认的日志输出-->
<Loggers>
<Root level="error">
<AppenderRef ref="Console"/>
</Root>
</Loggers>
</Configuration>
Nullable注解
该注解可以使用在方法上面,属性上面,参数上面,表示可以为空>
函数式风格注册对象
public void test() {
// 创建GenricApplicationContext对象
GenricApplicationContext context = new GenericAppliactionContext();
// 调用context方法注册对象
context.refresh();
context.registerBean(User.class,()->new User);
// 获取在spring注册的对象
User user = (User) context.getBean("com.cyan.User");
}
整合Junit5
1.整合Junit4
-
引入针对测试的依赖
-
创建测试类,使用注解方式完成
@RunWith(SpringJUnit4ClassRunner.class) // 单元测试框架 @ContextConfiguration("classpath:bean.xml") //加载配置文件 public class JTest { @Autowired private UserService userService; @Test public void test() { userService.account(); } }
2.整合JUnit5
- 引入依赖
- 创建测试类
@ExtendWith(SpringExtension.class)
@ContextConfiguration("classpath:bean.xml")
public class JTest {
@Autowired
private UserService userService;
@Test
public void test() {
userService.account();
}
}
使用复合注解替代上述两个注解完成Junit5整合
@SpringJUnitConfig(locations="classpath:bean.xml")
public class JTest {
@Autowired
private UserService userService;
@Test
public void test() {
userService.account();
}
}
7. Spring整合mybatis
所需依赖
<dependencies>
<!-- https://mvnrepository.com/artifact/org.springframework/spring-webmvc -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>5.2.0.RELEASE</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.aspectj/aspectjweaver -->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.4</version>
</dependency>
<!--Junit-->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
</dependency>
<!--mysql-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.47</version>
</dependency>
<!-- mybatis -->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.2</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>5.2.0.RELEASE</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.4</version>
</dependency>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
<version>2.0.2</version>
</dependency>
</dependencies>