Spring笔记


结构总览

结构图

基础

<!-- https://mvnrepository.com/artifact/org.springframework/spring-webmvc -->
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-webmvc</artifactId>
    <version>5.2.6.RELEASE</version>
</dependency>

<!-- https://mvnrepository.com/artifact/org.springframework/spring-webmvc -->
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-jdbc</artifactId>
    <version>5.2.6.RELEASE</version>
</dependency>

IOC理论推导

使用

  1. 导入jar包,用配置
    下面有个Enanble auto import。

思想

之前程序是主动的,执行什么由程序决定;
之后程序是被动的,执行什么看接收了什么;

从本质上解决了问题,程序员不用再去管对象的创建了,系统的耦合性大大降低,可以更专注在业务上的实现!

Dao接口

public interface UserDao {
    void getUser();
}

Dao的两个实现类

public class UserDaoImpl implements UserDao {
    public void getUser() {
        System.out.println("默认获取数据");
    }
}
public class UserDaoMysqlImpl implements UserDao{
    public void getUser() {
        System.out.println("Mysql");
    }
}

Service接口

public interface UserService {
    void getUser();
}

Service实现类原版

public class UserServiceImpl implements UserService {
    // 写死了
    private UserDao userDao = new UserDaoImpl();

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

原版测试代码

public class MyTest {
    public static void main(String[] args) {
        // 每次变化都要改动上面的源码,这里不变
        UserServiceImpl userService = new UserServiceImpl();
        userService.getUser();
    }
}

Service实现类新版

public class UserServiceImpl implements UserService {
    // 新思想,利用set动态注入
    private UserDao userDao;
    // 如果参数是接口的话,可以传递实现类进去
    public void setUserDao(UserDao userDao) {
        this.userDao = userDao;
    }
    public void getUser() {
        userDao.getUser();
    }
}

新版测试

public class UserServiceImpl implements UserService {
    // 新思想,利用set动态注入
    private UserDao userDao;

    // 如果参数是接口的话,可以传递实现类进去
    public void setUserDao(UserDao userDao) {
        this.userDao = userDao;
    }

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


IOC本质

使用和不使用的区别:

控制反转是一种通过描述(XML或注解)并通过第三方去生产或获取特定对象的方式。在Spring中实现控制反转的是IoC容器,其实现方法是依赖注入DI

好像就是把对象的创建、赋值都交给了xml文件

bean是利用set进行注入的,类的左边会有叶子标志(点了右上角的config才会有);
如果没有set的话是不能用bean的

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

使用xml配置文件

先把刚才的练习重写一遍,先添加个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="Daoimpl" class="com.kuang.dao.UserDaoImpl" ></bean>
    <bean id="Mysqlimpl" class="com.kuang.dao.UserDaoMysqlImpl"></bean>
    <bean id="ServiceImpl" class="com.kuang.service.UserServiceImpl">
        <!--ref是自己定义的类型, 使用上面bean定义的
        value是基本数据类型,是个值-->
        <property name="userDao" ref="Mysqlimpl"></property>
    </bean>
</beans>

修改测试代码

public class MyTestBean {
    public static void main(String[] args) {
        ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
        UserServiceImpl serviceImpl = (UserServiceImpl) context.getBean("ServiceImpl");
        serviceImpl.getUser();
    }
}

HelloSpring

<?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创建对象,在Spring中这些都成为Bean
    Bean = 对象,相当于new Hello();
    class就是要new的对象
    property就是个对象中的属性设置一个值
    -->
    <bean id="hello" class="com.it.Hello">
        <!--在这里设置参数-->
        <property name="str" value="Spring"/>
    </bean>
</beans>
public class MyTest {
    public static void main(String[] args) {
        // 获取spring的上下文对象
        // 使用xml文件专用初始化,可加载多个xml
        // 这个加载语句是固定的
        ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
        // 对象现在都在spring中管理,要使用直接从里面取出来就可以了
        // 参数为bean的id
        // 然后强转一下
        Hello hello = (Hello) context.getBean("hello");
        System.out.println(hello.getStr());
    }
}
public class Hello {
    private String str;

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

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

    public String getStr() {
        return str;
    }
}

IOC创建对象方式

  1. 使用类的无参构造方法,默认就是无参,此时如果没有无参构造函数,就会出错
  2. 有参
    1. 下标
    2. 参数类型
    3. 参数名

容器就像婚介所,配置文件加载的时候,容器中管理的对象就就已经初始化了。
而且容器的对象都是一个,get后是同一个

配置文件

<?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="com.it.User">-->
        <!--<constructor-arg index="0" value="第一种,index"></constructor-arg>-->
    <!--</bean>-->

    <!--第二种-->
    <!--<bean id="User" class="com.it.User">-->
    <!--<constructor-arg type="java.lang.String" value="第二种,参数类型"></constructor-arg>-->
    <!--</bean>-->

    <!--第三种-->
    <bean id="User" class="com.it.User">
        <constructor-arg name="name" value="第三种,参数名"></constructor-arg>
    </bean>

    <bean id="UserT" class="com.it.UserT"></bean>
</beans>

测试代码

public class MyTest {
    public static void main(String[] args) {
        ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
        User user = (User) context.getBean("User");
        User user2 = (User) context.getBean("User");
        System.out.println("if equal ? : "+ (user==user2));
        user.show();
    }
}

Spring配置

alisa

别名

    <alias name="User" alias="hhh"></alias>

使用原来的名字和别名都能调用

bean

简单的那几个不说了,另一种取别名方式,这个更高级,可以同时取多个别名。还可以用多种分割符,如空格,逗号,分号。

    <bean id="UserT" class="com.it.UserT" name="u2,u3 u4;u5"></bean>

import

一般用于团队开发使用,可以将多个配置文件,导入合并为一个。
假设有三个人复制不同的类开发,不同的类的注册再不同的bean中,使用import将所有人的bean.xml合并为一个总的

    <import resource="beans2.xml"></import>
    <import resource="beans3.xml"></import>

DI依赖注入

三种方式

构造器注入

用Set方式注入

依赖:bean对象的创建依赖容器
注入:bean对象的所有属性,由容器来注入

完善注入信息

<?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="address" class="com.it.Address">
        <property name="add" value="北京"></property>
    </bean>

    <bean id="student" class="com.it.Student">
        <!--第一种,普通指注入,直接value-->
        <property name="name" value="张三"></property>
        <!--第二种,bean注入,ref是bean中注册的-->
        <property name="address" ref="address"></property>
        <!--数组注入-->
        <property name="books">
            <array>
                <!--这里不用加引号-->
                <value>红楼梦</value>
                <value>西游记</value>
                <value>水浒传</value>
                <value>三国演义</value>
            </array>
        </property>
        <!--List注入-->
        <property name="hobbys">
            <list>
                <value>音乐</value>
                <value>运动</value>
            </list>
        </property>
        <!--Map注入-->
        <property name="card">
            <map>
                <!--这里用的entry,键值对-->
                <entry key="身份证" value="123"/>
                <entry key="银行卡" value="345"/>
            </map>
        </property>
        <!--Set注入-->
        <property name="games">
            <set>
                <value>LOL</value>
                <value>DNF</value>
            </set>
        </property>
        <!--NULL和空-->
        <property name="wife">
            <!--自闭和-->
            <null/>
        </property>
        <!--Properties-->
        <property name="info">
            <props>
                <prop key="学号">2075</prop>
                <prop key="url"></prop>
            </props>
        </property>
    </bean>
</beans>

拓展方式

p命名
    xmlns:p="http://www.springframework.org/schema/p"
    <bean id="User" class="com.it.User" p:name="张三" p:age="12"></bean>

可无参构造
p命名空间注入,可以直接注入属性的值

c命名

需要有参构造器

    xmlns:c="http://www.springframework.org/schema/c"
    <bean id="User2" class="com.it.User" c:age="12" c:name="李四"></bean>

测试

public void test(){
    ApplicationContext context = new ClassPathXmlApplicationContext("userbean.xml");
    User user = context.getBean("User", User.class);
    System.out.println(user.toString());
}

注意

  1. 需要导入约束才可以使用p、c命名空间

bean的作用域

singleton单例模式

设置为单例(默认)

    <bean id="User2" class="com.it.User" c:age="12" c:name="李四" scope="singleton"></bean>

单线程适合使用,多线程会出错

prototype原型模式

    <bean id="User2" class="com.it.User" c:age="12" c:name="李四" scope="prototype"></bean>

从容器中get的两个实例不想等,每次都产生新对象。
比较浪费资源,但是适合多线程

request、seesion、application只能在web开发中使用

Bean的自动装配

自动装配是Spring满足bean依赖的一种方式

在Spring中有三种装配方式

  1. 在xml中显示的配置
  2. 在java中显示配置
  3. 隐式的自动装配【重要】

byName
省了一些ref

<?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="cat" class="com.it.Cat"></bean>
    <bean id="dog" class="com.it.Dog"></bean>
    
    <!--会自动在容器中寻找和set后面的值对应的beanid-->
    <bean id="person" class="com.it.People" autowire="byName">
        <!--使用了byName就不需要下面这两个了-->
        <!--<property name="cat" ref="cat"/>-->
        <!--<property name="dog" ref="dog"/>-->
        <property name="name" value="张三"></property>
    </bean>
</beans>

byType
通过类型来省ref

<?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">
     <!--此时这边可以不加id了-->
    <bean class="com.it.Cat"></bean>
    <bean class="com.it.Dog"></bean>

    <!--byType自动找类型,但是要保证类型全局为一-->
    <bean id="person" class="com.it.People" autowire="byType">
        <property name="name" value="张三"></property>
    </bean>
</beans>

小结:

  • byName要保证bean的id唯一,并且bean需要和自动注入的属性的set方法的值一致(有点搞不懂,小写可以,大写就不可以了)
  • byType要保证所有bean的class唯一,并且这个bean需要和自动注入的属性类型一致
使用注解实现自动装配
Autowired和Qualifirer

注意:

  1. 导入约束
<?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
        http://www.springframework.org/schema/context/spring-context.xsd">

    <!--开启注解支持-->
    <context:annotation-config/>

    <bean id="cat" class="com.it.Cat"/>
    <bean id="dog" class="com.it.Dog"/>
    <bean id="person" class="com.it.People"/>

</beans>

使用Autowired

package com.it;

import org.springframework.beans.factory.annotation.Autowired;

public class People {
    @Autowired
    private Dog dog;
    @Autowired
    private Cat cat;
    private String name;

    public Dog getDog() {
        return dog;
    }

    public Cat getCat() {
        return cat;
    }

    public String getName() {
        return name;
    }

}

注意

  1. 可以在属性上用,也可以在set上用
  2. 可以不用set,前提是子弟哦那个装配的属性在IOC容器中存在,且符合名字

这个名字还是挺奇怪的,使用注解的时候ID不符合也可以:

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

    <!--开启注解支持-->
    <context:annotation-config/>

    <bean id="cat1" class="com.it.Cat"/>
    <bean id="Dog" class="com.it.Dog"/>
    <bean id="person" class="com.it.People"/>

</beans>

使用Qualifier
科普:

  1. 参数加了@Nullable,就允许该参数为null
  2. @Autowired(required = false)说明可以为null,否则不可以为空

如果@Autowired自动装配的环境比较复杂, 自动装配无法通过一个注解【@Autowired】完成的时候,我们可以使用@Qualifier(value= "xxx")来配置@Autowired的使用,指定唯一的bean对象注入。

public class People {
    @Autowired
    @Qualifier(value="dog222") // 自动装配指定id
    private Dog dog;
    @Autowired(required=false)
//    @Qualifier(value = "dog222") 类型不同也不能指定
    private Cat cat;
    private String name;

    public Dog getDog() {
        return dog;
    }

    public Cat getCat() {
        return cat;
    }

    public String getName() {
        return name;
    }
}
不需要spring的自动注入

@Resource先通过名字找,找不到就通过类型找,都找不到才会报错

但是有问题,先不管,主要还是用@Aurowire

二者区别

  1. 都是用来自动装配的,都可以放在属性字段上
  2. @Autowire通过byName方式实现的,而且必选要求这个对象存在,不然就空指针异常了
  3. @Resource默认通过byName实现,如果找不到就用byType,否则就报错

Spring注解开发

在Spring4之后,使用注解必须导入

类直接

指定要扫描的包,在这个包下的注解会生效
<context:comonent-scan base-package="com.kuang.pojo">

类加@Component ,此时不使用bean就可调用

// 等价于<bean id="user" class="com.kuang.poojo.User"/>
// 名字默认变小写
@Component
public class User{
}

属性注入

此时属性还没注入,属性注入方法:

@Component
public class User {
    // 相当于使用<property name="name" value="李四">
    @Value("李四")
    public String name;
}

也可在set上写

@Value("王五")
public void setName(String name) {
    this.name = name;
}

衍生的注解

这四个都一样,都是将类放到容器中,只是位置不同表示不同

// controller包用的
@Controller
public class UserController {
}

// Service层常用的注解
@Service
public class UserService {
}

// 仓库的意思
// 一般Dao层用这个标注
@Repository
public class UserDao {
}

@Component
public class User {
    // 相当于使用<property name="name" value="李四">
    @Value("李四")
    public String name;
}

自动配置注解

就Autowired那几个

作用域

@Scope控制,直接用类上的注解实现

@Scope("prototype")
@Scope("singleton")
public class UserDao {
}

小结

xml与注解:

  • xml万能,适用于任何场合,维护简单方便
  • 注解不是自己的类使用不了,要scan;维护相对复杂,每个类里面都要改

最佳实践:

  • xml用来管理bean
  • 注解只负责完成属性的注入
  • 我们在使用的过程中只需要注意一个问题:必须让注解生效,就需要开启直接的支持:
    <context:component-scan base-package="com.it.pojo"/> 控制扫描的包
    <context:annotation-config/> 开启注解使用

基于JAVA类的配置

用java类配置替代xml配置。

@componentScan()

@Import


代理模式

为什么要学习代理模式?

代理模式的分类:

  • 静态代理
  • 动态代理

静态代理

角色分析

  • 抽象角色:一般会使用接口或者抽象类
  • 真实角色:被代理的角色
  • 代理角色:代理真是角色,代理真实角色后,我们一般会做一些附属操作
  • 客户:访问代理对象的人

代码步骤

  1. 接口
  2. 真实角色
  3. 代理角色
  4. 客户端访问代理角色

代理模式好处

  • 可以使真实角色的操作更加纯村,不用去关注一些公共的业务
  • 公共也就交给代理角色,实现了业务的分工
  • 公共业务发生拓展的时候,方便集中管理

缺点

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

租房案例

// 出租接口
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() {
        host.rent();
        hetong();
        feiyong();
    }

    private void hetong(){
        System.out.println("跟中介签合同");
    }

    private void feiyong(){
        System.out.println("中介收取中介费");
    }
}

// 客户
public class Client {
    public static void main(String[] args) {
        // 一个房东出现了!
        Host host = new Host();
        Proxy proxy = new Proxy(host);
        proxy.rent();
    }
}

通过中介来完成租房事件,全程要租的人和有房子的人都没有接触。

不改变对象的情况下,使用代理模式很方便,可以不改变业务的源码,只改变代理类中的代码即可

代理模式就是用set接收一个对象,然后对这个对象操作,

// 改动原有的代码,在公司中是大忌

拓展时方便集中管理的例子

// 方法接口
public interface UserService {
    void add();
    void del();
    void update();
    void query();
}

// 方法实现类
public class USImpl implements UserService {
    public void add() {
        System.out.println("add");
    }

    public void del() {
        System.out.println("adeldd");

    }

    public void update() {
        System.out.println("update");

    }

    public void query() {
        System.out.println("query");

    }
}

// 代理,也实现了方法接口
public class USProxy implements UserService{
    private USImpl us;

    public void setUs(USImpl us) {
        this.us = us;
    }

    public USProxy() {
    }

    // 包装一下
    public void add() {
        show();
        us.add();
    }

    public void del() {
        show();
        us.del();
    }

    public void update() {
        show();
        us.update();
    }

    public void query() {
        show();
        us.query();
    }

    // 添加新功能
    private void show(){
        System.out.println("新功能!");
    }

}

// 客户
public class Client {
    public static void main(String[] args) {
        USImpl us = new USImpl();
        USProxy usProxy = new USProxy();
        usProxy.setUs(us);
        usProxy.add();
    }
}

动态代理

底层都是反射

  • 动态代理和静态代理角色一样
  • 动态代理的代理类是动态生成的,不是直接写好的
  • 动态代理分为两大类:基于接口的动态代理,基于类的动态代理
    • 基于接口—JDK动态代理
    • 基于类:cglib
      +java字节码实现:javasist

如何知道要用代理类的那个方法?如歌修改方法?

invoke是跟所有方法相关?一次改好多?
那如何一次改一个?
反射是万能的,可以直接用method.getName( )来获得名字

Proxy用来生活曾带实例,InvocationHandler用来调用方法返回结果

动态代理的好处:

  • 包含静态的全部好处
  • 一个动态代理类代理的是一个接口,一般就是对应的一类业务
  • 一个动态代理类可以代理多个类,只要是实现了同一个接口

不用每个类都写,直接由模板(invoke)

动态代理工具类

public class ProxyInv implements InvocationHandler {
    // 被代理的接口
    private Object target;

    // 输入实现类
    public void setTarget(Object target) {
        this.target = target;
    }

    // 生成得到代理类实例
    // 参数是:实现类自身属性,代理类获取接口,代理实现类本身
    public Object getProxy(){
        return Proxy.newProxyInstance(this.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 mst){
        System.out.println("执行了" + mst + "方法");
    }
}

测试代码

public class Client {
    public static void main(String[] args) {
        // 1. 创建真实角色,是个实现类
        USImpl us = new USImpl();
        // 2. 创建代理角色,现在并不存在实例
        ProxyInv proxyInv = new ProxyInv();
        // 3. 设置要代理的对象,代理真实角色
        proxyInv.setTarget(us);
        // 4. 动态生成代理类实例
        // 返回的类型是接口类型,调用接口类型的方法是实现类的方法
        // 之前手动写代理,代理也是实现的接口,所以代理的方法还是接口中的方法
        // 直接返回个接口类型也没毛病,方法还是那些方法,不过方法的实现被改造了
        // 每个方法是调用了invoke吧,然后invoke中有修改和原实现类方法
        UserService proxy = (UserService) proxyInv.getProxy();
        // 5. 利用代理类实例调用方法
        proxy.query();
    }
}

使用代理类步骤

  1. 创建真实角色,是个实现类
  2. 创建代理角色,现在并不存在实例
  3. 设置要代理的对象,代理真实角色
  4. 动态生成代理类实例
  5. 利用代理类实例调用方法

通过预定义的类,输入真实角色实现类就可进行代理,代理类会自动知道该类实现的接口,然后创建实例,实例类型就是接口的类型,然后调用方法的时候是调用的invoke,里面利用反射来调用实现类的方法,同时还可在invoke中附加操作。
动态的意思,就是类是动态设置的,每次改动传入的参数就能改变

AOP

实现方式:

  1. 导包

实现方式一:使用Spring的API接口

主要是Spring API的接口实现

两个.表示任意参数,一会的代码会在切入点执行

把log类切入到上面,

步骤
繁琐的紧呀

接口

public interface UserService {
    public void add();
    public void del();
    public void update();
    public void select();
}

实现类

public class UserServiceImpl implements UserService{
    public void add() {
        System.out.println("add");
    }

    public void del() {
        System.out.println("del");

    }

    public void update() {
        System.out.println("update");

    }

    public void select() {
        System.out.println("select");

    }
}

Log

public class Log implements MethodBeforeAdvice {
    // method: 要执行的目标对象方法
    // objects:参数
    // target: 目标对象
    // 执行方法前自动调用
    public void before(Method method, Object[] objects, Object target) throws Throwable {
        System.out.println(target.getClass().getName() + "的" + method.getName() + "方法被执行");
    }
}

AfterLog

// 像让方法在函数调用后被调用 要继承接口
public class AfterLog implements AfterReturningAdvice {

    public void afterReturning(Object returnvalue, Method method, Object[] objects, Object o1) throws Throwable {
        System.out.println("已经执行方法: " + method.getName() + " ,返回结果为:" + returnvalue);
    }
}

AOP配置文件

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

    <!--注册bean-->
    <bean id="userService" class="com.it.service.UserServiceImpl"/>
    <bean id="log" class="com.it.log.Log"/>
    <bean id="afterLog" class="com.it.log.AfterLog"/>
    <!--配置aop, 导入aop的约束-->
    <aop:config>
        <!--导入切入点:-->
        <!--exectution(要执行的位置 类 方法 参数)-->
        <!--*表示所有,类后面的.*表示类中所有方法,..表示所有参数-->
        <aop:pointcut id="pointcut" expression="execution(* com.it.service.UserServiceImpl.*(..))"/>

        <!--执行环绕增强-->
        <aop:advisor advice-ref="log" pointcut-ref="pointcut"/>
        <aop:advisor advice-ref="afterLog" pointcut-ref="pointcut"/>
    </aop:config>

</beans>

测试代码

public class MyTest9 {
    public static void main(String[] args) {
        ApplicationContext context= new ClassPathXmlApplicationContext("bean.xml");
        // 动态代理代理的是接口
        UserService userService = context.getBean("userService", UserService.class);

        userService.add();
    }
}

理解

  • 首先是把三各类都注册了,并没有和接口相关的操作,在使用getBean的时候,对注册的实现类获取接口类型,然后调用接口类型的方法,这个和上面的动态代理是一样的,都是直接调用接口的方法。
  • 两个log分别实现了两个接口,都插入到了pointcut中,而这个pointcut执行的位置是实现类,所以该实现类的方法都通过aop:advisor和两个log相关了。
  • 实现类能被动态代理是因为在aop:config中将该实现类导入了切点。

实现方法二

主要是切面定义

自定义优劣

  • 好处是更加方便了,不用实现接口
  • 确定是功能不强,无法和方法互动,只是独立的方法(API中的参数有方法和结果相关内容)

步骤

  1. 自定义一个普通类,里面有需要的方法
  2. 配置文件中注册该类
  3. 创建自定义切片,指向该类的id
  4. 将需要被使用的地方做成切点
  5. 使用aop中对应的方法把对应的方法插入切点

自定义类

public class diy {
    public void before(){
        System.out.println("=========执行之前========");
    }

    public void after(){
        System.out.println("=========执行之后========");
    }
}

配置文件

    <!--方法二,不用API-->
    <bean id="diy" class="com.it.diy.diy"/>
    <aop:config>
        <!--自定义切片, 使用ref指定类-->
        <aop:aspect ref="diy">
            <!--切点-->
            <aop:pointcut id="point" expression="execution(* com.it.service.UserServiceImpl.*(..))"/>
            <!--方法-->
            <aop:after method="after" pointcut-ref="point"/>
            <aop:before method="before" pointcut-ref="point"/>
        </aop:aspect>
    </aop:config>

测试代码不用改,因为是读取的配置文件


实现方式三:使用注解

步骤

  1. 建立一个类,在适当的地方加注解
  2. 配置文件中注册这个类
  3. 配置文件中开启注解支持

切面类

// 标注这是一个切面
@Aspect
public class Annotation {
    @Before("execution(* com.it.service.UserServiceImpl.*(..))")
    public void befor(){
        System.out.println("=====before=====");
    }

    @After("execution(* com.it.service.UserServiceImpl.*(..))")
    public void after(){
        System.out.println("=====after=====");
    }

    @Around("execution(* com.it.service.UserServiceImpl.*(..))")
    public void around(ProceedingJoinPoint jp) throws Throwable {
        System.out.println("环绕前");
        Signature signature = jp.getSignature();
        // 打印签名,即当前在调用哪个函数, void com.it.service.UserService.add()
        System.out.println("签名: " + signature);
        Object proceed = jp.proceed();  // 执行方法
        System.out.println("环绕后");
    }
}

配置文件

    <!--方法三, 注解-->
    <!--注册注解类-->
    <bean id="annoPointCut" class="com.it.diy.Annotation"/>
    <!--开启注解支持-->
    <aop:aspectj-autoproxy/>

整合Mybatis

步骤:

  1. 导入相关jar包
    1. junit
    2. mybatis
    3. mysql数据库
    4. spring相关
    5. aop注入
    6. mybatis-spring
  2. 编写配置文件
  3. 测试

依赖

<dependencies>
        <dependency>
            <groupId>org.mybatis</groupId>
            <artifactId>mybatis</artifactId>
            <version>3.5.2</version>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.47</version>
        </dependency>
        <!--spring操作数据库-->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-jdbc</artifactId>
            <version>5.1.9.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjweaver</artifactId>
            <version>1.8.13</version>
        </dependency>
        <dependency>
            <groupId>org.mybatis</groupId>
            <artifactId>mybatis-spring</artifactId>
            <version>2.0.2</version>
        </dependency>
</dependencies>

构建
最重要的两个东西:一个是SqlSessionFactory和至少一个数据映射器类。

创建SqlSessionFactory
在XML配置文件中配置工厂bean,工厂bean需要数据源,数据源是用来连接数据库的

<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
  <property name="dataSource" ref="dataSource" />
</bean>

上下是一样的,只是一个用xml,一个用java

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

将Mapper接口接入Spring
接口,可以用注解,也可以用xml配置文件:
那要是使用xml会自动读取吗?

public interface UserMapper {
  @Select("SELECT * FROM users WHERE id = #{userId}")
  User getUser(@Param("userId") String userId);
}

通过MapperFactoryBean来加入:

<bean id="userMapper" class="org.mybatis.spring.mapper.MapperFactoryBean">
  <property name="mapperInterface" value="org.mybatis.spring.sample.mapper.UserMapper" />
  <property name="sqlSessionFactory" ref="sqlSessionFactory" />
</bean>

使用
获取Mapper:

@Bean
public UserMapper userMapper() throws Exception {
  SqlSessionTemplate sqlSessionTemplate = new SqlSessionTemplate(sqlSessionFactory());
  return sqlSessionTemplate.getMapper(UserMapper.class);
}

调用Mybatis方法:

public class FooServiceImpl implements FooService {

  private final UserMapper userMapper;

  public FooServiceImpl(UserMapper userMapper) {
    this.userMapper = userMapper;
  }

  public User doSomeBusinessStuff(String userId) {
    return this.userMapper.getUser(userId);
  }
}

SqlSessionFactoryBean
SqlSessionFactoryBean实现了FactoryBean的结果,所以Spring最终创建的bean并不是SqlSessionFactroy本身,而是工厂类的getObject()方法返回的结果。此时,Spring会在应用启动时创建SqlSessionFactory,并使用sqlSessionFactory这个名字存储起来

SqlSessionFactory
必要属性:dataSource
常用属性:configLocation,指定Mybatis的xml配置文件路径,Mybatis的配置不需要完整,Spring能代替一部分。新版本可以在没有对应XML配置文件的时候,自己设置Configuration实例。
常用属性:mapperLocations,指定Mybatis的映射器XML配置文件的位置,可以用*

使用SqlSession
使用bean植入到线程安全的SqlSession,可以基于Spring的配置来自动提交、回滚、关闭Session。

SqlSessionTemplate
SqlSession的一个实现。
管理session的生命周期

SqlSessionTemplate的构建
使用构造方法,第一个参数为sqlSessionFactory:

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

java写法:

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

实现类
这个sqlSession是怎么传递进来的呢?

public class UserDaoImpl implements UserDao {

  private SqlSession sqlSession;

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

  public User getUser(String userId) {
    return sqlSession.selectOne("org.mybatis.spring.sample.mapper.UserMapper.getUser", userId);
  }
}

答:通过bean注入的,还真是啥都不用写定了,直接往里怼

<bean id="userDao" class="org.mybatis.spring.sample.dao.UserDaoImpl">
  <property name="sqlSession" ref="sqlSession" />
</bean>

批量操作
但是,没写insert的操作啊

public void insertUsers(List<User> users) {
  for (User user : users) {
    sqlSession.insert("org.mybatis.spring.sample.mapper.UserMapper.insertUser", user);
  }
}

SqlSessionDaoSupport
是一个抽象的支持类,用来提供SqlSession,可以调用getSqlSession()方法来获取一个SqlSessionTemplate
可以通过属性来设置sqlSessionFactroy或者SqlSessionTemplate,如果两个属性都被设置了,那么SqlSessionFactory会被忽略。

设置方法
前提是UserMapperImplSqlSessionDaoSupport的子类,可以编写下面的内容来执行配置:

<bean id="userDao" class="org.mybatis.spring.sample.dao.UserDaoImpl">
  <property name="sqlSessionFactory" ref="sqlSessionFactory" />
</bean>

注册映射器

使用MapperFactoryBean来将映射器注册到Spring中,如果在接口UserMapper的相同类路径下有对应的XML映射器配置文件,那么会被MapperFactoryBean自动解析,所以,就不用在Mybatis配置文件中显示配置映射器,除非路径不同。(所以说放在一块还是很方便的)

java方法:

@Bean
public MapperFactoryBean<UserMapper> userMapper() throws Exception {
  MapperFactoryBean<UserMapper> factoryBean = new MapperFactoryBean<>(UserMapper.class);
  factoryBean.setSqlSessionFactory(sqlSessionFactory());
  return factoryBean;
}

发现映射器

三种方法

  1. 使用<mybatis:scan/>元素
  2. 使用@MapperScan注解
  3. 在经典Spring XML配置文件中注册一个MapperScannerConfigurer

<mybatis:scan/>
对整个包进行扫描,可以通过逗号或者分号分隔来设置多个包。
不需要指定SqlSessionFactorySqlSessionTemplate,因为使用的是能够被自动注入的MapperFactoryBean
<context:component-scan/>无法发现并注册映射器

  <mybatis:scan base-package="org.mybatis.spring.sample.mapper" />

@MapperScan:
可以配置sqlSessionFactorysqlSessionTemplate属性。

@Configuration
@MapperScan("org.mybatis.spring.sample.mapper")
public class AppConfig {
  // ...
}

MapperScannerConfigurer

<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
  <property name="basePackage" value="org.mybatis.spring.sample.mapper" />
</bean>

如果想指定sqlSessionFactorysqlSessionTemplate,需要指定bean名而不是bean引用,所以要使用value属性:

<property name="sqlSessionFactoryBeanName" value="sqlSessionFactory" />

问题:

  1. SqlSessionFactorySqlSessionTemplate的区别是什么?
    SqlSessionFactory是通过SqlSessionFactoryBean的getObject获取的
    SqlSessionTemplate是以SqlSessionFactory为构造参数构造的
    使用SqlSessionFactory来创建SqlSession,有了SqlSession就不需要再使用SqlSessionFactory了,SqlSessionTemplateSqlSession的一个实现。

SqlSessionDaoSupport也可以获得SqlSession

报错

  1. Cause: java.lang.IllegalArgumentException: Mapped Statements collection already contains value for com.mapper.UserMapper.getUser. please check com/mapper/UserMapper.xml and file [D:\同步文件夹\Java_code\Spring\spring-10-mybatis\target\classes\com\mapper\UserMapper.xml]
    好像是说已经包含了mapper的值

  2. java.lang.NoSuchMethodError: org.springframework.beans.factory.config.BeanDefinition.getResolvableType()Lorg/springframework/core/ResolvableType;

说是没方法,有没报错,可能就是继承或实现出错了
版本问题,检查maven依赖中的spring-jdbc和spring-webmvc是否版本一致


方法一流程

流程图

UserMapperUserMapper.xml就不谈了,跟Mybatis一样,这里多了个UserMapperImpl
UserMapperImpl:

public class UserMapperImpl implements UserMapper {
    // 设置私有成员变量接受sqlSession
    // 注意啦,这里是Template
    private SqlSessionTemplate sqlSession;

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

    public User getUser(int id) {
        UserMapper mapper = sqlSession.getMapper(UserMapper.class);
        return mapper.getUser(id);
    }
}

绑定Mybatis
固定不变的

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:context="http://www.springframework.org/schema/context"
       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
        http://www.springframework.org/schema/context
        http://www.springframework.org/schema/context/spring-context.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?useSSL=false&amp;userUnicode=true&amp;characterEncoding=gbk"/>
        <property name="username" value="root"/>
        <property name="password" value="1234"/>
    </bean>

    <!--创建SqlSessionFactory-->
    <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
        <property name="dataSource" ref="dataSource"/>
        <!--还可以绑定Mybatis配置文件-->
        <property name="configLocation" value="classpath:config.xml"/>
        <property name="mapperLocations" value="classpath:com/mapper/*.xml"/>
    </bean>

    <!--创建sqlSession-->
    <bean id="sqlSession" class="org.mybatis.spring.SqlSessionTemplate">
        <!--通过构造函数设置的-->
        <constructor-arg index="0" ref="sqlSessionFactory"/>
    </bean>
</beans>

绑定实现类
可以达到分离的效果

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

    <import resource="spring-dao.xml"/>


    <!--注册实现类的bean-->
    <bean id="userMapper" class="com.mapper.UserMapperImpl">
        <!--参数赋值,利用set-->
        <property name="sqlSession" ref="sqlSession"/>
    </bean>
</beans>

测试

    @Test
    public void testSpring1(){
        ApplicationContext context = new ClassPathXmlApplicationContext("Application.xml");
        // 可以绑定上接口的class
        UserMapper userMapper = context.getBean("userMapper", UserMapper.class);
        User user = userMapper.getUser(1);
        System.out.println(user);
    }

思路总结
Spring利用dataSource创建了SqlSessionFactoryBean,然后利用SqlSessionFactoryBean来构造SqlSessionTemplate,将这个SqlSessionTemplate传到实现类中,在测试中用getMapper就能调用方法。

方法二流程

相当于方法一的简化版,跳过了对实现类SqlSessionTemplate赋值的部分,通过继承的父类中的方法直接过去session,该父类需要一个SqlSessionFactoryBean的输入

流程图

实现类

public class UserMapperImpl2 extends SqlSessionDaoSupport implements UserMapper{
    public User getUser(int id) {
        // 一步到位,点开父类看代码就知道只是做了简化
        return getSqlSession().getMapper(UserMapper.class).getUser(id);
    }
}

Spring配置

    <!--创建SqlSessionFactory-->
    <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
        <property name="dataSource" ref="dataSource"/>
        <!--还可以绑定Mybatis配置文件-->
        <property name="configLocation" value="classpath:config.xml"/>
        <property name="mapperLocations" value="classpath:com/mapper/*.xml"/>
    </bean>

    <bean id="userMapper2" class="com.mapper.UserMapperImpl2">
        <property name="sqlSessionFactory" ref="sqlSessionFactory"/>
    </bean>

事务

在事务处理期间,一个单独的SqlSession对象会被创建或使用。当事务完成时,这个session会以合适的方式提交或回滚。

配置
注意这里的dataSource要和用来创建SqlSessionFactoryBean的是同一个数据源。

<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
  <constructor-arg ref="dataSource" />
</bean>

交由容器管理事务
使用Spring的事务命名空间:

<tx:jta-transaction-manager />

Spring会自动使用任何一个存在的容器事务管理器,并注入一个SqlSession。如果没有正在进行的事务,而基于事务配置需要一个新的事物的时候,Spring会开启一个新的由容器管理的事务。

所以容器是什么意思?

如果不想使用Spring的事务管理,就必须配置SqlSessionFactoryBean来使用基本的Mybatis的ManagedTreansactionFactory

<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
  <property name="dataSource" ref="dataSource" />
  <property name="transactionFactory">
    <bean class="org.apache.ibatis.transaction.managed.ManagedTransactionFactory" />
  </property>
</bean>

编程式事务管理
使用Spring的时候,不能再Spring管理的SqlSession中调用SqlSession.commit()、SqlSession.rollback()、SqlSession.close()方法。

注意:无论 JDBC 连接是否设置为自动提交,调用 SqlSession 数据方法或在 Spring 事务之外调用任何在映射器中方法,事务都将会自动被提交。

导入

       xmlns:tx="http://www.springframework.org/schema/tx"
       http://www.springframework.org/schema/tx
       http://www.springframework.org/schema/tx/spring-tx.xsd

       xmlns:aop="http://www.springframework.org/schema/aop"
       http://www.springframework.org/schema/aop
       http://www.springframework.org/schema/aop/spring-aop.xsd

好像有版本问题

**固定步骤

    <!--配置声明式事务-->
    <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>
        <!--expression控制执行范围, mapper里的全部类的全部方法-->
        <aop:pointcut id="txPointCut" expression="execution(* com.mapper.*.*(..))"/>
        <aop:advisor advice-ref="txAdvice" pointcut-ref="txPointCut"/>
    </aop:config>

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值