一篇文章学习Spring5!!

本篇文章是学习完b站尚硅谷Spring5所做的笔记,希望对大家有所帮助!

一、Spring框架概述

  1. Spring 是轻量级的开源的 JavaEE 框架。

  2. Spring 可以解决企业应用开发的复杂。

  3. Spring 有两个核心部分:IOC 和 Aop 。

    (1)IOC:控制反转,把创建对象过程交给 Spring 进行管理

    (2)Aop:面向切面,不修改源代码进行功能增强

  4. Spring 特点 :

    (1)方便解耦,简化开发

    (2)Aop 编程支持

    (3)方便程序测试

    (4)方便和其他框架进行整合

    (5)方便进行事务操作

    (6)降低 API 开发难度

二、IOC容器

1. IOC概念和原理

(1)概念:

  • 控制反转,把对象创建和对象的调用过程交给spring进行管理。

  • 目的:降低耦合度。

  • 底层原理:xml,反射,工厂模式

  • Spring提供IOC容器两种实现方式(两个接口)

    • BeanFactory:Spring内部使用的接口,不提倡开发人员使用。特点:加载配置文件时不会创建对象,获取对象时才会创建对象。

    • **ApplicationContext:**BeanFactory的子接口,提供了更多更强大的功能,一般由开发人员使用。特点:加载配置文件时会把配置文件里的对象进行创建。

    • ApplicationContext两个常用实现类:

      • FileSystemXmlApplicationContext:绝对路径,从盘符开始算起
      • ClassPathXmlApplicationContext:相对路径,从src开始算起
      image-20210720000922536

什么是Bean管理?Bean管理是指两个操作:Spring创建对象 和 Spring注入属性

Bean管理有两种操作方式:基于xml配置文件方式实现 和 基于注解方式实现

2. IOC操作Bean管理(基于xml)

xml实现Bean管理:

(1)基于xml方式创建对象:

image-20210719101725911
  • 在Spring配置文件中使用bean标签来创建对象
  • bean标签有很多属性,常用属性:
    • id:唯一标识
    • class:类路径
  • 创建对象时,默认执行无参构造函数

(2)基于xml方式注入属性:

第一种方法:使用set方法进行注入:

首先先为类的属性提供set方法:

public class User {
    
    

    private String userName;
    private String userAge;

    public void setUserName(String userName) {
    
    
        this.userName = userName;
    }

    public void setUserAge(String userAge) {
    
    
        this.userAge = userAge;
    }

    public String getUserName() {
    
    
        return userName;
    }

    public String getUserAge() {
    
    
        return userAge;
    }
}

然后在xml配置文件中通过property标签进行属性注入

    <!--配置User对象-->
    <bean id="user" class="com.oymn.spring5.User">
        <property name="userName" value="haha"></property>
        <property name="userAge" value="18"></property>
    </bean>

这样就完成了

    ApplicationContext applicationContext = new ClassPathXmlApplicationContext("bean1.xml");
    User user = applicationContext.getBean("user", User.class);
    System.out.println(user.getUserName() + "     " + user.getUserAge());

第二种方法:使用有参构造函数进行注入

首先提供有参构造方法

public class User {
    
    

    private String userName;
    private String userAge;

    public User(String userName, String userAge){
    
    
        this.userName = userName;
        this.userAge = userAge;
    }
}

然后再xml配置文件中通过constructor-arg标签进行属性注入

    <!--配置User对象-->
    <bean id="user" class="com.oymn.spring5.User">
        <constructor-arg name="userName" value="haha"></constructor-arg>
        <constructor-arg name="userAge" value="18"></constructor-arg>
    </bean>

第三种方法:p名称空间注入(了解即可)

首先在xml配置文件中添加p名称空间,并且在bean标签中进行操作

image-20210719104230761

然后提供set方法

public class User {
    
    

    private String userName;
    private String userAge;

    public User() {
    
    
    }

    public void setUserName(String userName) {
    
    
        this.userName = userName;
    }

    public void setUserAge(String userAge) {
    
    
        this.userAge = userAge;
    }
}

(3)xml注入其他属性

  1. null值
    <!--配置User对象-->
    <bean id="user" class="com.oymn.spring5.User">
        <property name="userName"> <null/> </property>
    </bean>
  1. 属性值包含特殊符号

    假设现在userName属性需要赋值为 < haha >

    如果像上面那样直接在value中声明的话会报错,因为包含特殊符号 <>

    image-20210720003501206

    需要通过 <![CDATA[值]]> 来表示

    image-20210720003720138
  2. 注入属性——外部bean

    有两个类:UserService和UserDaoImpl,其中UserDaoImpl实现UserDao接口

    public class UserService {
          
          
    
        private UserDao userDao;
    
        public void setUserDao(UserDao userDao){
          
          
            this.userDao = userDao;
        }
    
        public void add(){
          
          
            System.out.println("add");
        }
    }
    

    通过 ref 来指定创建userDaoImpl

    <bean id="userDaoImpl" class="com.oymn.spring5.UserDaoImpl"></bean>
    
    <bean id="userService" class="com.oymn.spring5.UserService">
        <property name="userDao" ref="userDaoImpl"></property>
    </bean>
    
  3. 注入属性——内部bean

    不通过ref属性,而是通过嵌套一个bean标签实现

<!--内部 bean-->
<bean id="emp" class="com.atguigu.spring5.bean.Emp">
     <!--设置两个普通属性-->
     <property name="ename" value="lucy"></property>
     <property name="gender" value=""></property>
     <!--设置对象类型属性-->
     <property name="dept">
         <bean id="dept" class="com.atguigu.spring5.bean.Dept">
        	 <property name="dname" value="安保部"></property>
         </bean>
     </property>
</bean>
  1. 注入属性——级联赋值

    写法一:也就是上面所说的外部bean,通过ref属性来获取外部bean

    写法二:

    emp类中有ename和dept两个属性,其中dept有dname属性,写法二需要emp提供dept属性的get方法。

    <!--级联赋值-->
    <bean id="emp" class="com.atguigu.spring5.bean.Emp">
        <!--设置两个普通属性-->
        <property name="ename" value="lucy"></property> <property name="gender" value=""></property>
        <!--写法一-->
    	<property name="dept" ref="dept"></property>
        <!--写法二-->
        <property name="dept.dname" value="技术部"></property>
    </bean>
    <bean id="dept" class="com.atguigu.spring5.bean.Dept">
        <property name="dname" value="财务部"></property>
    </bean>
    
  2. 注入集合属性(数组,List,Map)

假设有一个Stu类

public class Stu {
    
    

    private String[] courses;
    private List<String> list;
    private Map<String,String> map;
    private Set<String> set;

    public void setCourses(String[] courses) {
    
    
        this.courses = courses;
    }

    public void setList(List<String> list) {
    
    
        this.list = list;
    }

    public void setMap(Map<String, String> map) {
    
    
        this.map = map;
    }

    public void setSet(Set<String> set) {
    
    
        this.set = set;
    }
}

在xml配置文件中对这些集合属性进行注入

<bean id="stu" class="com.oymn.spring5.Stu">
    <!--数组类型属性注入-->
    <property name="courses">
        <array>
            <value>java课程</value>
            <value>数据库课程</value>
        </array>
    </property>
    <!--List类型属性注入-->
    <property name="list">
        <list>
            <value>张三</value>
            <value>李四</value>
        </list>
    </property>
    <!--Map类型属性注入-->
    <property name="map">
        <map>
            <entry key="JAVA" value="java"></entry>
            <entry key="PHP" value="php"></entry>
        </map>
    </property>
    <!--Set类型属性注入-->
    <property name="set">
        <set>
            <value>Mysql</value>
            <value>Redis</value>
        </set>
    </property>
</bean>
  1. 上面的集合值都是字符串,如果是对象的话,如下:

    写法: 集合+外部bean

<!--创建多个 course 对象-->
<bean id="course1" class="com.atguigu.spring5.collectiontype.Course">
	<property name="cname" value="Spring5 框架"></property>
</bean>
<bean id="course2" class="com.atguigu.spring5.collectiontype.Course">
	<property name="cname" value="MyBatis 框架"></property>
</bean>

<!--注入 list 集合类型,值是对象-->
<property name="courseList">
    <list>
        <ref bean="course1"></ref>
        <ref bean="course2"></ref>
    </list>
</property>
  1. 把集合注入部分提取出来

    使用 util 标签,这样不同的bean都可以使用相同的集合注入部分了。

    <!--将集合注入部分提取出来-->
    <util:list id="booklist">
        <value>易筋经</value>
        <value>九阳神功</value>
    </util:list>
    
    <bean id="book" class="com.oymn.spring5.Book">
        <property name="list" ref="booklist"></property>
    </bean>
    
  2. FactoryBean

    Spring有两种Bean,一种是普通Bean,另一种是工厂Bean(FactoryBean)

    这块看不太懂,不知道有啥用,先放着。

Bean的作用域:

  • 在Spring中,默认情况下bean是单实例对象

image-20210719113035226

执行结果是相同的:

image-20210719113122345
  • 通过 bean标签的scope属性 来设置单实例还是多实例。

Scope属性值:

  • **singleton:**默认值,表示单实例对象。加载配置文件时就会创建单实例对象。
  • prototype:表示多实例对象。不是在加载配置文件时创建对象,在调用getBean方法时创建多实例对象。
image-20210719113500730

执行结果不同了:

image-20210719113518353

Bean的生命周期:

  1. bean的生命周期:

(1)通过构造器创建 bean 实例(无参数构造)

(2)为 bean 的属性设置值和对其他 bean 引用(调用 set 方法)

(3)把 bean 实例传递 bean 后置处理器的方法 postProcessBeforeInitialization

(4)调用 bean 的初始化的方法(需要进行配置初始化的方法)

(5)把 bean 实例传递 bean 后置处理器的方法 postProcessAfterInitialization

(6)bean 可以使用了(对象获取到了)

(7)当容器关闭时候,调用 bean 的销毁的方法(需要进行配置销毁的方法)

  1. 演示bean的生命周期
public class Orders {
    
    
    private String orderName;

    public Orders() {
    
    
        System.out.println("第一步:执行无参构造方法创建bean实例");
    }

    public void setOrderName(String orderName) {
    
    
        this.orderName = orderName;
        System.out.println("第二步:调用set方法设置属性值");
    }

    //初始化方法
    public void initMethod(){
    
    
        System.out.println("第四步:执行初始化方法");
    }

    //销毁方法
    public void destroyMethod(){
    
    
        System.out.println("第七步:执行销毁方法");
    }
}
//实现后置处理器,需要实现BeanPostProcessor接口
public class MyBeanPost implements BeanPostProcessor {
    
    

    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
    
    
        System.out.println("第三步:将bean实例传递给bean后置处理器的postProcessBeforeInitialization方法");
        return bean;
    }

    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
    
    
        System.out.println("第五步:将bean实例传递给bean后置处理器的postProcessAfterInitialization方法");
        return bean;
    }
}

<bean id="orders" class="com.oymn.spring5.Orders" init-method="initMethod" destroy-method="destroyMethod">
    <property name="orderName" value="hahah"></property>
</bean>

<!--配置bean后置处理器,这样配置后整个xml里面的bean用的都是这个后置处理器-->
<bean id="myBeanPost" class="com.oymn.spring5.MyBeanPost"></bean>
@Test
public void testOrders(){
    
    

    ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("bean1.xml");

    Orders orders = context.getBean("orders", Orders.class);

    System.out.println("第六步:获取bean实例对象");
    System.out.println(orders);

    //手动让bean实例销毁
    context.close();
}

执行结果:

image-20210720122628081

xml自动装配:

  • 根据指定的装配规则(属性名称或者属性类型),Spring自动将匹配的属性值进行注入

  • 根据属性名称自动装配:要求 emp中属性的名称dept 和 bean标签的id值dept 一样,才能识别

  • <!--指定autowire属性值为byName-->
    <bean id="emp" class="com.oymn.spring5.Emp" autowire="byName"></bean>
    
    <bean id="dept" class="com.oymn.spring5.Dept"></bean>
    
  • 根据属性类型自动装配:要求同一个xml文件中不能有两个相同类型的bean,否则无法识别是哪一个

  • <!--指定autowire属性值为byType-->
    <bean id="emp" class="com.oymn.spring5.Emp" autowire="byType"></bean>
    
    <bean id="dept" class="com.oymn.spring5.Dept"></bean>
    

通过外部属性文件来操作bean:

例如配置数据库信息:

  1. 导入德鲁伊连接池jar包

  2. 创建外部属性文件,properties格式文件,写数据库信息

    image-20210731004522456
  3. 引入context名称空间,并通过context标签引入外部属性文件,使用“${}”来获取文件中对应的值

    image-20210731010320233

3. IOC操作Bean管理(基于注解)

  • 格式:@注解名称(属性名=属性值,属性名=属性值,……)

  • 注解可以作用在类,属性,方法。

  • 使用注解的目的:简化xml配置

(1)基于注解创建对象:

spring提供了四种创建对象的注解:

  • @Component
  • @Service:一般用于Service层
  • @Controller:一般用于web层
  • @ Repository:一般用于Dao层

流程:

  1. 引入依赖:

    image-20210731144620261
  2. 开启组件扫描:扫描base-package包下所有有注解的类并为其创建对象

    <context:component-scan base-package="com.oymn"></context:component-scan>
    
  3. com.oymn.spring5.Service有一个stuService类

    //这里通过@Component注解来创建对象,括号中value的值等同于之前xml创建对象使用的id,为了后面使用时通过id来获取对象
    //括号中的内容也可以省略,默认是类名并且首字母小写
    //可以用其他三个注解
    @Component(value="stuService")
    public class StuService {
          
          
        public void add(){
          
          
            System.out.println("addService");
        }
    }
    
  4. 这样就可以通过getBean方法来获取stuService对象了

    ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("bean4.xml");
    StuService stuService = context.getBean("stuService", StuService.class);
    System.out.println(stuService);
    stuService.add();
    

开启组件扫描的细节配置:

  1. use-default-fileters设置为false表示不使用默认过滤器,通过include-filter来设置只扫描com.oymn包下的所有@Controller修饰的类。
<context:component-scan base-package="com.oymn" use-default-filters="false">
    <context:include-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
</context:component-scan>
  1. exclude-filter设置哪些注解不被扫描,例子中为@Controller修饰的类不被扫描
<context:component-scan base-package="com.oymn">
    <context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
</context:component-scan>

(2)基于注解进行属性注入:

  • @Autowired:根据属性类型自动装配

    创建StuDao接口和StuDaoImpl实现类,为StuDaoImpl添加创建对象注解

    public interface StuDao {
          
          
        public void add();
    }
    
    @Repository
    public class StuDaoImpl implements StuDao {
          
          
        @Override
        public void add() {
          
          
            System.out.println("StuDaoImpl");
        }
    }
    

    StuService类中添加StuDao属性,为其添加@Autowire注解,spring会自动为stuDao属性创建StuDaoImpl对象

    @Component(value="stuService")
    public class StuService {
          
          
        
        @Autowired
        public StuDao stuDao;
    
        public void add(){
          
          
            System.out.println("addService");
            stuDao.add();
        }
    }
    
    @Test
    public void test1(){
          
          
        ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("bean4.xml");
        StuService stuService = context.getBean("stuService", StuService.class);
        System.out.println(stuService);
        stuService.add();
    }
    

    测试结果:

    image-20210731164052536
  • @Qualifier:根据属性名称自动装配

    当遇到一个接口有很多实现类时,只通过@Autowire是无法完成自动装配的,所以需要再使用@Qualifier通过名称来锁定某个类

    @Component(value="stuService")
    public class StuService {
          
          
    
        @Autowired
        @Qualifier(value="stuDaoImpl")  //这样就能显式指定stuDaoImpl这个实现类
        public StuDao stuDao;
    
        public void add(){
          
          
            System.out.println("addService");
            stuDao.add();
        }
    }
    
  • @Resource:可以根据类型注入,也可以根据名称注入

    @Component(value="stuService")
    public class StuService {
          
          
        
        //@Resource   //根据类型进行注入
        @Resource(name="stuDaoImpl")  //根据名称进行注入
        public StuDao stuDao;
    
        public void add(){
          
          
            System.out.println("addService");
            stuDao.add();
        }
    }
    
  • @Value:注入普通类型属性

    @Value(value = "abc")
    private String name;
    

(3)完全注解开发:

创建配置类,替代xml配置文件

@Configuration    //表明为一个配置类
@ComponentScan(basePackages = "com.oymn")   //开启组件扫描
public class SpringConfig {
    
    
}

测试类:

@Test
public void test2(){
    
    
    //创建AnnotationConfigApplicationContext对象
    AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(SpringConfig.class);
    StuService stuService = context.getBean("stuService", StuService.class);
    System.out.println(stuService);
    stuService.add();
}

三、AOP

1. 底层原理

  • 面向切面编程,利用 AOP 可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。通俗来说就是在不修改代码的情况下添加新的功能。

  • 底层通过动态代理来实现:

    • 第一种:有接口的情况,使用JDK动态代理:创建接口实现类的代理对象
    • 第二种:无接口的情况,使用CGLIB动态代理:创建当前类子类的代理对象

JDK动态代理举例:

  • 通过 java.lang.reflect.Proxy类newProxyInstance方法 创建代理类。

  • newProxyInstance方法:

  • image-20210801004308007

    参数一:类加载器

    参数二:所增强方法所在的类,这个类实现的接口,支持多个接口

    参数三:实现InvocationHandle接口,重写invoke方法来添加新的功能

代码举例:

public interface UserDao {
    
    
    public int add(int a, int b);
    public int multi(int a, int b);
}
public class UserDaoImpl implements UserDao {
    
    
    @Override
    public int add(int a, int b) {
    
    
        return a+b;
    }

    @Override
    public int multi(int a, int b) {
    
    
        return a*b;
    }
}
public class Main {
    
    

    @Test
    public void test1(){
    
    

        //所需代理的类实现的接口,支持多个接口
        Class[] interfaces = {
    
    UserDao.class};
		
        UserDao userDao = new UserDaoImpl();
        
		//调用newProxyInstance方法来创建代理类
        UserDao userDaoProxy = (UserDao) Proxy.newProxyInstance(Main.class.getClassLoader(), interfaces, new UserDaoProxy(userDao));
		
        int result = userDaoProxy.add(1, 2);
        System.out.println(result);
    }

    //创建内部类,实现InvocationHandler接口,重写invoke方法,添加新功能
    class UserDaoProxy implements InvocationHandler {
    
    

        Object obj;
		//通过有参构造函数将所需代理的类传过来
        public UserDaoProxy(Object obj){
    
    
            this.obj = obj;
        }

        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    
    

            System.out.println("进入" + method.getName() + "方法,这是新增的代码,参数有" + Arrays.toString(args));
            
			//执行原有的代码
            Object invoke = method.invoke(obj, args);
            
            System.out.println("方法原先的内容执行完了");
            
            return invoke;
        }
    }
}

运行结果:

image-20210801005210363

2. 基于AspectJ实现AOP操作

(1)AOP相关术语:

  • 连接点:类中可以被增强的方法,称为连接点。
  • 切入点:实际被增强的方法,称为切入点。
  • 通知:增强的那一部分逻辑代码。通知有多种类型:
    • 前置通知:增强部分代码在原代码前面。
    • 后置通知:增强部分代码在原代码后面。
    • 环绕通知:增强部分代码既有在原代码前面,也有在原代码后面。
    • 异常通知:原代码发生异常后才会执行。
    • 最终通知:类似与finally那一部分
  • 切面:指把通知应用到切入点这一个动作。

(2)基于AspectJ实现AOP有两种方式:

  • 基于xml配置文件
  • 基于注解方法

(3)切入点表达式

  • 语法:execution([权限修饰符] [返回类型] [类全路径] [方法名称] [参数列表])

  • 举例1:对 com.atguigu.dao.BookDao 类里面的 add 进行增强

    execution(* com.auguigu.dao.BookDao.add(..))
    
  • 举例2:对 com.atguigu.dao.BookDao 类里面的所有的方法进行增强

    execution(* com.atguigu.dao.BookDao.*(..))
    
  • 举例 3:对 com.atguigu.dao 包里面所有类,类里面所有方法进行增强

    execution(* com.atguigu.dao.*.* (..))
    

(1)基于注解方式

@Component
public class User {
    
    
    public void add(){
    
        
        System.out.println("User.add()");
    }
}
@Component
@Aspect   //使用Aspect注解
public class UserProxy {
    
    
    //前置通知
    @Before(value="execution(* com.oymn.spring5.User.add(..))")
    public void before(){
    
    
        System.out.println("UserProxy.before()");
    }
    
    //后置通知
    @AfterReturning(value="execution(* com.oymn.spring5.User.add(..))")
    public void afterReturning(){
    
    
        System.out.println("UserProxy.afterReturning()");
    }
    
    //最终通知
    @After(value="execution(* com.oymn.spring5.User.add(..))")
    public void After(){
    
    
        System.out.println("UserProxy.After()");
    }

    //异常通知
    @AfterThrowing(value="execution(* com.oymn.spring5.User.add(..))")
    public void AfterThrowing(){
    
    
        System.out.println("UserProxy.AfterThrowing()");
    }

    //环绕通知
    @Around(value="execution(* com.oymn.spring5.User.add(..))")
    public void Around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable{
    
    

        System.out.println("UserProxy.Around()   _1");

        //调用proceed方法执行原先部分的代码
        proceedingJoinPoint.proceed();

        System.out.println("UserProxy.Around()   _2");
    }
}

配置xml文件:

<!--开启组件扫描-->
<context:component-scan base-package="com.oymn"></context:component-scan>
<!--开启AspectJ生成代理对象-->
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>

测试类:

@Test
public void test2(){
    
    
    ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("bean1.xml");
    User user = context.getBean("user", User.class);
    user.add();
}

运行结果:

image-20210801210024676

运行结果中没有出现异常通知,在add方法中添加int i = 1/0;

public void add(){
    
    
    int i = 1/0;
    System.out.println("User.add()");
}

运行结果:从这里也可以看到,但出现异常时,After最终通知有执行,而AfterReturning后置通知并没有执行。

image-20210801210304774

对于上面的例子,有很多通知的切入点都是相同的方法,因此,可以将该切入点进行抽取:通过@Pointcut注解

@Pointcut(value="execution(* com.oymn.spring5.User.add(..))")
public void pointDemo(){
    
    
    
}

//前置通知
@Before(value="pointDemo()")
public void before(){
    
    
    System.out.println("UserProxy.before()");
}

设置增强类优先级:

当有多个增强类对同一方法进行增强时,可以通过**@Order(数字值)来设置增强类的优先级,数字越小优先级越高。**

@Component
@Aspect
@Order(1)
public class PersonProxy

完全注解开发

可以通过配置类来彻底摆脱xml配置文件:

@Configuration
@ComponentScan(basePackages = "com.oymn.spring5")
//@EnableAspectJAutoProxy注解相当于上面xml文件中配置的 <aop:aspectj-autoproxy></aop:aspectj-autoproxy>
@EnableAspectJAutoProxy(proxyTargetClass = true)  
public class Config {
    
    
}

(2)基于xml方式

这种方式开发中不怎么用,了解即可。

创建Book和BookProxy类

public class Book {
    
    
    public void buy(){
    
    
        System.out.println("buy()");
    }
}
public class BookProxy {
    
    
    public void before(){
    
    
        System.out.println("before()");
    }
}

配置xml文件:

<!--创建对象-->
<bean id="book" class="com.oymn.spring5.Book"></bean>
<bean id="bookProxy" class="com.oymn.spring5.BookProxy"></bean>

<aop:config>
    <!--切入点-->
    <aop:pointcut id="p" expression="execution(* com.oymn.spring5.Book.buy(..))"/>
    <!--配置切面-->
    <aop:aspect ref="bookProxy">
        <aop:before method="before" pointcut-ref="p"/>  <!--将bookProxy中的before方法配置为切入点的前置通知-->
    </aop:aspect>
</aop:config>

四、JdbcTemplate

  • Spring对JDBC进行封装,使用JdbcTemplate方便对数据库的操作。

(1)增删改操作:

int update(String sql, Object... args);

(2)查询:返回某个值

T queryForObject(String sql,Class<T> requiredType);

(3)查询:返回某个对象

T queryForObject(String sql,RowMapper<T> rowMapper,Object ... args);

(4)查询:返回集合

List<T> query(String sql,RowMapper<T> rowMapper,Object... args);

(5)批量增删改:

int[] batchUpdate(String sql,List<Object[]> batchArgs);

举例:

  1. 引入相关jar包

    image-20210801231107102
  2. 配置数据库连接池;配置JdbcTemplate对象

    <context:component-scan base-package="com.oymn"></context:component-scan>
    
    <!--配置数据库连接池 -->
    <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource" destroy-method="close">
        <property name="url" value="jdbc:mysql://localhost:3306/book" />
        <property name="username" value="root" />
        <property name="password" value="000000" />
        <property name="driverClassName" value="com.mysql.jdbc.Driver" />
    </bean>
    
    <!--创建JdbcTemplate对象-->
    <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
        <!--注入数据库连接池-->
        <property name="dataSource" ref="dataSource"></property>
    </bean>
    
  3. 创建Service类和Dao类,在Dao类中注入JdbcTemplate对象

    public interface BookDao {
          
          
    
        public void add(Book book);  //添加图书
    
        public void update(Book book);  //修改图书
    
        public void delete(int id);  //删除图书
    
        public int queryCount();   //查询数量
    
        public Book queryBookById(int id);  //查询某本书
    
        public List<Book> queryBooks();   //查询所有书
    
        public void batchAddBook(List<Object[]> books);  //批量添加图书
    
        public void batchUpdateBook(List<Object[]> books);  //批量修改图书
    
        public void batchDeleteBook(List<Object[]> args);  //批量删除图书
    }
    
    
    @Repository
    public class BookDaoImpl implements BookDao {
          
          
    
        @Autowired
        private JdbcTemplate jdbcTemplate;
    
        @Override
        public void add(Book book) {
          
          
            String sql = "insert into t_book set name=?,price=?";
            Object[] args = {
          
          book.getBookName(),book.getBookPrice()};
            int update = jdbcTemplate.update(sql, args);
            System.out.println(update);
        }
    
        @Override
        public void update(Book book) {
          
          
            String sql = "update t_book set name=?,price=? where id=?";
            Object[] args = {
          
          book.getBookName(),book.getBookPrice(),book.getBookId()};
            int update = jdbcTemplate.update(sql, args);
            System.out.println(update);
        }
    
        @Override
        public void delete(int id) {
          
          
            String sql = "delete from t_book where id=?";
            int update = jdbcTemplate.update(sql, id);
            System.out.println(update);
        }
    
        @Override
        public int queryCount() {
          
          
            String sql = "select count(*) from t_book";
            Integer count = jdbcTemplate.queryForObject(sql, Integer.class);
            return count;
        }
    
        @Override
        public Book queryBookById(int id) {
          
          
            String sql = "select id bookId,name bookName,price bookPrice from t_book where id=?";
            Book book = jdbcTemplate.queryForObject(sql, new BeanPropertyRowMapper<Book>(Book.class), id);
            return book;
        }
    
        @Override
        public List<Book> queryBooks() {
          
          
            String sql = "select id bookId,name bookName,price bookPrice from t_book";
            List<Book> bookList = jdbcTemplate.query(sql, new BeanPropertyRowMapper<Book>(Book.class));
            return bookList;
        }
    
        @Override
        public void batchAddBook(List<Object[]> books) {
          
          
            String sql = "insert into t_book set id=?,name=?,price=?";
            int[] ints = jdbcTemplate.batchUpdate(sql, books);
            System.out.println(ints);
        }
    
        @Override
        public void batchUpdateBook(List<Object[]> books) {
          
          
            String sql = "update t_book set name=?,price=? where id=?";
            int[] ints = jdbcTemplate.batchUpdate(sql, books);
            System.out.println(ints);
        }
    
        @Override
        public void batchDeleteBook(List<Object[]> args) {
          
          
            String sql = "delete from t_book where id=?";
            int[] ints = jdbcTemplate.batchUpdate(sql, args);
            System.out.println(ints);
        }
    }
    
    
    @Service
    public class BookService {
          
          
        @Autowired
        private BookDao bookDao = new BookDaoImpl();
        //添加图书
        public void add(Book book){
          
          
            bookDao.add(book);
        }
        //修改图书
        public void update(Book book){
          
          
            bookDao.update(book);
        }
        //删除图书
        public void delete(Integer id){
          
          
            bookDao.delete(id);
        }
        //查询数量
        public int queryCount(){
          
          
            return bookDao.queryCount();
        }
        //查询图书
        public Book queryBookById(Integer id){
          
          
            return bookDao.queryBookById(id);
        }
        //查询所有图书
        public List<Book> queryBooks(){
          
          
            return bookDao.queryBooks();
        }
        //批量添加图书
        public void batchAddBook(List<Object[]> books){
          
          
            bookDao.batchAddBook(books);
        }
        //批量修改图书
        public void batchUpdateBook(List<Object[]> books){
          
          
            bookDao.batchUpdateBook(books);
        }
        //批量删除图书
        public void batchDeleteBook(List<Object[]> args){
          
          
            bookDao.batchDeleteBook(args);
        }
    }
    

五、事务管理

  • 事务是数据库操作最基本单位,要么都成功,要么都失败。

  • 典型场景:转账

  • 事务四个特性ACID:原子性,一致性,隔离性,持久性。

  • Spring事务管理有两种方式:编程式事务管理 和 声明式事务管理,一般使用声明式事务管理,底层使用AOP原理。

  • 声明式事务管理有两种方式:基于xml配置方式 和 基于注解方式,一般使用注解方式。

  • Spring事务管理提供了一个接口,叫做事务管理器,这个接口针对不同的框架提供不同的实现类。

    image-20210802191449867

    对于使用JdbcTemplate进行数据库交互,则使用DataSourceTransactionManager实现类,如果整合Hibernate框架则使用HibernateTransactionManager实现类,具体情况具体使用。

(1)注解实现声明式事务管理:

<!-- 数据库连接池 -->
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource"
      destroy-method="close">
    <property name="url" value="jdbc:mysql://localhost:3306/book" />
    <property name="username" value="root" />
    <property name="password" value="000000" />
    <property name="driverClassName" value="com.mysql.jdbc.Driver" />
</bean>

<!--创建JdbcTemplate对象-->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
    <!--注入数据库连接池-->
    <property name="dataSource" ref="dataSource"></property>
</bean>

<!--创建事务管理器-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
    <property name="dataSource" ref="dataSource"/>
</bean>

<!--开启事务注解-->
<tx:annotation-driven transaction-manager="transactionManager"></tx:annotation-driven>

在service类上面或者service类的方法上面添加事务注解@Transactional

  • 如果把@Transactional添加在类上面,这个类里面所有方法都添加事务。
  • 如果只是添加在方法上面,则只为这个方法添加事务。
@Service
@Transactional
public class UserService {
    
    

声明式事务管理的参数配置:

  1. propagation:事务传播行为,总共有7种,这一块讲的不是很清楚

  2. isolation:事务隔离级别

    有三个读问题:脏读,不可重复读,虚读(幻读)。

    设置隔离级别,解决读问题:

    脏读 不可重复读 虚读
    READ UNCOMMITED(读未提交)
    READ COMMITED(读已提交)
    REPEATABLE READ(可重复读)
    SERIALIZABLE(串行化)
  3. timeout:超时时间

  • 事务需要在一定时间内进行提交,超过时间后回滚。
  • 默认值是-1,设置时间以秒为单位。
  1. readOnly:是否只读
  • 默认值为false,表示可以查询,也可以增删改。
  • 设置为true,只能查询。
  1. rollbackFor:回滚,设置出现哪些异常进行事务回滚。
  2. noRollbackFor:不回滚,设置出现哪些异常不进行事务回滚。
@Service
@Transactional(propagation = Propagation.REQUIRED,isolation = Isolation.READ_COMMITTED)
public class AccountService {
    
    

完全注解实现声明式事务管理:

配置类:

@Configuration  //配置类
@ComponentScan(basePackages = "com.oymn.spring5")  //开启组件扫描
@EnableTransactionManagement  //开启事务
public class Config {
    
    

    //创建数据库连接池
    @Bean
    public DruidDataSource getDruidDataSource(){
    
    
        DruidDataSource druidDataSource = new DruidDataSource();
        druidDataSource.setDriverClassName("com.mysql.jdbc.Driver");
        druidDataSource.setUrl("jdbc:mysql://localhost:3306/book");
        druidDataSource.setUsername("root");
        druidDataSource.setPassword("000000");
        return druidDataSource;
    }
    //创建JdbcTemplate对象
    @Bean
    public JdbcTemplate getJdbcTemplate(DataSource dataSource){
    
    
        JdbcTemplate jdbcTemplate = new JdbcTemplate();
        jdbcTemplate.setDataSource(dataSource);
        return jdbcTemplate;
    }
    //创建事务管理器
    @Bean
    public DataSourceTransactionManager getDataSourceTransactionManager(DataSource dataSource){
    
    
        DataSourceTransactionManager transactionManager = new DataSourceTransactionManager();
        transactionManager.setDataSource(dataSource);
        return transactionManager;
    }
}
@Service
public class AccountService {
    
    

    @Autowired
    private AccountDao accountDao;

    @Transactional
    public void accountMoney(){
    
    
        accountDao.add();
        //int i=1/0;   //用来模拟转账失败
        accountDao.reduce();
    }
}

(2)xml实现声明式事务管理:

<context:component-scan base-package="com.oymn"></context:component-scan>

<!-- 数据库连接池 -->
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource"
      destroy-method="close">
    <property name="url" value="jdbc:mysql://localhost:3306/book" />
    <property name="username" value="root" />
    <property name="password" value="000000" />
    <property name="driverClassName" value="com.mysql.jdbc.Driver" />
</bean>

<!--创建JdbcTemplate对象-->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
    <!--注入数据库连接池-->
    <property name="dataSource" ref="dataSource"></property>
</bean>

<!--创建事务管理器-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
    <property name="dataSource" ref="dataSource"/>
</bean>

<!--配置事务通知-->
<tx:advice id="txadvice">
    <!--配置事务参数-->
    <tx:attributes>
        <tx:method name="accountMoney" propagation="REQUIRED" />
    </tx:attributes>
</tx:advice>

<!--配置切入点和切面-->
<aop:config>
    <!--配置切入点-->
    <aop:pointcut id="pt" expression="execution(* com.oymn.spring5.Service.*.*(..))"/>
    <!--配置切面-->
    <aop:advisor advice-ref="txadvice" pointcut-ref="pt"/>
</aop:config>

六、Spring5新特性

1. 自带了日志封装

  • Spring5移除了Log4jConfigListener,官方建议使用Log4j2

Spring5整合Log4j2:

第一步:引入jar包

image-20210807143733498

第二步:创建log4j2.xml配置文件

<?xml version="1.0" encoding="UTF-8"?>
<!--日志级别以及优先级排序: OFF > FATAL > ERROR > WARN > INFO > DEBUG > TRACE > ALL -->
<!--Configuration后面的status用于设置log4j2自身内部的信息输出,可以不设置,当设置成trace时,可以看到log4j2内部各种详细输出-->
<configuration status="INFO">
    <!--先定义所有的appender-->
    <appenders>
        <!--输出日志信息到控制台-->
        <console name="Console" target="SYSTEM_OUT">
            <!--控制日志输出的格式-->
            <PatternLayout pattern="%d{yyyy-MM-dd HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n"/>
        </console>
    </appenders>
    <!--然后定义logger,只有定义了logger并引入的appender,appender才会生效-->
    <!--root:用于指定项目的根日志,如果没有单独指定Logger,则会使用root作为默认的日志输出-->
    <loggers>
        <root level="info">
            <appender-ref ref="Console"/>
        </root>
    </loggers>
</configuration>

2. @Nullable注解

  • @Nullable注解可以用在方法上,属性上,参数上,表示方法返回值可以为空,属性可以为空,参数可以为空。

    @Nullable     //表示方法返回值可以为空
    public int getId();
    
    @Nullable     //表示参数可以为空
    public void setId(@Nullable int Id);
    
    @Nullable     //表示属性可以为空
    public int id;
    

3. 支持函数式风格编程

这是因为java8新增了lamda表达式

@Test
public void test() {
    
    
    //1 创建 GenericApplicationContext 对象
    GenericApplicationContext context = new GenericApplicationContext();
    //2 调用 context 的方法对象注册
    context.refresh();
    context.registerBean("user1",User.class,() -> new User());
    //3 获取在 spring 注册的对象
    // User user = (User)context.getBean("com.atguigu.spring5.test.User");
    User user = (User)context.getBean("user1");
    System.out.println(user);
}

4. 支持整合JUnit5

(1)整合JUnit4:

第一步:引入jar包

image-20210807181007790

第二步:创建测试类,使用注解方式完成

@RunWith(SpringJUnit4ClassRunner.class) //单元测试框架
@ContextConfiguration("classpath:bean4.xml") //加载配置文件
public class JUnitTest {
    
    

    @Autowired
    public User user;

    @Test
    public void test(){
    
    
        System.out.println(user);
    }
}

bean4.xml:

<context:component-scan base-package="com.oymn"></context:component-scan>

通过使用@ContextConfiguration注解,测试方法中就不用每次都通过context来获取对象了,比较方便。

ApplicationContext context = new ClassPathXmlApplicationContext("bean2.xml");
BookService bookService = context.getBean("bookService",BookService.class);

(2)整合JUnit5:

image-20210807212940977

5. Webflux

猜你喜欢

转载自blog.csdn.net/OYMNCHR/article/details/120077303
今日推荐