回头来看 spring5

spring 是一个轻量级的非侵入式的开源框架
它的的两大特点,控制反转(IOC) 和 面向切面编程(AOP)

现在spring已经在 java后端这一块形成大一统了。

IOC理论推导

引入相关依赖,因为spring模块较多,我们引入一个较大的springWebMvc,这里maven会自动帮我们导入需要的jar 包

  1. <!-- https://mvnrepository.com/artifact/org.springframework/spring-webmvc -->
  2. <dependency>
  3. <groupId>org.springframework</groupId>
  4. <artifactId>spring-webmvc</artifactId>
  5. <version>5.2.1.RELEASE</version>
  6. </dependency>

首先我们创建一个 UserDao接口,这个接口很简单,他就只有一个getUser的方法

public interface UserDao {
    void getUser();
}

然后呢?我们再去搞一个实现类,就叫UserDaoImpl这个吧

public class UserDaoImpl implements  UserDao {

    public void getUser() {
        System.out.println("获取用户数据");
    }
}

同样的 我们需要再写一个 UserService 和 UserServiceImpl
UserService:

public interface UserService {
    void getUser();
}

UserServiceImpl:

public class UserServiceImpl implements UserService {

    private UserDao userDao = new UserDaoMysqlImpl();

    public void getUser() {
        userDao.getUser();
    }
}

假如前端现在需要从 UserController 也就是我们的后台接口来获取一个 用户信息
那我们用main 方法来替代

public class MyTest {

    public static void main(String[] args) {

        UserServiceImpl userService = new UserServiceImpl();

        userService.getUser();

    }
}

那这样我们是不是就能获取到用户信息了呢?
答案是当然的。但是,这种方法有什么问题呢?

我们是不是将UserDao 的代码写死了? 对的,如果这个时候我们需要扩展了,假如说,我们需要从mysql数据库来获取数据了
我们还通过这种方法来写,那当然没问题,但是如果再多一种 Oracle的获取方式呢,再多一种 sqlserver的获取方式呢?
我们总不能每次都将我们的代码 在 service 层修改吧?

那么现在我们 给 Service 层新增一个SetUserDao 方法,当然你也可以在构造方法中传入UserDao这都是没问题的

修改后的 UserService

public class UserServiceImpl implements UserService {

    private UserDao userDao = new UserDaoMysqlImpl();

    //利用set 进行动态注入值
    public void setUserDao(UserDao userDao) {
        this.userDao = userDao;
    }

    public void getUser() {
        userDao.getUser();
    }
}

这个时候,其实我们如果需要对获取用户数据源的方法进行修改的话
我们是不是只需要调用在 main方法中 调用set方法就行了?
修改后:

public class MyTest {

    public static void main(String[] args) {

        UserServiceImpl userService = new UserServiceImpl();

        userService.setUserDao(new UserDaoMysqlImpl());
        userService.getUser();

    }
}

其实这样我们的程序已经发生了革命性的变化,那就是原来程序的控制权是在我们的service手上,也就是我们程序员手上。

代码修改后呢?

其实控制器已经从我们程序员手上,跑到了用户手上?为什么这么说呢?
因为main方法(现在充当我们的controller)来接受参数,我们在controller 里就可以修改 Dao 的实现类了,而我们的 service 的代码并没有发生改变

这种思想从本质上解决了问题,我们程序员不需要再去管创建什么对象了,系统的耦合性大大降低,我们可以更加专注于业务上,这就是控制反转ioc 的原型

spring hello (依赖注入)

Hello.java

public class Hello {

    private String str;

    public String getStr() {
        return str;
    }

    public void setStr(String str) {
        this.str = str;
    }

    @Override
    public String toString() {
        return "Hello{" +
                "str='" + str + '\'' +
                '}';
    }
}

application.xml

<?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">


    <!--  使用 spring 来创建 对象  -->
    <bean id="hello" class="cn.edu.zzuli.pojo.Hello">
        <property name="str" value="hello spring"/>
    </bean>
</beans>

main

public class MyTest {
    public static void main(String[] args) {
        //获取 spring 的上下文对象
        ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");

        //那我们现在对象都被 spring 管理了,我们如果需要,直接从里边取出来就可以了
        Object hello = context.getBean("hello");
        System.out.println(hello);

    }
}

那么思考一下,Hello 这个对象是谁创建的?
毫无疑问,spring帮我们创建的,这也是控制反转
控制: spring 来控制这些对象,帮我创建好
反转: 我们从主动创建对象,变成了被动的接受对象
依赖注入: 通过set方法 为对象赋值

关于 spring 的配置文件

常用标签

bean 标签的属性
- id: 唯一标识
- class: 全类名
- name: 别名 可以有多个,可以空格分隔,也可以,分割

我们可以通过 set方式为属性赋值
也可以通过构造器的方式为属性赋值:

<?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="user" class="cn.edu.zzuli.pojo.User">-->
<!--        <property name="name" value="halo"/>-->
<!--    </bean>-->


    <bean id="user" class="cn.edu.zzuli.pojo.User" name="user2,user3 user4">
        <constructor-arg name="name" value="hello"/>
    </bean>

    <alias name="user" alias="getUserByConstructor"/>

</beans>

alias 是设置别名,与bean标签的name属性相同(spring:你可以不用,但我不能没有)

import:

当我们项目较大的时候,多人编写多个配置文件,但是我们在创建ApplicationContext对象的时候,只获取一个xml配置文件

ApplicationContext context = new ClassPathXmlApplicationContext("application.xml");

但是我们需要在这个配置文件里,引入其他配置文件,这个时候就可以使用到 import 标签了

<?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">

    <!--  引入其他 xml 文件  -->
    <import resource="beans.xml"/>
</beans>

依赖注入(DI)的复杂类型

Student.java

@Data
public class Student {

    private String name;
    private Address address;
    private String[] books;
    private List<String> hobbies;
    private Map<String, String> card;
    private Set<String> games;
    private String wife;
    private Properties info;

}

Address.java

@Data
public class Address {
    private String address;
}

appliaction.xml

<?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="student" class="cn.edu.zzuli.pojo.Student">
        <property name="name" value="hello"/>
        <property name="address" ref="address"/>
        <property name="books">
            <array>
                <value>java</value>
                <value>spring</value>
                <value>mybatis</value>
            </array>
        </property>

        <property name="hobbies">
            <list>
                <value>读书</value>
                <value>看报</value>
                <value>睡觉</value>
            </list>
        </property>

        <property name="card">
            <map>
                <entry key="stuId" value="541713140206"/>
                <entry key="id" value="410XXXXXXXXXXXXXXXXX"/>
            </map>
        </property>

        <property name="games">
            <set>
                <value>GTA</value>
                <value>DNF</value>
                <value>刺客信条</value>
            </set>
        </property>

        <property name="wife">
            <null/>
        </property>

        <property name="info">
            <props>
                <prop key="driver">com.jdbc.mysql.driver</prop>
            </props>
        </property>
    </bean>

    <bean id="address" class="cn.edu.zzuli.pojo.Address">
        <property name="address" value="zzuli"/>
    </bean>
</beans>

命名空间 c 和 p

c: constructor 构造器注入扩展
p: params 参数注入扩展

具体使用如下
记得引用 xml 的扩展

       xmlns:p="http://www.springframework.org/schema/p"
       xmlns:c="http://www.springframework.org/schema/c"
<?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 http://www.springframework.org/schema/beans/spring-beans.xsd">

    <bean id="user" class="cn.edu.zzuli.pojo.User" p:age="18" p:name="hello"/>

    <bean id="user2" class="cn.edu.zzuli.pojo.User" c:age="20" c:name="halo"/>

</beans>

bean 的作用域

  • singleton : 单例,在xml中配置的 bean 全局只有共享一个(唯一)。spring 默认就是单例模式
  • prototype :原型模式,每获取一次对象,就创建一个对象
  • request,session,application,websocket 对应web的作用域

我们可以在 bean 的标签里通过 scope 属性修改作用域

    <!-- 默认的单例模式   -->
    <bean id="user" class="cn.edu.zzuli.pojo.User" p:age="18" p:name="hello"/>

    <!--  原型模式   -->
    <bean id="user2" class="cn.edu.zzuli.pojo.User" c:age="20" c:name="halo" scope="prototype"/>

我们可以在代码中进行测试


    @Test
    public void testBeanScope() {
        ApplicationContext context = new ClassPathXmlApplicationContext("application.xml");
        User user = (User) context.getBean("user");
        User user1 = (User) context.getBean("user");
        System.out.println(user == user1);

        User user2 = (User) context.getBean("user2");
        User user3 = (User) context.getBean("user2");
        System.out.println(user2 == user3);
    }

可以看到输出结果

true
false

bean 的自动装配

之前,我们注册bean 都是在xml属性中,手动装配。
但是在 spring 中spring可以通过上下文寻找,自动装配

可以通过 @Autowired 来自动装配

也可在 xml 文件中的 bean 标签的属性 autowire 开启自动注入

  • byName 自动在上下文寻找属性名对应的值,没有的话报空指针异常
  • byType 自动在上下文寻找对象类型对应的值,但是在配置文件中,只能有一个本类型的对象

在配置文件中加入标签开启注解

<?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
        https://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context
        https://www.springframework.org/schema/context/spring-context.xsd">

    <context:annotation-config/>

    <bean id="address" class="cn.edu.zzuli.pojo.Address" p:address="zzuli"/>
    <bean id="user" class="cn.edu.zzuli.pojo.User" p:name="helllo"/>

</beans>

User.java

@Data
public class User {

    private String name;

    @Autowired
    private Address address;
}

Address.java

@Data
public class Address {
    private String address;
}

这样直接输出User 会发现,spring 已经帮我们自动注入了 Address 对象

@AutoWired 默认采用的是 byType方式,如果有多个同类型的对象,那么采取 byName的方式

可以测试一下。

    <bean id="address" class="cn.edu.zzuli.pojo.Address" p:address="zzuli"/>
    <bean id="address1" class="cn.edu.zzuli.pojo.Address" p:address="zzuli-1111"/>

    <bean id="user" class="cn.edu.zzuli.pojo.User" p:name="helllo"/>

那么这个时候我们想获取第二个 address1对象该怎么办呢?

我们就要用到 @Qualifier 了,它的 value 值即我们要寻找bean的名字(id)

    @Autowired
    @Qualifier(value = "address1")
    private Address address;

另外,如果想实现自动装配,其实java也为我们提供了一个注解
@Resource
默认情况下他去 byName ,如果没有对应的,他会去byType

spring 的注解开发

在 spring 4 之后,要使用注解,必须要导入 aop这个依赖。
而且,在java8出来后,注解开发,越来越流行了,spring也从配置文件的方式,渐渐转为注解的方式,在 springboot 中你可以明显的享受到注解的快乐

@Data
@Component
public class User {
    @Value("helllo")
    private String name ;

    @Value("20")
    private Integer age;
}

@Component 注解,意味着,这个类,你会被 IOC容器所管理

<context:component-scan base-package="cn.edu.zzuli.pojo"/>

我们可以通过配置文件来 对整个包进行扫描,扫描后,我们在对应的类上加上@Component 注解,这个类就会被spring的 IOC 容器所管理。

同样
@Scope(“singleton”) 可以来进行作用域的修改

@Data
@Component
@Scope("singleton")
public class User {
}

使用Java类的方式,配置spring

@Configuration标记的类叫做配置类,它相当于xml文件的beans标签,这个类里面需要注册bean,相当于在beans中注册bean。

@Configuration
public class AppConfig {

}

bean注册是有两种方式
第一种,
使用@Bean标记一个返回javabean的方法,返回的javabean作为bean注册到beans中。

@Configuration
public class AppConfig {

    @Bean
    public Person getPerson() {
        return new Person();
    }

}

但是要注意,这个时候,spring为我们注册的bean 的名字是方法名
如果你要让他的名字为 person 的话
你需要让注解的name 属性为 person

@Bean(name = "person")

第二种,使用@Component+@ComponentScan的方式:@Component标记在要注册bean的类上,@ComponentScan标记在配置类上用于扫描组件。

AppConfig.java :

@Configuration
@ComponentScan("cn.edu.zzuli.pojo")
public class AppConfig {

    //采用第二种方式,就不需要自己再手动注册Bean了
    // @Bean(name = "person")
    // public Person getPerson() {
    //     return new Person();
    // }

}

Person.java

@Data
@Component
public class Person {
    @Value("helllo")
    private String name ;

    @Value("20")
    private Integer age;
}

Ps:( 如果你两个方法一起使用,并且没有在@Bean注解里指定名字
那么 spring 会注册两个bean。一个叫 person 一个叫 getPerson )


如果这个时候我们还需要采用多个配置文件的话,那么我们可以使用@import注解在我们的配置类上引用 其他配置文件的.class 属性

@Import(App2Config.class)

@Configuration
@ComponentScan("cn.edu.zzuli.pojo")
@Import(App2Config.class)
public class AppConfig {

}

AOP

在 AOP 之前,我们可以先来看看静态代理和动态代理

静态代理

静态代理需要抽象出真实角色,代理角色,还要有访问代理角色

具体例子

  • 房东:出租房 (真实角色(被代理角色))
  • 中介:帮你租房,帮房东出租房 (代理角色)
  • 你:租房 (访问代理角色)

具体代码:
租房接口:(抽象出的业务行为)

public interface Rent {

    void Rent();
}

房东:

public class Host implements Rent {

    public void Rent() {
        System.out.println("房东出租房子");
    }

}

中介:

public class Proxy implements Rent {

    //被代理的房东
    private Host host;

    public Proxy() {}

    public Proxy(Host host) {
        this.host = host;
    }

    public void Rent() {
        sign();
        visitHost();
        host.Rent();
    }

    //中介的增强功能
    public void sign() {
        System.out.println("签合同");
    }

    //中介的增强功能
    public void visitHost() {
        System.out.println("看房");
    }
}

你:

public class Cilent {

    public static void main(String[] args) {
        Proxy proxy = new Proxy(new Host());
        //你找中介租房
        proxy.Rent();
    }
}

静态代理的优点:

  • 可以使真实角色的操作更存粹
  • 通过代理角色,可以做一些公共业务基础上的功能增强,也实现了业务的分工。
  • 公共业务发生扩展时方便集中管理。

🌰

  • 假如有多个房东,我们需要添加房屋维修的方法,这个时候我们只需要在代理对象,也就是中介这个对象里添加维修房屋的方法就可以了,不需要再每一个房东里去加
  • 假如我们需要在真实对象的每个方法中都加入日志功能,那么我们在代理类中实现就好,虽然代码增加了,但是解耦了,而且保证了原有业务代码不变。

记住最最最最重要的一点就是,我们尽量不改变原有的代码,而是再代理类里边坐增强。

但是它也有缺点

  • 一个真实角色就会产生一个代理角色,代码里翻倍,开发效率变低

而解决这个缺点的就是动态代理

动态代理

动态代理和静态代理角色一样,但是它的代理类是动态生成的,不是我们一开始就写好的。
动态代理分为两大类,基于接口的动态代理,基于类的动态代理

  • 基于接口的动态代理—jdk 的动态代理
  • 基于类的动态代理: cglib (不用在意,我们一般都用jdk的)

需要了解两个类,

Proxy: 代理类
newProxyInstance proxy 的静态方法,用来获取一个Proxy(代理类),
参数分别为,(类加载器,实现的接口,InvocationHandler)

InvocationHandler:
调用处理程序,用来动态生成代理类
其中有一个invoke方法,当代理类调用方法时,处理程序类会帮我们委派到 invoke方法里

我们现在要为 用户业务逻辑接口 的每一个方法都添加日志功能

UserService

public interface UserService {
    void add();
    void delete();
    void update();
    void query();
}

UserServiceImpl

public class UserServiceImpl implements UserService {

    public void add() {
        System.out.println("添加一个用户");
    }

    public void delete() {
        System.out.println("删除一个用户");
    }

    public void update() {
        System.out.println("修改一个用户");
    }

    public void query() {
        System.out.println("查找一个用户");
    }
}

InvocationHandler:程序处理类

public class ProxyInvocationHandler implements InvocationHandler {

    //被代理的类
    //如果要具体的到某一个类的话,最好使用它实现的接口
    private Object target;

    public void setTarget(Object target) {
        this.target = target;
    }

    //动态生成代理类
    //newProxyInstance proxy 的静态方法,用来获取一个Proxy(代理类),
    //参数分别为,(类加载器,实现的接口,InvocationHandler)
    public Object getProxy() {
        return Proxy.newProxyInstance(target.getClass().getClassLoader(),
                target.getClass().getInterfaces(),this);
    }


    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        //利用反射来获取方法名字
        log(method.getName());
        Object result = method.invoke(target, args);
        return result;
    }

    //添加日志
    public void log(String methodName) {
        System.out.println("使用了" + methodName + "方法");
    }
}

main

public class Cilent {
    public static void main(String[] args) {

        UserServiceImpl userService = new UserServiceImpl();

        //创建程序处理类,用来动态生成代理类
        ProxyInvocationHandler invocationHandler = new ProxyInvocationHandler();
        //设置被代理对象
        invocationHandler.setTarget(userService);

        //获取代理类
        UserService proxy = (UserService) invocationHandler.getProxy();

        //调用方法时,程序处理类会帮我们委派到invoke方法里。
        proxy.add();

    }
}

动态代理的好处:

  • 一个动态代理类,代理的是一个接口,改代码的成本很低,代码量也少

AOP xml 方式

什么是aop呢?它叫做面向切面编程,是面向对象的扩展,一般当我们在处理业务逻辑前后有需要执行的操作的时候,比如说日志或者权限验证

这时我们会选择横向的来扩展,在原业务代码外来处理新的逻辑,不改变原有代码,这种方式就是 aop 编程

而spring 的aop呢,它就是基于 动态代理的基础上进行实现的

一些重要的名词解释

  • 目标 被通知的对象
  • 代理 向目标对象通知之后创建的对象
  • 连接点
    • 基本每个方法的前,后(两者都有也行),或抛出异常时都可以是连接点
  • 切入点
    • 上面说的连接点的基础上,来定义切入点,你的一个类里,有15个方法,那就有几十个连接点了对把,但是你并不想在所有方法附近都使用通知(使用叫织入,以后再说),你只想让其中的几个,在调用这几个方法之前,之后或者抛出异常时干点什么,那么就用切点来定义这几个方法,让切点来筛选连接点,选中那几个你想要的方法。
  • 通知
    • 切面必须要实现的功能,也是类的方法,就比如你想在方法执行前这个连接点干点啥
  • 切面
    • 具体可以看成类,是通知和切入点的结合。,没连接点什么事情,连接点就是为了让你好理解切点,搞出来的,明白这个概念就行了。通知说明了干什么和什么时候干(什么时候通过方法名中的before,after,around等就能知道),而切入点说明了在哪干(指定到底是哪个方法),这就是一个完整的切面定义
  • 织入
      把切面应用到目标对象来创建新的代理对象的过程。spring采用的是运行时
      关键就是:切点定义了哪些连接点会得到通知

在此感谢大佬的总结:aop名词解释

首先要使用aop,我们必须导入 aspectjweaver 依赖

方式一

    <dependencies>
        <!-- https://mvnrepository.com/artifact/org.aspectj/aspectjweaver -->
        <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjweaver</artifactId>
            <version>1.9.4</version>
        </dependency>
    </dependencies>

其次,我们创建一个日志类,让他实现 MethodBeforeAdvice

MethodBeforeAdvice,在目标方法执行之前执行
- method 要执行的目标方法
- objects 方法参数
- target 目标对象

public class Log implements MethodBeforeAdvice {

    public void before(Method method, Object[] objects, Object o) throws Throwable {
        System.out.println(o.getClass().getName()+ "的"+ method.getName() + "被执行了");
    }
}

UserServiceImpl 和 UserSerive同上相同

public interface UserService {
    void add();
    void delete();
    void update();
    void query();
}
public class UserServiceImpl implements UserService {

    public void add() {
        System.out.println("添加一个用户");
    }

    public void delete() {
        System.out.println("删除一个用户");
    }

    public void update() {
        System.out.println("修改一个用户");
    }

    public void query() {
        System.out.println("查找一个用户");
    }
}

然后再 application.xml 的文件中配置

<?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 http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd">

    <bean id="log" class="cn.edu.zzuli.log.Log"/>
    <bean id="userService" class="cn.edu.zzuli.service.UserServiceImpl"/>
    <aop:config>
        <aop:pointcut id="pointcut"
                      expression="execution(* cn.edu.zzuli.service.UserServiceImpl.*(..))"/>
        <aop:advisor advice-ref="log" pointcut-ref="pointcut"/>
    </aop:config>
</beans>

然后 main 方法中执行测试,发现结果却是如我们所料

cn.edu.zzuli.service.UserServiceImpl的add被执行了
添加一个用户

方式二

初次之外,我们还可以自定义切面来进行环绕通知

切面类

public class DiyLogPointCut {
    public void before() {
        System.out.println("方法执行前----->");
    }

    public void after() {
        System.out.println("<-----方法执行后");
    }
}

配置文件修改为:

<?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 http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd">

    <bean id="log" class="cn.edu.zzuli.log.Log"/>
    <bean id="userService" class="cn.edu.zzuli.service.UserServiceImpl"/>

    <bean id="diyLogPointCut" class="cn.edu.zzuli.diy.DiyLogPointCut"/>

    <aop:config>
        <aop:aspect id="aspect" ref="diyLogPointCut">
            <aop:pointcut id="pointcut"
                          expression="execution(* cn.edu.zzuli.service.UserServiceImpl.*(..))"/>
            <aop:before method="before" pointcut-ref="pointcut"/>
            <aop:after method="after" pointcut-ref="pointcut"/>

        </aop:aspect>
    </aop:config>
</beans>

效果相同

AOP 注解方式

既然使用注解了,那当然要使用配置类了。

记得在配置类上开启 @EnableAspectJAutoProxy aspect支持啊

AppConfig.java

@Configuration
@ComponentScans({@ComponentScan("cn.edu.zzuli.service"),
        @ComponentScan("cn.edu.zzuli.annotation")})
@EnableAspectJAutoProxy
public class AppConfig {
}

AnnoPointCut 切面:

//标注这个类是一个切面
@Aspect
@Component
public class AnnoPointCut {

    private final String pointcut = "execution(* cn.edu.zzuli.service.UserServiceImpl.*(..))";

    //可以使用变量的方式
    @Before(pointcut)
    public void before() {
        System.out.println("方法执行前----->");
    }

    //同样可以直接写切入点
    @After("execution(* cn.edu.zzuli.service.UserServiceImpl.*(..))")
    public void after() {
        System.out.println("<-----方法执行后");
    }

    @AfterReturning(pointcut)
    public void afterReturning() {
        System.out.println("<-----返回参数后----->");
    }

    //在环绕时,我们可以给定一个参数
    //ProceedingJoinPoint 代表我们要获取的切入点
    @Around(pointcut)
    public void around(ProceedingJoinPoint joinPoint) throws Throwable {

        System.out.println("环绕前");
        //获取签名(哪个方法被执行了)
        //void cn.edu.zzuli.service.UserService.add()
        Signature signature = joinPoint.getSignature();
        System.out.println(signature);
        //执行方法,你可以将它想象成一个过滤器
        Object proceed = joinPoint.proceed();
        System.out.println(proceed);

        System.out.println("环绕后");

    }
}

service类同上

然后再main 方法执行结果也同上。

整合mybatis

这里只写最重要的一部分代码

配置文件的方式

根据官方的快速入门可以这样来做

public interface UserMapper {

    @Select("select * from users")
    List<User> getUsers();

}

xml 文件创建放在 resource/mapper 下可以了

而且这里要添加一个实现类,其实在我看来这里可以看成service,但这样看着就像多封装了一层一样,所以我还觉得 使用 java类的方式更好一点。

public class UserMapperImpl implements UserMapper {

    private SqlSessionTemplate sqlSession;

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

    public List<User> getUsers() {
        return sqlSession.getMapper(UserMapper.class).getUsers();
    }
}
    <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
        <property name="url" value="jdbc:mysql://127.0.0.1:3306/mybatis?serverTimezone=Asia/Shanghai&amp;useUnicode=yes&amp;characterEncoding=UTF8"/>
        <property name="driverClassName" value="com.mysql.jdbc.Driver"/>
        <property name="username" value="root"/>
        <property name="password" value="123456"/>
    </bean>

    <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
        <property name="dataSource" ref="dataSource" />
        <property name="mapperLocations" value="classpath:mapper/*.xml"/>
    </bean>

    <bean id="sqlSession" class="org.mybatis.spring.SqlSessionTemplate">
        <constructor-arg index="0" ref="sqlSessionFactory"/>
    </bean>

    <bean id="userMapper" class="cn.edu.zzuli.mapper.UserMapperImpl">
       <property name="sqlSession" ref="sqlSession"/>
    </bean>

javaConfig 方式

其实这种方式还是较为支持的。不复杂而且springboot应该也是采用了这种方式

@Configuration
@MapperScan("cn.edu.zzuli.mapper")
@ComponentScan("cn.edu.zzuli.bean")
public class AppConfig {
    @Bean
    public DriverManagerDataSource dataSource() {
        DriverManagerDataSource dataSource = new DriverManagerDataSource();
        dataSource.setUrl("jdbc:mysql://127.0.0.1:3306/mybatis?serverTimezone=Asia/Shanghai&useUnicode=yes&characterEncoding=UTF8");
        dataSource.setDriverClassName("com.mysql.jdbc.Driver");
        dataSource.setUsername("root");
        dataSource.setPassword("123456");

        return dataSource;
    }

    @Bean
    public SqlSessionFactory sqlSessionFactory() throws Exception {
        SqlSessionFactoryBean factoryBean = new SqlSessionFactoryBean();
        factoryBean.setDataSource(dataSource());
        return factoryBean.getObject();
    }

    @Bean
    public SqlSessionTemplate SqlSessionTemplate() throws Exception {
        return new SqlSessionTemplate(sqlSessionFactory());
    }

}

声明式事务

现在有spring了,我们再也不需要自己在写事务了,我们可以全权交给spring来管理。

这里还要说明一下声明式事务和编程式事务的区别
声明式事务:不侵入原有业务代码,在外部进行事务管理
编程式事务:在原有业务代码中进行事务处理

spring中,使用aop的方式来进行声明式事务
@EnableTransactionManagement 来开启注解式事务

@Configuration
@MapperScan("cn.edu.zzuli.mapper")
@ComponentScans({
        @ComponentScan("cn.edu.zzuli.bean"),
        @ComponentScan("cn.edu.zzuli.service")})
@EnableTransactionManagement
public class AppConfig {
    @Bean
    public DriverManagerDataSource dataSource() {
        DriverManagerDataSource dataSource = new DriverManagerDataSource();
        dataSource.setUrl("jdbc:mysql://127.0.0.1:3306/mybatis?serverTimezone=Asia/Shanghai&useUnicode=yes&characterEncoding=UTF8");
        dataSource.setDriverClassName("com.mysql.jdbc.Driver");
        dataSource.setUsername("root");
        dataSource.setPassword("123456");

        return dataSource;
    }

    @Bean
    public SqlSessionFactory sqlSessionFactory() throws Exception {
        SqlSessionFactoryBean factoryBean = new SqlSessionFactoryBean();
        factoryBean.setDataSource(dataSource());
        return factoryBean.getObject();
    }

    @Bean
    public SqlSessionTemplate SqlSessionTemplate() throws Exception {
        return new SqlSessionTemplate(sqlSessionFactory());
    }


    @Bean(name="transactionManager")
    public PlatformTransactionManager transactionManager(){
        //这两个的区别是:DataSourceTransactionManager是配置本地单数据源的数据库事务管理,
        //而JtaTransactionManager是为配置分布式数据库的
        //return new JtaTransactionManagerFactoryBean().getObject();
        return new DataSourceTransactionManager(dataSource());
    }
}

在方法或类上来使用 @Transactional 来开启事务,它会通知spring的事务切面来织入事务
在方法抛出RuntimeException或者Error时会触发事务的回滚,在平常我们自己处理事务回滚的时候,我们可以手动抛异常来出发回滚。

同样该注解还可以指定异常类型 rollbackFor = Exception.class


Spring定义了7中传播行为:

  • propagation_requierd:如果当前没有事务,就新建一个事务,如果已存在一个事务中,加入到这个事务中,这是Spring默认的选择。
  • propagation_supports:支持当前事务,如果没有当前事务,就以非事务方法执行。
  • propagation_mandatory:使用当前事务,如果没有当前事务,就抛出异常。
  • propagation_required_new:新建事务,如果当前存在事务,把当前事务挂起。
  • propagation_not_supported:以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。
  • propagation_never:以非事务方式执行操作,如果当前事务存在则抛出异常。
  • propagation_nested:如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则执行与propagation_required类似的操作。

一般我们使用默认的 propagation_requierd,如果执行查询操作的时候,我们会加上 readOnly=true

    @Transactional(readOnly=true)
    public List<User> getUsers() {
        return userMapper.getUsers();
    }

    @Transactional(rollbackFor = java.lang.ArithmeticException.class)
    public void addUser(User user) {
        userMapper.addUser(user);

        //这里故意报错
        int a = 0, b = 0;
        System.out.println(a/b);
    }

其次spring 还可以通过 isolation 属性来控制事务的隔离级别
spring 事务的传播属性和隔离级别
@Transactional 注解的生效和失效场景