[阶段3 企业开发基础] 2. Spring

文章目录

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的子接口,提供更强大的功能
      • 加载配置文件时就创建对象
  • 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.完全注解开发
  1. 创建配置类,代替xml配置文件
@Configuration
@ComponentScan(basePackages={"com.cyan"}) 
public class SpringConfig {
    
}
  1. 编写测试类
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.引入外部属性文件配置数据库连接池

  1. 编写properties文件
driverClass=com.mysql.jdbc.Driver
url=jdbc://localhost:3306/user_db
username=xxxx
password=xxxx
  1. 将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. 幻读的重点在于新增或者删除:同样的条件, 第1次和第2次读出来的记录数不一样
  3. 从总的结果来看, 似乎不可重复读和幻读都表现为两次读取的结果不一致。但如果你从控制的角度来看, 两者的区别就比较大。 对于前者, 只需要锁住满足条件的记录。 对于后者, 要锁住满足条件及其相近的记录
隔离级别含义
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>
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Cyan Chau

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值