当前位置: 首页 > news >正文

[JavaWeb]—Spring入门

IOC与DI

IOC:是一种把对象的创建和对象之间的调用过程,交给spring管理,从而减低耦合度的一种面向对象的设计方式

DI:是ioc的另一种表表达形式;即组件以一些预先定义好的方式(例如:setter方法)接收来自于容器的资源注入。相对于ioc而言,这种表述更直接

简单地说IOC就是一种反转控制的思想,而DI是对IOC的一种具体实现

IOC容器

spring的ioc容器就是ioc思想的一个落地产品的实现。ioc容器中管理的组件叫做bean。在创建bean之前,首先需要创建ioc容器。spring提供了ioc容器的两种实现方式

BeanFactory:这是IOC容器的基本实现,是spring内部使用的接口。面向spring本身,不提供给开发人员使用
ApplicationContext:BeanFactory的子接口,提供了更多高级的特性。面向spring的使用者,几乎所有的场合都使用ApplicationContext,而不是底层的BeanFactory

基于Xml文件来管理bean

依赖

<!--设置打包方式-->
<packaging>jar</packaging>
<dependencies>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-context</artifactId>
        <version>5.3.1</version>
    </dependency>
    <!--junit测试-->
    <dependency>
        <groupId>junit</groupId>
        <artifactId>junit</artifactId>
        <version>4.12</version>
        <scope>test</scope>
    </dependency>
</dependencies>

helloworld对象

public class HelloWorld {
    public void sayHello(){
        System.out.println("Hello,Spring");
    }
}

spring配置文件

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
    
    <!--ioc容器配置文件-->
    <!--id是bean的唯一标识,class是bean对象所对应的类型(将helloworld这个对象交给ioc容器来管理)-->
    <bean id="helloworld" class="com.sentiment.pojo.HelloWorld"></bean>
</beans>

测试类

import org.junit.Test;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class SpringTest {
    @Test
    public void HelloWorldTest(){
        //通过配置文件获取ioc容器
        ClassPathXmlApplicationContext ioc = new ClassPathXmlApplicationContext("applicationContext.xml");
        //从ioc中获取bean对象
        HelloWorld bean = (HelloWorld) ioc.getBean("helloworld");
        bean.sayHello();
    }
}

获取bean的三种方式

  • 根据bean的id获取
Student studentOne = (Student) ioc.getBean("studentOne");
  • 根据bean的类型获取
Student studentOne = ioc.getBean(Student.class);

注意:根据类型获取bean时,要求Ioc容器中有且只有一个类型匹配的bean

​ 若没有任何一个类型匹配的bean,此时抛出异常:NoSuchBeanDefinitionException

​ 若有多个类型匹配的bean,此时抛出异常:NouniqueBeanDefinitionException

  • 根据bean的id和类型获取
Student studentOne = ioc.getBean("studentOne", Student.class);

根据类型来获取bean时,在满足bean唯一性的前提下其实只是看:『对象instanceof指定的类型』的返回结果只要返回的是true就可以认定为和类型匹配,能够获取到。即通过bean的类型、bean所继承的类的类型、bean所实现的接口的类型都可以获取bean

扩展

若组件类实现了接口,可以根据接口获取bean,但若这个接口有多个实现类则无法获取即:若Student实现或继承了Persion类,则可以通过person获取student的bean

Person person = ioc.getBean(Person.class);

三种方式及扩展

public void testIoc(){
    ClassPathXmlApplicationContext ioc = new ClassPathXmlApplicationContext("spring-ioc.xml");
    //Student studentOne = (Student) ioc.getBean("studentOne");
    //Student studentOne = ioc.getBean(Student.class);
    //Student studentOne = ioc.getBean("studentOne", Student.class);
    Person person = ioc.getBean(Person.class);
    System.out.println(person);
}

依赖注入

setter注入

配置

  • property:通过成员变量的set方法进行赋值
  • name :设置需要赋值的属性名(和set方法有关)
  • value:设置为属性所赋的值
<bean id="studentTwo" class="com.sentiment.pojo.Student">
    <property name="sid" value="1842"></property>
    <property name="sname" value="Sentiment"></property>
    <property name="age" value="20"></property>
    <property name="gender" value=""></property>
</bean>

测试

public void testSetter(){
    ClassPathXmlApplicationContext ioc = new ClassPathXmlApplicationContext("spring-ioc.xml");
    Student studentTwo = ioc.getBean("studentTwo", Student.class);
    System.out.println(studentTwo);
}

结果

此时通过property标签,对各个属性进行了赋值

Student{sid=1842, sname='Sentiment', age='20', gender='男'}

构造器注入

配置

若只有一个有参控制器,直接赋值即可

<bean id="studentThree" class="com.sentiment.pojo.Student">
    <constructor-arg value="1843"></constructor-arg>
    <constructor-arg value="Tana"></constructor-arg>
    <constructor-arg value="21"></constructor-arg>
    <constructor-arg value="男"></constructor-arg>
</bean>

测试

public void testByConstructor(){
    ClassPathXmlApplicationContext ioc = new ClassPathXmlApplicationContext("spring-ioc.xml");
    Student studentThree = ioc.getBean("studentThree", Student.class);
    System.out.println(studentThree);
}

结果

Student{sid=1843, sname='Tana', age='21', gender='男'}

此时若再加一个属性并定义他的有参控制器

public Student(Integer sid, String sname, Double score, String gender) {
    this.sid = sid;
    this.sname = sname;
    this.score = score;
    this.gender = gender;
}

在执行后结果:

Student{sid=1843, sname='Tana', age='null', gender='男', score=21.0}

可以看到21想赋给age但,赋值给了score,所以在配置文件后边可以再加上一个name属性,来定义赋值对象

<constructor-arg value="21" name="age"></constructor-arg>

此时结果就赋给了age

Student{sid=1843, sname='Tana', age='21', gender='男', score=null}

特殊值处理

赋值null

常规的通过value赋值,只会将null字符串赋给属性,并不是真正意义的null值,所以若想要为null的话可以通过<null>标签完成

<bean id="studentFour" class="com.sentiment.pojo.Student">
    <property name="sid" value="1844"></property>
    <property name="sname" value="Shelter"></property>
    <property name="gender">
        <null></null>
    </property>
    <property name="age" value="22"></property>
</bean>

xml实体

在xml文档中,<>会被当做标签处理,因此不能随便使用,而当我们赋值中需要带上<>时,则会报错,此时就可以用xml实体来表示

<property name="sname" value="&lt;Shelter&gt;"></property>

CDATA字节

除上边方法外,还可以通过CDATA字节解决即:

CDATA中的数据会被正常解析

<property name="sname" >
    <value><![CDATA[<Shelter>]]></value>
</property>

不同类型属性赋值

为类类型属性赋值

加一个Clazz类

package com.sentiment.pojo;

public class Clazz {

    private Integer cid;

    private String cname;

    public Integer getCid() {
        return cid;
    }

    public void setCid(Integer cid) {
        this.cid = cid;
    }

    public String getCname() {
        return cname;
    }

    public void setCname(String cname) {
        this.cname = cname;
    }

    @Override
    public String toString() {
        return "Clazz{" +
                "cid=" + cid +
                ", cname='" + cname + '\'' +
                '}';
    }
}

并在student类中,添加一个Clazz类型的属性

private Clazz clazz;

外部bean

ref:引用IOC容器中的某个bean的id

Clazz是类对象,因此不能直接使用value赋值,要使用ref

<bean id="studentFive" class="com.sentiment.pojo.Student">
    <property name="sid" value="1845"></property>
    <property name="sname" value="Demo"></property>
    <property name="age" value="23"></property>
    <property name="gender" value=""></property>
    <property name="clazz" ref="clazzOne"></property>
</bean>

<bean id="clazzOne" class="com.sentiment.pojo.Clazz">
    <property name="cid" value="1"></property>
    <property name="cname" value="QLNU"></property>
</bean>

测试

@Test
public void testByRef(){
    ClassPathXmlApplicationContext ioc = new ClassPathXmlApplicationContext("spring-ioc.xml");
    Student studentFive = ioc.getBean("studentFive", Student.class);
    System.out.println(studentFive);
}

级联方式

这种方式,仍需用ref,因为不是用的话 clazz值为空,匹配不到对应的cid和cname,所以这种方式也就相当于一种另外的赋值方式

<bean>
	<property name="clazz" ref="clazzOne"></property>
    <property name="clazz.cid" value="2"></property>
    <property name="clazz.cname" value="QlNU2"></property>
</bean>

<bean id="clazzOne" class="com.sentiment.pojo.Clazz">
    <property name="cid" value="1"></property>
    <property name="cname" value="QLNU"></property>
</bean>

内部bean

内部bean,只能在当前bean的内部使用,不能直接通过ioc容器得到

    <bean id="studentFive" class="com.sentiment.pojo.Student">
        <property name="sid" value="1845"></property>
        <property name="sname" value="Demo"></property>
        <property name="age" value="23"></property>
        <property name="gender" value=""></property>
        <property name="clazz">
            <bean id="clazzInner" class="com.sentiment.pojo.Clazz">
                <property name="cid" value="2"></property>
                <property name="cname" value="QLNU2"></property>
            </bean>
        </property>
<!--        <property name="clazz.cid" value="2"></property>-->
<!--        <property name="clazz.cname" value="QlNU2"></property>-->
    </bean>

数组类型属性赋值

添加一个字符串数组型变量 hobby,并设置对应的setter、getter和toString方法

private String[] hobby;

数组类型赋值有对应的标签<array>,如果数组存储的是类变量,则将value标签改为ref标签

<property name="hobby">
    <array>
        <value></value>
        <value></value>
        <value>rap</value>
        <value>篮球</value>
    </array>
</property>

list集合类型赋值

在clazz类中添加一个students集合属性,并设置对应的setter、getter和toString方法

private List<Student> students;

list标签

同数组形式,直接用list标签即可,由于存储的事Student类型数据,所以里边用ref标签

<property name="students">
    <list>
        <ref bean="studentOne"></ref>
        <ref bean="studentTwo"></ref>
        <ref bean="studentThree"></ref>
    </list>
</property>

util标签

list标签相当于是内部调用,而util就相当于外部调用

<!--通过ref调用util标签-->
<property name="students" ref="studentList"></property>
<!--定义一个util标签-->
<util:list id="studentList">
    <ref bean="studentOne"></ref>
    <ref bean="studentTwo"></ref>
    <ref bean="studentThree"></ref>
</util:list>

map集合类型赋值

新建一个Teacher类

package com.sentiment.pojo;

public class Teacher {
    private Integer tid;

    private String tname;

    @Override
    public String toString() {
        return "Teacher{" +
                "tid=" + tid +
                ", tname='" + tname + '\'' +
                '}';
    }

    public Integer getTid() {
        return tid;
    }

    public void setTid(Integer tid) {
        this.tid = tid;
    }

    public String getTname() {
        return tname;
    }

    public void setTname(String tname) {
        this.tname = tname;
    }
}

在Student类中,定义一个 teacher类型的map属性,并设置对应的setter、getter、toString方法

private Map<String,Teacher> teacherMap;

map标签

map中的entry标签 ,自动存储键和值,设置value-ref存储 bean中的类型数据

<property name="teacherMap">
    <map>
        <entry key="1" value-ref="teacherOne"></entry>
        <entry key="2" value-ref="teacherTwo"></entry>
    </map>
</property>

<bean id="teacherOne" class="com.sentiment.pojo.Teacher">
    <property name="tid" value="1"></property>
    <property name="tname" value="AA"></property>
 </bean>
<bean id="teacherTwo" class="com.sentiment.pojo.Teacher">
    <property name="tid" value="2"></property>
    <property name="tname" value="BB"></property>
</bean>

util标签

<!--调用定义的map类型的util标签--->
<property name="teacherMap" ref="studentMap"></property>

<!--定义map类型的util标签-->
<util:map id="studentMap">
    <entry key="1" value-ref="teacherOne"></entry>
    <entry key="2" value-ref="teacherTwo"></entry>
</util:map>

p命名空间

配置

<bean id="studentSix" class="com.sentiment.pojo.Student"
      p:sid="111" p:sname="Sentiment" p:teacherMap-ref="studentMap"></bean>

测试

public void testByPnamesapce(){
    ClassPathXmlApplicationContext ioc = new ClassPathXmlApplicationContext("spring-ioc.xml");
    Student studentSix = ioc.getBean("studentSix", Student.class);
    System.out.println(studentSix);
}

spring管理数据源和引入外部属性文件

依赖

<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>5.1.6</version>
</dependency>
<!--数据源-->
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>druid</artifactId>
    <version>1.0.31</version>
</dependency>

配置文件

jdbc.properties用的是mybatis里的

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd">
    <!--引入jdbc.properties配置文件-->
    <context:property-placeholder location="jdbc.properties"></context:property-placeholder>
    
    <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
        <property name="driverClassName" value="${jdbc.driver}"></property>
        <property name="url" value="${jdbc.url}"></property>
        <property name="username" value="${jdbc.username}"></property>
        <property name="password" value="${jdbc.password}"></property>
    </bean>
</beans>

测试

@Test
public void dataSourceTest() throws SQLException {
    ClassPathXmlApplicationContext ioc = new ClassPathXmlApplicationContext("spring-datasource.xml");
    DruidDataSource bean = ioc.getBean(DruidDataSource.class);
    System.out.println(bean.getConnection());
}

在这里插入图片描述

bean的作用域及生命周期

作用域

在spring中可以通过配置bean标签的scope属性来指定bean的作用域范围,属性如下

常规情况下同一个ioc容器获取的bean值是相等的,在比较时会返回true

public void ScopeTest(){
    ClassPathXmlApplicationContext ioc = new ClassPathXmlApplicationContext("spring-scope.xml");
    Student bean1 = ioc.getBean(Student.class);
    Student bean2 = ioc.getBean(Student.class);
    System.out.println(bean1 == bean2);
}

当设置了scope标签并且值为prototype时(默认为singleton),两个bean值则会不同,返回false

<bean id="student" class="com.sentiment.pojo.Student" scope="prototype">
    <property name="sid" value="1842"></property>
    <property name="sname" value="Sentiment"></property>
</bean>

如果是在WebApplicationContext环境下会有另外两个作用域

取值含义
request在一个请求范围内有效
session在一个会话范围内有效

作用域注解:@Scope(value=“singleton”)

注意:该注解可以用在@Bean标识的方法中,也可以标识在@Component标识的类中;从而表明获取到的对象为单例或多例模式

生命周期

生命周期过程

  1. 实例化
  2. 依赖注入(给对象设置属性)
  3. bean对象初始化之前的操作(由bean的后置处理器负责)
  4. 初始化:通过bean的init-method属性指定初始化方法
  5. bean对象初始化后的操作(由bean的后置处理器负责)
  6. bean对象的使用
  7. 销毁:通过bean的destroy-method属性来指定销毁的方法
  8. IOC容器的关闭

第一步是实例化,是由于ioc容器管理对象时,是通过工厂和反射获取的,所以会默认使用无参构造。

创建一个User类

public class User {
    private Integer id;
    private String username;
    private String password;
    private Integer age;
    public User() {
        System.out.println("生命周期1:实例化");
    }
    public void setUsername(String username) {
        this.username = username;
    }
    public void setPassword(String password) {
        this.password = password;
    }
    public void setAge(Integer age) {
        this.age = age;
    }
    public void setId(Integer id) {
        System.out.println("生命周期2:依赖注入");
        this.id = id;
    }
    void init(){
        System.out.println("生命周期3:初始化");
    }
    void destroy(){
        System.out.println("生命周期4:销毁");
    }

    @Override
    public String toString() {
        return "User{" +
                "id=" + id +
                ", username='" + username + '\'' +
                ", password='" + password + '\'' +
                ", age=" + age +
                '}';
    }
}

初始化和销毁需要在配置文件中定义

<!--init-method表示bean初始化方法,destory-meethod表示销毁方法-->
<bean id="student" class="com.sentiment.pojo.Student" scope="prototype">
    <property name="sid" value="1842"></property>
    <property name="sname" value="Sentiment"></property>
</bean>

测试

public void test(){
    ConfigurableApplicationContext ioc = new ClassPathXmlApplicationContext("spring-lifecycle.xml");
    User bean = ioc.getBean(User.class);
    System.out.println(bean);
    ioc.close();
}

最后的销毁部分是通过ioc.close()来完成的,而这里的用的是ConfigurableApplicationContext类型,因为ApplicationContext中没有close方法,而ConfigurableApplicationContext是他的子接口其中定义了刷新和关闭的方法。这里用原来的ClassPathXmlApplicationContext也是可以的

在这里插入图片描述

作用域对生命周期的影响

其实当执行第一步的时候就已经,初始化了,而这里的初始化只指单例模式的

在这里插入图片描述

如果换成多例模式即:配置中加上scope="prototype"后

此时运行便没有任何结果,而它的初始化则是在获取bean的时候生成

bean的后置处理器

在bean的声明周期过程中,初始化前后还有两个操作但是在上边并没有体现到

bean对象初始化之前的操作(由bean的后置处理器postProcessBeforeInitialization负责)

bean对象初始化后的操作(由bean的后置处理器postProcessAfterInitialization负责)

bean的后置处理器会在声明周期的初始化前后添加额外的操作,需要实现BeanPostProcessor接口,且配置到IOC容器中,需要注意的是,bean后置处理器不是单独针对某一个bean生效,而是针对IOC容器中所有bean都会执行

创建bean的后置处理器:

package com.sentiment.process;

import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;

public class MyBeanProcessor implements BeanPostProcessor {
    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        System.out.println("初始化之前执行——>postProcessBeforeInitialization");
        return bean;
    }

    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        System.out.println("初始化之后执行——>postProcessAfterInitialization");
        return bean;    }
}

配置到IOC容器中

<bean id="mybeanprocessor" class="com.sentiment.process.MyBeanProcessor"></bean>

此时在执行则会调用bean的后置处理器

在这里插入图片描述

FactroyBean

FactoryBean是Spring提供的一种整合第三方框架的常用机制。和普通的bean不同,配置一个FactoryBean类型的bean,在获取bean的时候得到的并不是class属性中配置的这个类的对象,而是getObject()方法的返回值。通过这种机制,Spring可以帮我们把复杂组件创建的详细过程和繁琐细节都屏蔽起来,只把最简洁的使用界面展示给我们。

将来我们整合Mybatis时,Spring就是通过FactoryBean机制来帮我们创建SqlSessionFactory对象的。

<T> getObject():通过一个对象交给ioc容器来管理

Class<?> getObjectType():设置所提供对象的类型

boolean isSingleton():所提供的对象是否为单例

Factory

import com.sentiment.pojo.User;
import org.springframework.beans.factory.FactoryBean;

public class UserFactoryBean implements FactoryBean<User> {

    @Override
    public User getObject() throws Exception {
        return new User();
    }

    @Override
    public Class<?> getObjectType() {
        return User.class;
    }
}

配置文件

这里并不是返回UserFactoryBean的类对象,而是该类中getObject方法中返回的User()对象,较以往的工厂来说bean工厂省去了找工厂的过程直接找我们需要的对象。

<bean class="com.sentiment.factory.UserFactoryBean"></bean>

测试

@Test
public void factoryTest(){
    ClassPathXmlApplicationContext ioc = new ClassPathXmlApplicationContext("spring-factory.xml");
    User bean = ioc.getBean(User.class);
    System.out.println(bean);
}

输出结果

可以看到这里实例化了User对象,并输出了对应内容

在这里插入图片描述

基于xml的自动装配

根据指定的策略,在IOC容器中匹配某一个bean,自动为指定的bean中所依赖的类类型或接口类型属性赋值

三层架构

自动装配用到了三层架构,先了解了一下

controller

controller又叫web层、控制层、控制器,主要用于用户交互,接收和响应来自用户端的http请求,并且包含一些web功能

常见技术:cookie seesion jsp servlet listener filter

service

service层又叫业务层,主要用来处理逻辑,用于事务处理、日志管理、监控等

dao

dao层又叫持久层、mapper层、respotiry层,主要用于操纵数据库返回用户数组,前篇提到的mybatis就属于该层

技术:jdbc druid mybatis

三层架构的调用关系

controller -> service -> dao -> 操作数据库

数据返回关系

dao操作数据库数据 ->service -> controller

场景模拟

回到xml自动装配,感觉跟动态代理好像。。。

先写一个controller

public class UserController {
    private UserService userService;

    public void setUserService(UserService userService) {
        this.userService = userService;
    }

    public void saveUser() {
        userService.saveUser();
    }
}

controller中定义了UserService并调用了他的saveUser()方法,所以在创建个UserService接口和实现类

UserService接口

public interface UserService {
    void saveUser();
}

实现类

public class UserServiceImpl implements UserService {
    private UserDao userDao;

    public void setUserDao(UserDao userDao) {
        this.userDao = userDao;
    }

    @Override
    public void saveUser() {
        userDao.saveUser();
    }
}

接着又调用了UserDao的saveUser所以继续创建接口和实现类

UserDao接口

public interface UserDao {

    void saveUser();
}

实现类

public class UserDaoImpl implements UserDao {

    @Override
    public void saveUser() {
        System.out.println("保存成功");
    }
}

最后通过配置文件自动装配,通过其中的set方法进行赋值

<bean id="userController" class="com.sentiment.controller.UserController">
    <property name="userService" ref="userService"></property>
</bean>

<bean id="userService" class="com.sentiment.service.iml.UserServiceImpl">
    <property name="userDao" ref="userDao"></property>
</bean>

<bean id="userDao" class="com.sentiment.dao.iml.UserDaoImpl"></bean>

测试

public void test(){
    ClassPathXmlApplicationContext ioc = new ClassPathXmlApplicationContext("spring-autowire.xml");
    UserController bean = ioc.getBean(UserController.class);
    bean.saveUser();
}

byType

在场景模拟中,用到的并不是自动装配,而是通过property标签手动设置了对应的属性值,所以这里就通过autowire标签的byType属性实现自动装配,若设置no或default属性则表示不会自动装配即使用默认值

    <bean id="userController" class="com.sentiment.controller.UserController" autowire="byType">
<!--        <property name="userService" ref="userService"></property>-->
    </bean>

    <bean id="userService" class="com.sentiment.service.iml.UserServiceImpl" autowire="byType">
<!--        <property name="userDao" ref="userDao"></property>-->
    </bean>

    <bean id="userDao" class="com.sentiment.dao.iml.UserDaoImpl"></bean>

此时在运行程序后,会通过byType自动匹配对应的类型属性赋值

但需要注意两个问题:

  1. 若删除userService的bean,则会爆空指针错误,即:当匹配不到对应的bean后,则会不自动装配使用默认值
  2. 由于是根据类型进行匹配所以当设置多个userService类型的bean,则会报错:NoUniqueBeanDefinitionException,也就是这样会匹配到了多个bean,无法执行

byName

根据bean的id名来进行bean的匹配。

上边提到当设置多个userService类型的bean后,则会报错,这时就可以使用byName因为他是根据bean的id进行匹配的,所以不管设置几个同类型bean,只要id唯一就能匹配到(而id是唯一标识,所以常规状态下不会出现id相同情况)

在这里插入图片描述

基于注解管理bean

基于注解

基于注解管理bean和xml配置文件管理一样,注解本身并不能执行,注解本身仅仅只做一个标记,具体的功能是框架检测到注解标记的位置,然后针对这个位置,按照注解标记的功能来执行具体的操作

扫描

spring为了知道程序员在那些地方标记了什么注解就需要通过扫描的方式来进行检测,然后根据注解来进行后续操作(将扫描到的包内的类交给spring容器来保存)
常用注解

  • @Component:将类标识为普通组件
  • @Controller:将类标识为控制层组件
  • @Service:将类标识为业务层组件
  • @Repository:将类标识为持久层组件

注:这四个注解功能一模一样,没有任何区别,都是将对应的类交给spring容器来保存,只不过名字不一样(便于分辨组件的作用)

配置个扫描器,扫描对应的注解

<!--扫描组件-->
<context:component-scan base-package="com.sentiment"></context:component-scan>

测试

public void test(){
    ClassPathXmlApplicationContext ioc = new ClassPathXmlApplicationContext("spring-annotation.xml");
    UserController controller = ioc.getBean(UserController.class);
    System.out.println(controller);
    UserService service = ioc.getBean(UserService.class);
    System.out.println(service);
    UserDao dao = ioc.getBean(UserDao.class);
    System.out.println(dao);
}

扫描组件

在exclude-filter标签中可以设置两个参数:

  • exclude-filter:排除扫描
  • include-filter:包含扫描

其中包含几个属性:

  • type:设置扫描的方式有两个常用值—annotation、assignable

    • annotation:根据注解的类型进行排除
    • assignable:根据类的类型进行排除
  • expression:设置排除的类的全类名

  • use-default-filters:设置是否扫描包下所有的包,默认为true,但若使用include-filte包含扫描时,需设置为flase(包含扫描是指只扫描哪个标签,而若use-default-filters设为true则会默认扫描包下的所有标签,就是去了include-filte的意义)

    <context:component-scan base-package="com.sentiment" use-default-filters="false">
<!--        排除扫描-->
<!--        <context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller"/>-->
<!--        包含扫描-->
        <context:include-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
        <context:include-filter type="assignable" expression="com.sentiment.service.impl.UserServiceImpl"/>
    </context:component-scan>

bean的id

  • id默认为类的小驼峰例:userController
  • 也可自定义id,在标签中设置value值即可,例:@Controller(“controller”)

测试

getbean方法可以通过bean的id获取ioc容器

ClassPathXmlApplicationContext ioc = new ClassPathXmlApplicationContext("spring-annotation.xml");
UserController controller = ioc.getBean("controller",UserController.class);
Syste标识在成员变量上,此时不需要设置成员变量的set方法(直接根据策略在ioc容器中查找对应对象)
标识在set方法上
为当前成员变量赋值的有参构造上m.out.println(controller);

@Autowierd自动装配

标识位置

  • 标识在成员变量上,此时不需要设置成员变量的set方法(直接根据策略在ioc容器中查找对应对象)
  • 标识在set方法上
  • 为当前成员变量赋值的有参构造上

建议标识在成员变量上

@Controller("controller")
public class UserController {
    @Autowired
    private UserService userService;
    public void saveUser(){
        userService.saveUser();
    }
}

自动装配原理

  1. 默认通过byType的方式,在IOC容器中通过类型匹配某个bean为属性赋值
  2. 若有多个类型匹配的bean,此时会自动转换为byName的方式实现自动装配的效果即将要赋值的属性的属性名作为bean的id匹配某个bean为属性赋值
  3. 若byType和byName的方式都无妨实现自动装配,即IOC容器中有多个类型匹配的bean且这些bean的id和要赋值的属性的属性名都不一致,此时抛异常:NouniqueBeanDefinitionException,此时可以在要赋值的属性上,添加一个注解@Qualifier通过该注解的value属性值,指定某个bean的id,将这个bean为属性赋值

例:

② 若此时在下边添加两个bean标签,则默认会通过byName方式获取,因为这里当调用UserService类型是,这里存在两个所以无法匹配。

验证方法也很简单,只需要把id随意修改一下便会报错,出现找不到对应属性的问题

<context:component-scan base-package="com.sentiment"></context:component-scan>
<bean id="userService" class="com.sentiment.service.impl.UserServiceImpl"></bean>
<bean id="userDao" class="com.sentiment.dao.impl.UserDaoImpl"></bean>

③ 如上 边所说id随意修改一下后,便会出现找不到对应的id的问题而且此时也有两个同类型bean,byName和byType就都是失效了,所以就用到了@Qualifier注解

<context:component-scan base-package="com.sentiment"></context:component-scan>
<bean id="Service" class="com.sentiment.service.impl.UserServiceImpl"></bean>
<bean id="Dao" class="com.sentiment.dao.impl.UserDaoImpl"></bean>

测试

通过@Qualifier来指定对应bean的id即可

@Controller("controller")
public class UserController {
    @Autowired
    @Qualifier("service")
    private UserService userService;
    public void saveUser(){
        userService.saveUser();
    }
}

注:在@Autowired注解属性里有个required属性,默认为true,要求必须完成自动装配,可以将required设置为false,此时能装配则装配,不能装配则使用属性默认值

AOP

代理模式

场景模拟

接口类

public interface Calculator {

     int add(int i ,int j);

     int sub(int i ,int j);

     int mul(int i ,int j);

     int div(int i ,int j);
}

实现类

在四个方法中模拟了日志功能,但这些日志并不是add方法的核心业务功能,这就会导致一些问题:

  • 对核心业务功能有千扰。导致程序员在开发核心业务功能时分散了精力
  • 附加功能分散在各个业务功能方法中,不利于统—维护
package com.sentiment.proxy;

public class CalculatorImpl implements Calculator {

    @Override
    public int add(int i, int j) {
        System.out.println("日志,方法:add,参数"+i+","+j);
        int result=i+j;
        System.out.println("方法内部,result:"+result);
        System.out.println("日志,方法:add,结果"+i+","+j);
        return result;
    }

    @Override
    public int sub(int i, int j) {
        System.out.println("日志,方法:sub,参数"+i+","+j);
        int result=i-j;
        System.out.println("方法内部,result:"+result);
        System.out.println("日志,方法:sub,结果"+i+","+j);
        return result;
    }

    @Override
    public int mul(int i, int j) {
        System.out.println("日志,方法:mul,参数"+i+","+j);
        int result=i*j;
        System.out.println("方法内部,result:"+result);
        System.out.println("日志,方法:mul,结果"+i+","+j);
        return result;
    }

    @Override
    public int div(int i, int j) {
        System.out.println("日志,方法:div,参数"+i+","+j);
        int result=i/j;
        System.out.println("方法内部,result:"+result);
        System.out.println("日志,方法:div,结果"+i+","+j);
        return result;
    }
}

静态代理

通过上例就引出了代理模式,即:各自的方法由自己来实现就好,附加功能由代理来实现

将CalculatorImpl中的日志功能部分除去,加到代理类中

代理类

package com.sentiment.proxy;

public class CalculatorStiaticProxy implements Calculator {
    private CalculatorImpl target;

    public CalculatorStiaticProxy(CalculatorImpl target) {
        this.target = target;
    }

    @Override
    public int add(int i, int j) {
        System.out.println("日志,方法:add,参数"+i+","+j);
        int result = target.add(i,j);
        System.out.println("日志,方法:add,结果"+i+","+j);
        return result;
    }

    @Override
    public int sub(int i, int j) {
        System.out.println("日志,方法:sub,参数"+i+","+j);
        int result = target.sub(i,j);
        System.out.println("日志,方法:sub,结果"+i+","+j);
        return result;
    }

    @Override
    public int mul(int i, int j) {
        System.out.println("日志,方法:mul,参数"+i+","+j);
        int result = target.sub(i,j);
        System.out.println("日志,方法:mul,结果"+i+","+j);
        return result;
    }

    @Override
    public int div(int i, int j) {
        System.out.println("日志,方法:div,参数"+i+","+j);
        int result = target.sub(i,j);
        System.out.println("日志,方法:div,结果"+i+","+j);
        return result;
    }
}

测试类

public void testProxy(){
    CalculatorStiaticProxy proxy = new CalculatorStiaticProxy(new CalculatorImpl());
    proxy.add(1,2);
}

动态代理

第三遍回顾动态代理了,之前有一篇文章专门分析过可以参考一下: [Java基础]—动态代理_Sentiment.的博客-CSDN博客

静态代理代理类和业务类都需要实现同样的接口,会造成代码的重复并且静态代理缺乏一定的灵活性,因此引出了动态代理。

动态代理主要涉及java.lang.reflect包下的Proxy类和InvocationHandler接口。

先看下java.lang.reflect.Proxy类,其中有个newProxyInstance就是实现动态代理的方法

package java.lang.reflect;

import java.lang.reflect.InvocationHandler;

public class Proxy implements java.io.Serializable {

	public static Object newProxyInstance(ClassLoader loader,
                                          	Class<?>[] interfaces,
                                          	InvocationHandler h)
      						
        ..........

}
  • classLoader Loader:指定加载动态生成的代理类的类加载器
  • class[] interfaces:获取目标对象实现的所有接口的class对象的数组
  • invocationHandler h:设置代理类中的抽象方法如何重写

重点在invocationHandler h,该类只有一个方法

public interface InvocationHandler {

    public Object invoke(Object proxy, Method method, Object[] args)
        throws Throwable;
}

  • proxy 代理对象
  • method 要调用的代理对象方法
  • args 要调用方法的参数

代理类

package com.sentiment.proxy;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.Arrays;

public class ProxyFactory {
    private Object target;

    public ProxyFactory(Object target) {
        this.target = target;
    }

    public Object getProxy(){
        ClassLoader classLoader = this.getClass().getClassLoader();
        Class<?>[] interfaces = target.getClass().getInterfaces();
        InvocationHandler h = new InvocationHandler() {
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                System.out.println("日志参数:" + Arrays.toString(args));
                Object result = method.invoke(target, args);
                System.out.println("日志结果:" + result);
                return result;
            }
        };
        return Proxy.newProxyInstance(classLoader,interfaces,h);
    }
}

测试

import com.sentiment.proxy.Calculator;
import com.sentiment.proxy.CalculatorImpl;
import com.sentiment.proxy.CalculatorStiaticProxy;
import com.sentiment.proxy.ProxyFactory;
import org.junit.Test;

public class ProxyTest {
    @Test
    public void dynamicProxy(){
        ProxyFactory proxyFactory = new ProxyFactory(new CalculatorImpl());
        Calculator proxy = (Calculator) proxyFactory.getProxy();
        proxy.add(1,2);
    }
}

流程

在这里插入图片描述

AOP概念

AOP(aspect oriented programming)是一种设计思想,是软件设计领域中的面向切面编程,他是面向对象编程的一种补充和完善,它通过预编译的方式和运行期动态代理的方式实现在不修改源代码的情况下给程序动态统一添加额外功能的一种技术

相关术语

横切关注点(日志):

从每个方法中抽取出来的同一类非核心业务,同一个项目中,我们可以使用多个横切关注点对相关方法进行多个不同方面的增强

通知 :

每一个横切关注点上要做的事情都需要写一个方法来实现,这样的方法叫通知方法

切面:

用于封装横切关注点的类(每个横切关注点都表示为一个通知方法)

注:我们要把横切关注点封装到切面中,而在这个切面中每一个横切关注点都表示一个通知方法

目标

被代理的目标对象(加减乘除功能)

连接点:

表示横切关注点抽出来的位置

切入点:

定位连接点的方式

每一个横切关注点也就是非核心业务方法都会被抽出到切面中,而切面中的每个横切关注点表示一个通知方法,通过切入点就是将通知方法放到连接点处

通知分类

  • 前置通知:在被代理的目标方法前执行
  • 返回通知:在被代理的目标方法成功结束后执行
  • 异常通知:在被代理的目标方法异常结束后执行
  • 后置通知:在被代理的目标方法最终结束后执行
  • 环绕通知:目标方法的前后都可以执行某些代码,用于控制目标方法

AOP作用

简化代码:把方法中固定位置的重复代码抽取出来,让抽取的方法更专注于自己的核心功能,提高内聚性

代码增强:把特定的功能封装到切面类中,看哪里有需要,就往上套,被套用了切面逻辑的方法就被切面给增强了

**注:**AOP依赖于IOC而存在

基于注解的AOP

前置通知

配置文件

  • 切面类和目标类都需要交给IOC容器管理
  • 切面类必须通过@Aspect注解标识为一个切面在spring的配置文件中设置<aop:aspectj-autoproxy/>开启基于注解的AOP
<context:component-scan base-package="com.sentiment.aop"></context:component-scan>
<!--开启基于注解的AOP-->
<aop:aspectj-autoproxy/>

测试类

@Component
@Aspect
public class LogAspect {
    @Before("execution(public int com.sentiment.aop.CalculatorImpl.add(int ,int))")
    public void beforeAdvicedMethdo(){
        System.out.println("前置通知");
    }
}

切入点表达式

  • bean表达式

    bean(bean的id)//没有引号
    
  • within表达式

    //只拦截具体包下的具体类
    within(com.Sentiment.service.User)
    //拦截具体包下的所有类
    within(com.sentiment.service.*)
    //拦截具体包下的所有包类
    within(com.sentiment.service..*)
    //拦截com.任意包.service包下的所有包类
    within(com.*.service..*)
    
  • execution表达式

语法:execution(返回值类型 包名.类名.方法名(参数列表))

//拦截返回值任意的具体方法
execution(* com.sentiment.service.UserServiceImpl.addUser())
//拦截返回值任意,参数列表任意,具体包service所有子包与子类的所有方法
execution(* com.sentiment.service..*.*(..))
//拦截返回值任意,参数列表为两个int类型,具体包service所有子包与子类的add方法
execution(* com.sentiment.service..*.add(int,int))
  • @annoation表达式

定义一个注解类,在需要扩展的方法上加注解

//对被有该注解标明的方法有效(里面填定义注解的位置)
@annotation(com.sentiment.anno.注解)

简化操作

在上边定义了一个add方法的前置通知,但是此时"减乘除"都没有设置,若使用刚才的方式则需再定义三个,代码重复量过高,所以就可以用表达式进行简化

第一个表示任意的访问修饰符和返回值类型

第二个表示类中任意的方法

… 表示任意的参数列表

@Before("execution(* com.sentiment.aop.CalculatorImpl.*(..))")

除此外类的地方也可以用*,表示包下所有的类

重用切入点

此时只定义了前置操作,若在定义后置通知,异常通知等,就会导致execution(* com.sentiment.aop.CalculatorImpl.*(..))出现反复重写问题,所以又引入了一个注解@Pointcut

此时After后边只需要填写对应的pointCut方法即可

@Pointcut("execution(* com.sentiment.aop.CalculatorImpl.*(..))")
public void pointCut(){}

@After("pointCut()")
public void after(){
    System.out.println("后置通知");
}

获取连接点信息

在通知方法的参数位置,设置JoinPoint类型的参数,就可以获取连接点对应方法的信息

测试

@Before("pointCut()")
public void beforeAdvicedMethdo(JoinPoint joinPoint){
    //获取连接点对应方法的签名信息
    Signature signature = joinPoint.getSignature();
    //获取连接点对应方法的参数
    Object[] args = joinPoint.getArgs();
    System.out.println("前置通知,方法:"+signature.getName()+"参数:"+ Arrays.toString(args));
}

结果

前置通知,方法:mul参数:[1, 2]
方法内部,result:2

常见通知方式

先用try语句理解一下各个通知的接入点

try {
    //前置通知@Before
    目标方法执行语句..........
	//返回通知@AfterReturning
   }catch(exception e){
    //异常通知@AfterThrowing
} finally{
    //后置通知@After
}

后置通知

@After

接入点相当于finally位置,无论执行是否异常都会执行

在这里插入图片描述

返回通知

@AfterReturning

接入点在目标方法语句后,若出现异常则不会执行,另外该注释中有一个参数returning,作为为目标函数的返回结果

@AfterReturning(value = "pointCut()",returning = "result")
public void afterReturning(JoinPoint joinPoint,Object result){
    Signature signature = joinPoint.getSignature();
    System.out.println("返回通知,方法:"+signature.getName()+",结果:"+result);
}

异常通知

@AfterThrowing

接入点在catch语句中,若出现异常则会执行,另外该注释中有一个参数throwing,作为为目标函数的异常结果

@AfterThrowing(value = "pointCut()",throwing = "e")
public void afterThrowing(JoinPoint joinPoint,Throwable e){
    Signature signature = joinPoint.getSignature();
    System.out.println("异常通知,方法:"+signature.getName()+",异常:"+e);
}

环绕通知

@Around("pointCut()")
public Object around(ProceedingJoinPoint joinPoint){
    Object result=null;
    try {
        System.out.println("环绕通知-->前置通知");
         result = joinPoint.proceed();
        System.out.println("环绕通知-->后置通知");
    }catch (Throwable throwable){
        System.out.println("环绕通知-->异常通知");
    }finally {
        System.out.println("环绕通知-->后置通知");
    }
    return result;
}

切面优先级

再添加一个AOP类,加上前置通知

public class OrderAspect {
    @Before("com.sentiment.aop.LogAspect.pointCut()")
    public void before(){
        System.out.println("前置通知:Order");
    }
}

此时在程序执行后发现,该通知的执行在LogAspect类的前置通知后,所有就引入了一个注解@Order来设置切面执行的优先级

在这里插入图片描述

此时发现Order的前置通知被执行, @Order填写一个int值即可,值越小优先级越高,而默认值为int类型的最大值2147483647,所以上边值随便给个比这个小的即可

在这里插入图片描述

基于xml的AOP

将前边的AOP注解都去掉后,可以基于xml实现AOP管理

配置文件

    <context:component-scan base-package="com.sentiment.aop.xml"></context:component-scan>

    <aop:config>
        <aop:pointcut id="pointCut" expression="execution(* com.sentiment.aop.xml.CalculatorImpl.*(..))"/>
        <aop:aspect ref="logAspect" >
            <aop:before method="beforeAdvicedMethdo" pointcut-ref="pointCut"></aop:before>
            <aop:after method="after" pointcut-ref="pointCut"></aop:after>
            <aop:after-returning method="afterReturning" returning="result" pointcut-ref="pointCut"></aop:after-returning>
            <aop:after-throwing method="afterThrowing" throwing="e" pointcut-ref="pointCut"></aop:after-throwing>
        </aop:aspect>
        
        <!-- 同样可以通过order修改优先级-->
        <aop:aspect ref="orderAspect" order="1">
            <aop:before method="before" pointcut-ref="pointCut"></aop:before>
        </aop:aspect>
    </aop:config>

测试

@Test
public void test(){
    ClassPathXmlApplicationContext ioc = new ClassPathXmlApplicationContext("aop-xml.xml");
    Calculator bean = ioc.getBean(Calculator.class);
    bean.add(1,0);
}

整合Mybatis

Mybatis-spring

官方文档

MyBatis-Spring 会帮助你将 MyBatis 代码无缝地整合到 Spring 中。它将允许 MyBatis 参与到 Spring 的事务管理之中,创建映射器 mapper 和 SqlSession 并注入到 bean 中,以及将 Mybatis 的异常转换为 Spring 的 DataAccessException。 最终,可以做到应用代码不依赖于 MyBatis,Spring 或 MyBatis-Spring。

版本要求:

MyBatis-SpringMyBatisSpring FrameworkSpring BatchJava
2.03.5+5.0+4.0+Java 8+
1.33.4+3.2.2+2.1+Java 6+

依赖

如果mybatis是3.4以下就用1.3版本

<dependency>
  <groupId>org.mybatis</groupId>
  <artifactId>mybatis-spring</artifactId>
  <version>2.0.7</version>
</dependency>

SqlSessionTemplate

Usermapper实现类

由于Spring通过bean来管理,所以这里的sqlSeesionFactory的创建等都集成在了bean中,只留下的最后的getMapper,用类来实现

package com.sentiment.mapper;

import com.sentiment.pojo.User;
import org.mybatis.spring.SqlSessionTemplate;

import java.util.List;

public class UserMapperImpl implements UserMapper{
    private SqlSessionTemplate sqlSession;

    public void setSqlSession(SqlSessionTemplate sqlSession) {
        this.sqlSession = sqlSession;
    }

    @Override
    public List<User> selectUser() {
        UserMapper mapper = sqlSession.getMapper(UserMapper.class);
        return mapper.selectUser();
    }
}

配置文件

下边主要是通过管理bean的方式创建sqlSeesion

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">


    <!--数据源-->
    <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
        <property name="driverClassName" value="com.mysql.jdbc.Driver"/>
        <property name="url" value="jdbc:mysql://localhost:3306/Mybatis"></property>
        <property name="username" value="root"></property>
        <property name="password" value="123456"></property>
    </bean>
    
    <!--sqlSeesionFactory: 使用 SqlSessionFactoryBean来创建 SqlSessionFactory-->
    <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
        <property name="dataSource" ref="dataSource" />
        <!--绑定Mybatis配置文件-->
        <property name="configLocation" value="classpath:mybatis-config.xml"></property>
        <property name="mapperLocations" value="classpath:com/sentiment/mapper/*.xml"></property>
    </bean>
    
    <!--SqlSeesionTemplate:相当于Mybatis中的sqlSession-->
    <bean id="sqlSeesion" class="org.mybatis.spring.SqlSessionTemplate">
        <constructor-arg ref="sqlSessionFactory"></constructor-arg>
    </bean>

    <!--给sqlSeesion属性赋值,并通过mapper获取我们要执行的方法-->
    <bean id="userMapper" class="com.sentiment.mapper.UserMapperImpl">
        <property name="sqlSession" ref="sqlSeesion"></property>
    </bean>
</beans>

总体流程:

查询语句首先要定义对应的类(User.java)

之后就要定义实现查询语句的接口(UserMapper.java)

有了接口后就要有对应实现语句的配置文件(UserMapper.xml)

之后是总的mybatis配置文件(而由于结合spring,所以用了spring配置文件代替—spring-mybatis.xml)

最后添加一个接口实现类来获取配置文件中创建的sqlSeesion对象并实现对应的查询方法(UserMapperImpl.java)

SqlSessionDaoSupport

除SqlSeesionTemplate外,官方还给出了另一种方法—SqlSessionDaoSupport,这种方法的本质其实还是SqlSeesionTemplate只是对他进行了一些简化,所以使用起来更方便一些。

使用该种方式配置文件中就不在需要获取sqlSeesion,只需要获取到SQLSessionFactory处即可。

public class UserMapperImpl2 extends SqlSessionDaoSupport implements UserMapper {
    @Override
    public List<User> selectUser() {
        SqlSession sqlSession = getSqlSession();
        UserMapper mapper = sqlSession.getMapper(UserMapper.class);
        return mapper.selectUser();
    }
}

执行语句也可精简成一句

return getSqlSession().getMapper(UserMapperImpl2.class).selectUser();

事务管理

可以把一系列要执行的操作称为事务,而事务管理就是管理这些操作要么完全执行,要么完全不执行

在UsermapperImpl.java实现添加和删除方法

@Override
public List<User> selectUser() {
    User user = new User(5, "Sentiment", "123456", 20, "男", "123@qq.com");
    UserMapper mapper = getSqlSession().getMapper(UserMapper.class);
    mapper.insertUser(user);
    mapper.deleteUser(5);
    return getSqlSession().getMapper(UserMapper.class).selectUser();
}

@Override
public int insertUser(User user) {
    return getSqlSession().getMapper(UserMapper.class).insertUser(user);
}

@Override
public int deleteUser(int id) {
    return getSqlSession().getMapper(UserMapper.class).deleteUser(id);
}

并且将mapper的配置文件中delete特意写错

在这里插入图片描述

此时执行后发现报错,但查询表后发现insert方法已经被执行了,所以就引入了声明式事务来确保操作要么完全执行,要么完全不执行。

声明式事务

mybatis-spring –

此时添加事务声明配置文件,之后在执行当遇到错误时,就会直接终止完全不执行

<!--配置声明式事务-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
    <property name="dataSource" ref="dataSource"/>
</bean>

<!--结合AOP使用-->
<tx:advice id="txAdvice" transaction-manager="transactionManager">
    <tx:attributes>
        <tx:method name="*" propagation="REQUIRED"/>
    </tx:attributes>
</tx:advice>

<!--配置事务切入-->
<aop:config>
    <aop:pointcut id="PointCut" expression="execution(* com.sentiment.mapper.UserMapper.*(..))"/>
    <aop:advisor advice-ref="txAdvice" pointcut-ref="PointCut"/>
</aop:config>

相关文章:

  • OpenGL入门教程
  • 嵌入式系统开发笔记91:认识ARM微控制器架构
  • gnn explainer笔记
  • Cadence Allegro 在Gerber光绘中生成板卡层叠结构文件
  • 达梦数据物理逻辑备份还原
  • ssm项目布置流程
  • JavaScript简介与快速体验
  • ExecutorService、Callable、Future实现有返回结果的多线程原理解析
  • java 字节流写入文件内容实现换行
  • Greenplum数据库数据分片策略Hash分布——执行器行为
  • java题3
  • 初探Prometheus+grafana
  • Axios入门
  • 数据库(mysql)主从复制与读写分离
  • Windbg 命令 (四)
  • [原]深入对比数据科学工具箱:Python和R 非结构化数据的结构化
  • Android开源项目规范总结
  • IIS 10 PHP CGI 设置 PHP_INI_SCAN_DIR
  • Java到底能干嘛?
  • KMP算法及优化
  • Linux快速配置 VIM 实现语法高亮 补全 缩进等功能
  • miniui datagrid 的客户端分页解决方案 - CS结合
  • NLPIR语义挖掘平台推动行业大数据应用服务
  • Octave 入门
  • Python语法速览与机器学习开发环境搭建
  • Redis学习笔记 - pipline(流水线、管道)
  • Redux系列x:源码分析
  • Spring技术内幕笔记(2):Spring MVC 与 Web
  • vue 配置sass、scss全局变量
  • Vue全家桶实现一个Web App
  • Web标准制定过程
  • 动态魔术使用DBMS_SQL
  • 基于Javascript, Springboot的管理系统报表查询页面代码设计
  • 开源SQL-on-Hadoop系统一览
  • 码农张的Bug人生 - 初来乍到
  • 微信开放平台全网发布【失败】的几点排查方法
  • kubernetes资源对象--ingress
  • 微龛半导体获数千万Pre-A轮融资,投资方为国中创投 ...
  • 智能情侣枕Pillow Talk,倾听彼此的心跳
  • #LLM入门|Prompt#1.8_聊天机器人_Chatbot
  • #传输# #传输数据判断#
  • #我与Java虚拟机的故事#连载18:JAVA成长之路
  • $NOIp2018$劝退记
  • (Redis使用系列) Springboot 使用redis的List数据结构实现简单的排队功能场景 九
  • (Redis使用系列) Springboot 使用redis实现接口Api限流 十
  • (Redis使用系列) SpringBoot 中对应2.0.x版本的Redis配置 一
  • (zt)基于Facebook和Flash平台的应用架构解析
  • (办公)springboot配置aop处理请求.
  • (第8天)保姆级 PL/SQL Developer 安装与配置
  • (六)c52学习之旅-独立按键
  • (论文阅读40-45)图像描述1
  • (原創) 如何解决make kernel时『clock skew detected』的warning? (OS) (Linux)
  • (转)【Hibernate总结系列】使用举例
  • .net core使用ef 6
  • .NET Micro Framework初体验(二)