SSM 学习笔记

44 阅读18分钟

IOC(控制反转)

程序中不要主动用new产生对象,对象的创建控制权转移到外部,这就是所谓的控制反转。

比如没有spring ioc之前我们是这样写的:

package com.akbar.ioc;

public class BookServiceImpl implements BookService {
    private BookDao bookDao =  new BookDaoImpl();    // 这种手动new耦合度很高

    @Override
    public void save() {
        bookDao.save();
        System.out.println("bookService save ....");
    }
}

使用spring ioc思想解决这个问题(但是下面的代码还不能运行,只是举个例子):

不用自己主动new对象,而从外部提供对象(spring ioc容器)

package com.akbar.ioc;

public class BookServiceImpl implements BookService {
    private BookDao bookDao;     // 耦合度低了

    @Override
    public void save() {
        bookDao.save();
        System.out.println("bookService save ....");
    }
}

由spring ioc容器管理的对象称为Bean

IOC配置

pom.xml中导入spring的坐标:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.akbar</groupId>
    <artifactId>ssm01</artifactId>
    <version>1.0-SNAPSHOT</version>

    <properties>
        <maven.compiler.source>17</maven.compiler.source>
        <maven.compiler.target>17</maven.compiler.target>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>6.2.8</version>
        </dependency>
    </dependencies>
</project>

在resources目录下创建spring的配置文件applicationContext.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-->
    <!--注意我们交给ioc容器的是实现类,不是接口,因为后续需要早对象-->
    <!--
        class: 要管理的Bean
        id: 给这个Bean起个名字,可以随便起
    -->
    <bean id="bookDao" class="com.akbar.dao.impl.BookDaoImpl"/>
    <bean id="bookService" class="com.akbar.service.impl.BookServiceImpl"/>
</beans>

看看我的BookServiceImpl

package com.akbar.service.impl;

import com.akbar.dao.BookDao;
import com.akbar.dao.impl.BookDaoImpl;
import com.akbar.service.BookService;

public class BookServiceImpl implements BookService {

    private BookDao bookDao = new BookDaoImpl();

    @Override
    public void save() {
        bookDao.save();
        System.out.println("BookService save...");
    }
}

注意,我们还没进行依赖注入,所以如果不写new BookDaoImpl(),程序会报nullPointerException。这是因为spring不会自动给对象内部的属性注入依赖。

然后在启动类中获取IOC容器并获取Bean,然后执行:

package com.akbar;

import com.akbar.dao.BookDao;
import com.akbar.service.BookService;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class App {
    public static void main(String[] args) {
        // 获取IOC容器
        ApplicationContext app = new ClassPathXmlApplicationContext("applicationContext.xml");

        // 获取Bean
        BookDao bookDao = (BookDao) app.getBean("bookDao");
        BookService bookService = (BookService) app.getBean("bookService");

        bookService.save();
    }
}

DI 依赖注入入门

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

    <!--配置Bean-->
    <!--注意我们交给ioc容器的是实现类,不是接口,因为后续需要早对象-->
    <!--
        class: 要管理的Bean
        id: 给这个Bean起个名字,可以随便起
    -->
    <bean id="bookDao" class="com.akbar.dao.impl.BookDaoImpl"/>

    <!--配置service和dao的关系-->
    <!-- 因为service里面放dao对象,所以要在service中配置 -->
    <!-- name="bookDao"这里的bookDao是service中的成员变量bookDao -->
    <!-- ref="bookDao"这里的bookDao是id="bookDao"的Bean -->
    <bean id="bookService" class="com.akbar.service.impl.BookServiceImpl">
        <property name="bookDao" ref="bookDao"/>
    </bean>
</beans>

然后我们可以把BookServiceImpl中的new去掉了

package com.akbar.service.impl;

import com.akbar.dao.BookDao;
import com.akbar.service.BookService;

public class BookServiceImpl implements BookService {

    // 不用通过new造对象了
    private BookDao bookDao;

    // 提供对应的set方法
    public void setBookDao(BookDao bookDao) {
        this.bookDao = bookDao;
    }

    @Override
    public void save() {
        bookDao.save();
        System.out.println("BookService save...");
    }
}

配置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="bookDao" name="dao" class="com.akbar.dao.impl.BookDaoImpl"/>

    <!-- 用name属性起别名,不同的别名之间可以使用逗号,空格和分号隔开。 -->
    <!--property中的ref也可以填上面的Bean的name-->
    <bean id="bookService" name="service myService" class="com.akbar.service.impl.BookServiceImpl">
        <property name="bookDao" ref="dao"/>
    </bean>
</beans>

使用别名获取Bean

package com.akbar;

import com.akbar.dao.BookDao;
import com.akbar.service.BookService;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class App {
    public static void main(String[] args) {
        // 获取IOC容器
        ApplicationContext app = new ClassPathXmlApplicationContext("applicationContext.xml");

        // 获取Bean
        BookDao bookDao = (BookDao) app.getBean("bookDao");
        
        //bookService service myService,可以使用这三个名称获取这个Bean
        BookService bookService = (BookService) app.getBean("myService");

        bookService.save();
    }
}

Bean的作用范围控制

package com.akbar;

import com.akbar.dao.BookDao;
import com.akbar.service.BookService;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class App {
    public static void main(String[] args) {
        // 获取IOC容器
        ApplicationContext app = new ClassPathXmlApplicationContext("applicationContext.xml");

        BookService bookService1 = (BookService) app.getBean("bookService");
        BookService bookService2 = (BookService) app.getBean("bookService");

        // 默认情况下这两个Bean的内存地址是一样的,就是单例的
        System.out.println(bookService1);
        System.out.println(bookService2);
    }
}

我们可以在配置文件中进行这个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="bookDao" name="dao" class="com.akbar.dao.impl.BookDaoImpl"/>
    
    <!-- scope默认是singleton(单例的),我们可以设置成scope="prototype"多例的 -->
    <bean id="bookService" name="service" class="com.akbar.service.impl.BookServiceImpl" scope="prototype">
        <property name="bookDao" ref="bookDao"/>
    </bean>
</beans>

这样以后我们打印的Bean的地址就是不一样的。

image.png

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="bookService" name="service" class="com.akbar.service.impl.BookServiceImpl"/>
</beans>
package com.akbar.service.impl;

import com.akbar.service.BookService;

public class BookServiceImpl implements BookService {

    private BookServiceImpl() {
        System.out.println("BookServiceImpl constructor is running...");
    }

    @Override
    public void save() {
        System.out.println("BookService save...");
    }
}
package com.akbar;

import com.akbar.dao.BookDao;
import com.akbar.service.BookService;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class App {
    public static void main(String[] args) {
        // 获取IOC容器
        ApplicationContext app = new ClassPathXmlApplicationContext("applicationContext.xml");

        BookService bookService = (BookService) app.getBean("bookService");
        bookService.save();
    }
}

image.png 输出结果中可以看到,BookServiceImpl的构造方法也执行了。

spring造Bean的时候调的是无参的构造方法。

静态工厂方法造Bean

先写一下代码结构:

package com.akbar.dao;

public interface BookDao {
    void save();
}
package com.akbar.dao.impl;

import com.akbar.dao.BookDao;

public class BookDaoImpl implements BookDao {
    @Override
    public void save() {
        System.out.println("BookDao save...");
    }
}
package com.akbar.dao.factory;

import com.akbar.dao.BookDao;
import com.akbar.dao.impl.BookDaoImpl;

public class BookDaoFactory {
    public static BookDao getBookDao() {
        return new BookDaoImpl();
    }
}

App中运行:

package com.akbar;

import com.akbar.dao.BookDao;
import com.akbar.dao.factory.BookDaoFactory;

public class App {
    public static void main(String[] args) {
        BookDao bookDao = BookDaoFactory.getBookDao();
        bookDao.save();
    }
}

image.png

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

    <!--这个造出来的是BookDaoFactory的对象,我们不需要这个BookDaoFactory的对象-->
    <!--<bean id="bookDao" class="com.akbar.factory.BookDaoFactory" />-->

    <!--这个造出来的是BookDaoImpl的对象,别忘了指定factory-method="getBookDao"-->
    <bean id="bookDao" class="com.akbar.factory.BookDaoFactory" factory-method="getBookDao" />
</beans>

App中运行

package com.akbar;

import com.akbar.dao.BookDao;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class App {
    public static void main(String[] args) {
        ApplicationContext app = new ClassPathXmlApplicationContext("applicationContext.xml");
        BookDao bookDao = (BookDao) app.getBean("bookDao");
        bookDao.save();
    }
}

用实例工厂造对象

注意,这里的工厂方法不是静态的

package com.akbar.factory;

import com.akbar.dao.BookDao;
import com.akbar.dao.impl.BookDaoImpl;

public class BookDaoFactory {
    public BookDao getBookDao() {                   // 注意不是静态方法
        System.out.println("factory setup ...");
        return new BookDaoImpl();
    }
}

配置文件:

<?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-->
    <bean id="bookDaoFactory" class="com.akbar.factory.BookDaoFactory"/>

    <!--注意这里不用指定class,factory-bean要写上面BookDaoFactory的Bean-->
    <bean id="bookDao" factory-bean="bookDaoFactory" factory-method="getBookDao"/>
</beans>

然后在App里面运行:

package com.akbar;

import com.akbar.dao.BookDao;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class App {
    public static void main(String[] args) {
        ApplicationContext app = new ClassPathXmlApplicationContext("applicationContext.xml");
        BookDao bookDao = (BookDao) app.getBean("bookDao");
        bookDao.save();
    }
}

改良:

我们发现下面的配置纯粹是为了配置而配置,我们无缘无故的造了一个bookDaoFactory的Bean,有没有办法改良呢?

    <!--先造工厂的Bean-->
    <bean id="bookDaoFactory" class="com.akbar.factory.BookDaoFactory"/>

我们先创建bookDaoFactoryBean,让这个类实现spring的FactoryBean<BookDao>

package com.akbar.factory;

import com.akbar.dao.BookDao;
import com.akbar.dao.impl.BookDaoImpl;
import org.springframework.beans.factory.FactoryBean;

public class BookDaoFactoryBean implements FactoryBean<BookDao> {

    // 代替原来实例工厂中创建对象的方法
    @Override
    public BookDao getObject() throws Exception {
        return new BookDaoImpl();
    }

    // 指定什么类型的Bean
    @Override
    public Class<?> getObjectType() {
        return BookDao.class;
    }

    // 指定造出来的Bean单例的还是非单例的
    @Override
    public boolean isSingleton() {
        return true;        // true表示单例的
    }
}

然后配置文件怎么写呢?

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

    <!--使用factoryBean实例化-->
    <bean id="bookDao" class="com.akbar.factory.BookDaoFactoryBean" />
</beans>

App中运行:

package com.akbar;

import com.akbar.dao.BookDao;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class App {
    public static void main(String[] args) {
        ApplicationContext app = new ClassPathXmlApplicationContext("applicationContext.xml");
        BookDao bookDao = (BookDao) app.getBean("bookDao");
        bookDao.save();
    }
}

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

    <!--这里用init-method和destroy-method分别制定初始化和销毁方法-->
    <bean id="bookDao" class="com.akbar.dao.impl.BookDaoImpl" init-method="init" destroy-method="destroy"/>

    <bean id="bookService" class="com.akbar.service.impl.BookServiceImpl">
        <property name="bookDao" ref="bookDao"/>
    </bean>
</beans>

BookDaoImpl:

package com.akbar.dao.impl;

import com.akbar.dao.BookDao;

public class BookDaoImpl implements BookDao {
    @Override
    public void save() {
        System.out.println("BookDao save...");
    }

    // 表示Bean初始化对应的操作
    public void init() {
        System.out.println("BookDao init...");
    }

    // 表示Bean初始化前对应的操作
    public void destroy() {
        System.out.println("BookDao destroy...");
    }
}

BookServiceImpl:

package com.akbar.service.impl;

import com.akbar.dao.BookDao;
import com.akbar.service.BookService;

public class BookServiceImpl implements BookService {
    BookDao bookDao;

    public void setBookDao(BookDao bookDao) {
        this.bookDao = bookDao;
    }

    @Override
    public void save() {
        bookDao.save();
    }
}

App:

package com.akbar;

import com.akbar.service.BookService;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class App {
    public static void main(String[] args) {
        // 注意这里左边用的是ClassPathXmlApplicationContext,不是ApplicationContext
        ClassPathXmlApplicationContext app = new ClassPathXmlApplicationContext("applicationContext.xml");
        BookService bookService = (BookService) app.getBean("bookService");
        bookService.save();
        
        // 手动关闭虚拟机,这样Bean的销毁就有机会机会
        app.close();
    }
}

但是上面的app.close有点暴力,所以不推荐用这个。

更推荐使用app.registerShutdownHook();钩子。

package com.akbar;

import com.akbar.service.BookService;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class App {
    public static void main(String[] args) {
        // 注意这里左边用的是ClassPathXmlApplicationContext,不是ApplicationContext
        ClassPathXmlApplicationContext app = new ClassPathXmlApplicationContext("applicationContext.xml");
        BookService bookService = (BookService) app.getBean("bookService");
        bookService.save();

        // 手动关闭虚拟机,这样Bean的销毁就有机会机会
        app.registerShutdownHook();
    }
}

更规范的写法:

更规范的写法是让BookDaoImpl除了实现自己的接口BookDao,还实现InitializingBeanDisposableBean

package com.akbar.dao.impl;

import com.akbar.dao.BookDao;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.InitializingBean;

public class BookDaoImpl implements BookDao, InitializingBean, DisposableBean {
    @Override
    public void save() {
        System.out.println("BookDao save...");
    }

    @Override
    public void afterPropertiesSet() throws Exception {
        System.out.println("BookDao init...");
    }

    @Override
    public void destroy() throws Exception {
        System.out.println("BookDao destroy...");
    }
}

别忘了去掉配置文件中的init-methoddestroy-method

<?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="bookDao" class="com.akbar.dao.impl.BookDaoImpl"/>

    <bean id="bookService" class="com.akbar.service.impl.BookServiceImpl">
        <property name="bookDao" ref="bookDao"/>
    </bean>
</beans>

image.png

依赖注入方式

依赖注入描述了容器中建立Bean与Bean之间依赖关系的过程,如果Bean运行需要的是数字字符串呢?

image.png

setter方法注入

setter方法注入就是提供对应的setter方法:

package com.akbar.service.impl;

import com.akbar.dao.BookDao;
import com.akbar.dao.UserDao;
import com.akbar.service.BookService;

public class BookServiceImpl implements BookService {
    BookDao bookDao;
    UserDao userDao;

    public void setBookDao(BookDao bookDao) {
        this.bookDao = bookDao;
    }

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

    @Override
    public void save() {
        bookDao.save();
        userDao.insert();
    }
}

然后在配置文件中用ref指定要引用的对象,用name指定对应的成员变量名,比如下面配置中的name配置的是BookServiceImpl中的bookDao:

<?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="bookDao" class="com.akbar.dao.impl.BookDaoImpl"/>

    <bean id="userDao" class="com.akbar.dao.impl.UserDaoImpl" />

    <bean id="bookService" class="com.akbar.service.impl.BookServiceImpl">
        <property name="bookDao" ref="bookDao"/>
        <property name="userDao" ref="userDao"/>
    </bean>
</beans>

Java自带的类型怎么注入呢,比如int,string等:

package com.akbar.dao.impl;

import com.akbar.dao.UserDao;

public class UserDaoImpl implements UserDao {
    // 注入基本类型
    private int connectionNumber;
    private String connectionName;

    public void setConnectionNumber(int connectionNumber) {
        this.connectionNumber = connectionNumber;
    }

    public void setConnectionName(String connectionName) {
        this.connectionName = connectionName;
    }

    @Override
    public void insert() {
        System.out.println("UserDao insert..., connectionNumber:" + connectionNumber + ",connectionName:" + connectionName);
    }
}

配置文件

<?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="bookDao" class="com.akbar.dao.impl.BookDaoImpl"/>

    <!--配置基本类型-->
    <bean id="userDao" class="com.akbar.dao.impl.UserDaoImpl">
        <property name="connectionNumber" value="10"/>
        <property name="connectionName" value="Mysql"/>
    </bean>

    <bean id="bookService" class="com.akbar.service.impl.BookServiceImpl">
        <property name="bookDao" ref="bookDao"/>
        <property name="userDao" ref="userDao"/>
    </bean>
</beans>

构造器注入

很简单,只是把setter方法改成了构造器而已:

BookServiceImpl:

package com.akbar.service.impl;

import com.akbar.dao.BookDao;
import com.akbar.dao.UserDao;
import com.akbar.service.BookService;

public class BookServiceImpl implements BookService {
    BookDao bookDao;
    UserDao userDao;

    // 构造器注入
    public BookServiceImpl(BookDao bookDao, UserDao userDao) {
        this.bookDao = bookDao;
        this.userDao = userDao;
    }

    @Override
    public void save() {
        bookDao.save();
        userDao.insert();
    }
}

UserDaoImpl

package com.akbar.dao.impl;

import com.akbar.dao.UserDao;

public class UserDaoImpl implements UserDao {
    // 注入基本类型
    private int connectionNumber;
    private String connectionName;

    // 构造器注入
    public UserDaoImpl(int connectionNumber, String connectionName) {
        this.connectionNumber = connectionNumber;
        this.connectionName = connectionName;
    }

    @Override
    public void insert() {
        System.out.println("UserDao insert..., connectionNumber:" + connectionNumber + ",connectionName:" + connectionName);
    }
}

然后是配置文件:

<?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="bookDao" class="com.akbar.dao.impl.BookDaoImpl"/>

    <!--配置基本类型-->
    <bean id="userDao" class="com.akbar.dao.impl.UserDaoImpl">
        <constructor-arg name="connectionNumber" value="10"/>
        <constructor-arg name="connectionName" value="Mysql"/>
    </bean>

    <bean id="bookService" class="com.akbar.service.impl.BookServiceImpl">
        <constructor-arg name="bookDao" ref="bookDao"/>
        <constructor-arg name="userDao" ref="userDao"/>
    </bean>
</beans>

注意,constructor-arg中的name属性对应的是构造方法中的形参

1.解决构造方法形参名称耦合的问题:

<!--配置基本类型-->
<bean id="userDao" class="com.akbar.dao.impl.UserDaoImpl">
    <constructor-arg name="connectionNumber" value="10"/>
    <constructor-arg name="connectionName" value="Mysql"/>
</bean>

通过观察上面的配置代码发现,如果我们在写类的构造方法的时候万一写错了形参名称,就需要同步改动配置文件,有没有办法解决这个问题?

有,我们配置文件中不指定形参名,而指定参数类型(使用type属性):

<!--配置基本类型-->
<bean id="userDao" class="com.akbar.dao.impl.UserDaoImpl">
    <constructor-arg type="int" value="10"/>
    <constructor-arg type="java.lang.String" value="Mysql"/>
</bean>

2. 如果有多个相同类型的变量怎么办?

我们可以指定形参的位置(使用index属性)来解决这个问题:

<!--配置基本类型-->
<bean id="userDao" class="com.akbar.dao.impl.UserDaoImpl">
    <constructor-arg index="0" value="10"/>
    <constructor-arg index="1" value="Mysql"/>
</bean>

不同的依赖注入方法选择建议

image.png

而且构造器注入有一定的耦合度,不太推荐。

依赖自动装配

什么是依赖自动装配:

image.png

1.按类型装配:

package com.akbar.service.impl;

import com.akbar.dao.UserDao;
import com.akbar.service.BookService;

public class BookServiceImpl implements BookService {
    UserDao userDao;

    // setter方法注入,自动装配这个一定要提供的
    public void setUserDao(UserDao userDao) {
        this.userDao = userDao;
    }

    @Override
    public void save() {
        userDao.insert();
    }
}

配置文件中我们不用写<propertiers/>标签了:

<?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="userDao" class="com.akbar.dao.impl.UserDaoImpl"/>

    <!--按类型装配byType,会找UserDao类型的Bean,然后把对应的对象依赖注入进去-->
    <bean id="bookService" class="com.akbar.service.impl.BookServiceImpl" autowire="byType"/>
</beans>

Spring 会执行以下步骤:

1️⃣ 获取 BookServiceImpl 中所有需要注入的属性(通过 setter 方法分析)。
2️⃣ 识别 setUserDao(UserDao userDao) 的参数类型是 UserDao(接口)。
3️⃣ 在 IOC 容器中查找与 UserDao 类型匹配的 Bean。
4️⃣ 如果有且仅有一个 Bean 的类型与 UserDao 类型兼容(包括其实现类),Spring 会将该 Bean 注入到 userDao 属性中。

容器中必须有且仅有一个与依赖属性类型匹配的 Bean,否则 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">

    <!--有两个相同类型的Bean,spring会报错-->
    <bean id="userDao1" class="com.akbar.dao.impl.UserDaoImpl"/>
    <bean id="userDao2" class="com.akbar.dao.impl.UserDaoImpl"/>

    <!--按类型装配byType,会找UserDao类型的Bean,然后把对应的对象依赖注入进去-->
    <bean id="bookService" class="com.akbar.service.impl.BookServiceImpl" autowire="byType"/>
</beans>

2. 按名称装配:

Spring 在自动注入依赖时,会根据属性名去容器中查找与之同名的 Bean,并将该 Bean 注入到对应的属性中。

比如:

 <bean id="userDao" class="com.akbar.dao.impl.UserDaoImpl"/>

<bean id="bookService" class="com.akbar.service.impl.BookServiceImpl" autowire="byName"/>
package com.akbar.service.impl;

import com.akbar.dao.UserDao;
import com.akbar.service.BookService;

public class BookServiceImpl implements BookService {
    UserDao userDao;

    // setter方法注入,自动装配这个一定要提供的
    public void setUserDao(UserDao userDao) {
        this.userDao = userDao;
    }

    @Override
    public void save() {
        userDao.insert();
    }
}

识别 BookServiceImpl 中所有需要注入的属性(通过 Setter 方法)。

比如上面的代码中,Spring 确定需要注入的属性名是:userDao

完整配置代码:

<?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="userDao" class="com.akbar.dao.impl.UserDaoImpl"/>

    <!--BookServiceImpl中找到属性userDao,如果上面的Bean的id="userDao"和userDao相同,就可以自动装配-->
    <bean id="bookService" class="com.akbar.service.impl.BookServiceImpl" autowire="byName"/>
</beans>

依赖自动装配的特征:

image.png

集合注入

代码示例:

package com.akbar.dao.impl;

import com.akbar.dao.BookDao;

import java.util.*;

public class BookDaoImpl implements BookDao{
    private int[] array;
    private List<String> list;
    private Set<String> set;
    private Map<String, String> map;
    private Properties properties;

    public void setArray(int[] array) {
        this.array = array;
    }

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

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

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

    public void setProperties(Properties properties) {
        this.properties = properties;
    }

    @Override
    public void save() {
        System.out.println("bookDao save...");

        System.out.println("遍历数组:" + Arrays.toString(array));
        System.out.println("遍历list:" + list);
        System.out.println("遍历set:" + set);
        System.out.println("遍历map:" + map);
        System.out.println("遍历properties:"  + properties);
    }
}

什么是Properties类型:

Properties 是继承自 Hashtable<Object,Object> 的类,用于以键值对(通常是字符串)的形式管理配置属性,是 Java 中用于配置文件处理的常用工具类。

配置文件:

<?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="bookDao" class="com.akbar.dao.impl.BookDaoImpl">
        <!--数组-->
        <property name="array">
            <array>
                <value>100</value>
                <value>200</value>
                <value>300</value>
            </array>
        </property>

        <!--list集合-->
        <property name="list">
            <list>
                <value>akbar</value>
                <value>saipulla</value>
                <value>gulhuma</value>
            </list>
        </property>

        <!--set集合-->
        <property name="set">
            <set>
                <value>nihao</value>
                <value>hello</value>
                <value>nice</value>
                <value>nice</value>
            </set>
        </property>

        <!--map集合-->
        <property name="map">
            <map>
                <entry key="country" value="China"/>
                <entry key="province" value="Hubei"/>
                <entry key="city" value="Yichang"/>
            </map>
        </property>

        <!--Properties类型-->
        <property name="properties">
            <props>
                <prop key="country">China</prop>
                <prop key="province">Hubei</prop>
                <prop key="city">Yichang</prop>
            </props>
        </property>
    </bean>
</beans>

数据源对象管理

druid数据库连接池

坐标:

<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>druid</artifactId>
    <version>1.2.25</version>
</dependency>

applicationProperties.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">

    <!--使用setter方法注入-->
    <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
        <property name="driverClassName" value="com.mysql.jdbc.Driver"/>
        <property name="url" value="jdbc:mysql://localhost:3306/big_event"/>
        <property name="username" value="root"/>
        <property name="password" value="#123456*"/>
    </bean>
</beans>

App中运行:

package com.akbar;

import com.akbar.dao.BookDao;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

import javax.sql.DataSource;

public class App {
    public static void main(String[] args) {
        // 注意这里左边用的是ClassPathXmlApplicationContext,不是ApplicationContext
        ApplicationContext app = new ClassPathXmlApplicationContext("applicationContext.xml");
        DataSource dataSource = (DataSource) app.getBean("dataSource");
        System.out.println(dataSource);
    }
}

c3p0数据库连接池

依赖坐标:

<dependency>
    <groupId>c3p0</groupId>
    <artifactId>c3p0</artifactId>
    <version>0.9.1.2</version>
</dependency>

<dependency>
    <groupId>com.mysql</groupId>
    <artifactId>mysql-connector-j</artifactId>
    <version>9.3.0</version>
</dependency>

applicationProperties.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="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
        <property name="driverClass" value="com.mysql.jdbc.Driver"/>
        <property name="jdbcUrl" value="jdbc:mysql://localhost:3306/big_event"/>
        <property name="user" value="root"/>
        <property name="password" value="#123456*"/>
    </bean>
</beans>

App中运行:

package com.akbar;

import com.akbar.dao.BookDao;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

import javax.sql.DataSource;

public class App {
    public static void main(String[] args) {
        // 注意这里左边用的是ClassPathXmlApplicationContext,不是ApplicationContext
        ApplicationContext app = new ClassPathXmlApplicationContext("applicationContext.xml");
        DataSource dataSource = (DataSource) app.getBean("dataSource");
        System.out.println(dataSource);
    }
}

加载properties文件

视频地址:

www.bilibili.com/video/BV1Fi…

加载xml配置文件的方法

加载类路径下的配置文件

package com.akbar;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

import javax.sql.DataSource;

public class App {
    public static void main(String[] args) {
        // 这就是加载类路径下的配置文件
        ApplicationContext app = new ClassPathXmlApplicationContext("applicationContext.xml");
        DataSource dataSource = (DataSource) app.getBean("dataSource");
        System.out.println(dataSource);
    }
}

根据绝对路径来加载

// 绝对路径加载
ApplicationContext app = new FileSystemXmlApplicationContext("E:\Java-Project\practice\ssm\ssm01\src\main\resources\applicationContext.xml");

获取Bean的方式

一般获取方式

DataSource dataSource = (DataSource) app.getBean("dataSource");

这种获取方式需要强转

指定类型的字节码文件

DataSource dataSource = app.getBean("dataSource", DataSource.class);

直接指定类型

DataSource dataSource = app.getBean(DataSource.class);

容器中只能有一种Bean。

注解开发

首先需要写下面的配置:

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

@Component

@Component相当于

<bean id="userDao" class="com.akbar.dao.impl.UserDaoImpl"/>

@Component里面可以给Bean起个名字:

@Component("userDao")

如果@Component没有写Bean的名字,获取Bean的时候要用类型获取:

UserDao userDao = (UserDao) app.getBean(UserDao.class);

如果起了个名字,就可以根据名字获取:

UserDao userDao = (UserDao) app.getBean("userDao");

@Component衍生注解

下面的这些注解本质上跟@Component差不多,就是名字更直观一些:

  • @Sservice:在Service层实现类上加
  • @Repository:在持久层实现类上加
  • @Controller:在表现层上加

纯注解开发

我们可以建一个包叫config,然后里面建一个类:SpringCofig

package com.akbar.config;

import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;

@Configuration
// @Configuration注解相当于整个xml配置文件
@ComponentScan("com.akbar")
// @ComponentScan("com.akbar")这个相当于xml配置文件中的 <context:component-scan base-package="com.akbar"/>
public class SpringConfig {
}

现在可以把xml配置文件删掉了

然后启动类怎么写呢?

package com.akbar;

import com.akbar.config.SpringConfig;
import com.akbar.dao.UserDao;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

public class App {
    public static void main(String[] args) {
        // 注意这里AnnotationConfigApplicationContext(SpringConfig.class);
        ApplicationContext app =new AnnotationConfigApplicationContext(SpringConfig.class);
        UserDao userDao = (UserDao) app.getBean("userDao");
        System.out.println(userDao);
    }
}

如果@ComponentScan()指定多个包路径,可以这样写

@ComponentScan({"com.akbar.dao", "com.akbar.service"})

@Scope配置Bean的依赖范围

package com.akbar.dao.impl;

import com.akbar.dao.UserDao;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Repository;

@Repository
@Scope("singleton") // 单例
//@Scope("prototype") // 非单例
public class UserDaoImpl implements UserDao {

    @Override
    public void insert() {
        System.out.println("UserDao insert...,");
    }
}

声明周期

需要先导入下面的依赖:

<dependency>
    <groupId>javax.annotation</groupId>
    <artifactId>javax.annotation-api</artifactId>
    <version>1.3.2</version>
</dependency>

然后进行编写:

package com.akbar.dao.impl;

import com.akbar.dao.UserDao;
import org.springframework.stereotype.Repository;

import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;

@Repository
public class UserDaoImpl implements UserDao {

    @Override
    public void insert() {
        System.out.println("UserDao insert...,");
    }

    // 初始化时运行
    // 相当于构造方法后运行
    @PostConstruct
    public void init() {
        System.out.println("UserDao init...,");
    }
    
    // 销毁前运行
    @PreDestroy
    public void destroy() {
        System.out.println("UserDao destroy...,");
    }
}

启动类:

package com.akbar;

import com.akbar.config.SpringConfig;
import com.akbar.dao.UserDao;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

public class App {
    public static void main(String[] args) {
        AnnotationConfigApplicationContext app =new AnnotationConfigApplicationContext(SpringConfig.class);
        UserDao userDao = (UserDao) app.getBean(UserDao.class);
        System.out.println(userDao);
        app.close();
    }
}

依赖注入

@Avotowrite

package com.akbar.service.impl;

import com.akbar.dao.BookDao;
import com.akbar.service.BookService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

@Service
public class BookServiceImpl implements BookService {

    // 自动装配
    @Autowired
    private BookDao bookDao;

    @Override
    public void save() {
        bookDao.save();
        System.out.println("BookServiceImpl save");
    }
}

image.png

需要提供无参构造方法,不然造不出对象。

简单类型注入:

我们可以使用@Value注解赋值

package com.akbar.service.impl;

import com.akbar.dao.BookDao;
import com.akbar.service.BookService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;

@Service
public class BookServiceImpl implements BookService {

    // 自动装配
    @Autowired
    private BookDao bookDao;

    // 简单类型装配
    @Value("akbar")
    private String name;

    @Override
    public void save() {
        bookDao.save();
        System.out.println("BookServiceImpl save " + name);
    }
}

我们利用这个方法可以注入配置文件中的内容

image.png

package com.akbar.config;

import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;

@Configuration
@ComponentScan("com.akbar")
// 这里把配置文件的名称写上去
@PropertySource("jdbc.properties")
public class SpringConfig {
}
package com.akbar.service.impl;

import com.akbar.dao.BookDao;
import com.akbar.service.BookService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;

@Service
public class BookServiceImpl implements BookService {

    // 自动装配
    @Autowired
    private BookDao bookDao;

    // 使用配置文件中的值
    @Value("${name}")
    private String name;

    @Override
    public void save() {
        bookDao.save();
        System.out.println("BookServiceImpl save " + name);
    }
}

配置文件的完整写法:

@PropertySource("classpath:jdbc.properties")

第三方的Bean管理

比如配置Druid:

在spring的配置类中进行手动编写:

package com.akbar.config;

import com.alibaba.druid.pool.DruidDataSource;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import javax.sql.DataSource;

@Configuration
public class SpringConfig {

    // 定义一个方法获得要管理的Bean
    // 让这个方法的返回值定义成一个Bean
    //@Bean("dataSource")
    @Bean
    public DataSource dataSource() {
        DruidDataSource ds = new DruidDataSource();
        ds.setDriverClassName("com.mysql.jdbc.Driver");
        ds.setUrl("jdbc:mysql://localhost:3306/book?useSSL=false");
        ds.setUsername("root");
        ds.setPassword("123456");
        return ds;
    }
}

然后在启动类中使用:

package com.akbar;

import com.akbar.config.SpringConfig;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

import javax.sql.DataSource;

public class App {
    public static void main(String[] args) {
        ApplicationContext app =new AnnotationConfigApplicationContext(SpringConfig.class);
        DataSource ds = app.getBean(DataSource.class);
        System.out.println(ds);
    }
}

这种spring的配置类中编写第三方的配置信息不推荐,有没有好的解决办法,我们可以把第三方依赖单独写一个配置文件:

单独建一个JdbcConfig类:

package com.akbar.config;

import com.alibaba.druid.pool.DruidDataSource;
import org.springframework.context.annotation.Bean;

import javax.sql.DataSource;

public class JdbcConfig {
    // 定义一个方法获得要管理的Bean
    // 让这个方法的返回值定义成一个Bean
    //@Bean("dataSource")
    @Bean
    public DataSource dataSource() {
        DruidDataSource ds = new DruidDataSource();
        ds.setDriverClassName("com.mysql.jdbc.Driver");
        ds.setUrl("jdbc:mysql://localhost:3306/book?useSSL=false");
        ds.setUsername("root");
        ds.setPassword("123456");
        return ds;
    }
}

然后在spring的配置文件中用@Import来导入:

package com.akbar.config;

import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;

@Configuration
// 导入别的配置类
@Import(JdbcConfig.class)
public class SpringConfig {

}

如果第三方Bean需要别的Bean怎么办?

package com.akbar.config;

import com.akbar.dao.BookDao;
import com.alibaba.druid.pool.DruidDataSource;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;

import javax.sql.DataSource;

public class JdbcConfig {
    @Value("com.mysql.jdbc.Driver")
    private String driverClassName;
    @Value("jdbc:mysql://localhost:3306/book?useSSL=false")
    private String url;
    @Value("root")
    private String username;
    @Value("123456")
    private  String password;

    @Bean
    public DataSource dataSource(BookDao bookDao) {     // 这里直接用形参的方式写就可以,spring自动进行装配
        System.out.println(bookDao);
        DruidDataSource ds = new DruidDataSource();
        ds.setDriverClassName(driverClassName);
        ds.setUrl(url);
        ds.setUsername(username);
        ds.setPassword(password);
        return ds;
    }
}
package com.akbar.config;

import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;

@Configuration
@ComponentScan("com.akbar")
@Import(JdbcConfig.class)
public class SpringConfig {

}

启动类:

package com.akbar;

import com.akbar.config.SpringConfig;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

import javax.sql.DataSource;

public class App {
    public static void main(String[] args) {
        ApplicationContext app =new AnnotationConfigApplicationContext(SpringConfig.class);
        DataSource ds = app.getBean(DataSource.class);
        System.out.println(ds);
    }
}

spring整合mybatis和junit

视频链接:

www.bilibili.com/video/BV1Fi…

AOP面向切面编程

AOP的作用:不惊动原始设计的基础上,对代码进行增强,

这是spring的无侵入式的编程思想。

image.png

AOP入门

spring的依赖中默认就包含aop的依赖:

image.png

所以,我们只需要到以下aspect包就可以了:

<dependency>
    <groupId>org.aspectj</groupId>
    <artifactId>aspectjweaver</artifactId>
    <version>1.9.7</version>
</dependency>

下面以打印系统时间为例,演示aop:

package com.akbar.dao.impl;

import com.akbar.dao.BookDao;
import org.springframework.stereotype.Repository;

@Repository
public class BookDaoImpl implements BookDao{

    @Override
    public void save() {
        System.out.println("BookDaoImpl save");
    }

    // 这个方法中的执行的时候(当里面的逻辑只想之前)用aop获取系统时间
    @Override
    public void update() {
        System.out.println("bookDaoImpl update ");
    }
}

在aop包下创建MyAdvice类:

package com.akbar.aop;

import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;

@Component      // 必须被spring控制
@Aspect          //告诉spring 当iop来处理,不要当普通的Bean处理
public class MyAdvice {

    // 1.定义切入点
    // 当执行到void com.akbar.dao.BookDao.update()这个方法的时候做什么
    @Pointcut("execution(void com.akbar.dao.BookDao.update())")
    private void pt() {

    }

    // 2.共性功能
    // 方法的什么位置执行
    @Before("pt()")
    public void message() {
        System.out.println(System.currentTimeMillis());
    }
}

image.png

别忘了在spring的配置类中告诉spring,我们是用注解的方式定义的aop:

package com.akbar.config;

import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;

@Configuration
@ComponentScan("com.akbar")
@EnableAspectJAutoProxy()       // 告诉spring,我们是用注解写的aop
public class SpringConfig {

}

启动类:

package com.akbar;

import com.akbar.config.SpringConfig;
import com.akbar.dao.BookDao;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

public class App {
    public static void main(String[] args) {
        ApplicationContext app =new AnnotationConfigApplicationContext(SpringConfig.class);
        BookDao bookDao = app.getBean(BookDao.class);
        bookDao.update();
    }
}

切入点表达式

image.png

Aop通知类型

image.png

  • 前置通知:@Before
package com.akbar.aop;

import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;

@Component      // 必须被spring控制
@Aspect          //告诉spring 当iop来处理,不要当普通的Bean处理
public class MyAdvice {

    // 1.定义切入点
    // 当执行到void com.akbar.dao.BookDao.update()这个方法的时候做什么
    @Pointcut("execution(void com.akbar.dao.BookDao.update())")
    private void pt() {

    }

    // 2.共性功能
    // 方法的什么位置执行
    @Before("pt()")
    public void message() {
        System.out.println(System.currentTimeMillis());
    }
}
  • 后置通知:@After
package com.akbar.aop;

import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;

@Component      // 必须被spring控制
@Aspect          //告诉spring 当iop来处理,不要当普通的Bean处理
public class MyAdvice {

    // 1.定义切入点
    // 当执行到void com.akbar.dao.BookDao.update()这个方法的时候做什么
    @Pointcut("execution(void com.akbar.dao.BookDao.update())")
    private void pt() {

    }

    // 2.共性功能
    // 方法的什么位置执行
    @After("pt()")
    public void message() {
        System.out.println(System.currentTimeMillis());
    }
}
  • 环绕通知:@Around
package com.akbar.aop;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;

@Component      // 必须被spring控制
@Aspect          //告诉spring 当iop来处理,不要当普通的Bean处理
public class MyAdvice {

    // 1.定义切入点
    // 当执行到void com.akbar.dao.BookDao.update()这个方法的时候做什么
    @Pointcut("execution(void com.akbar.dao.BookDao.update())")
    private void pt() {

    }

    // 2.共性功能
    // 方法的什么位置执行
    // 原始方法可能有返回值,所以@Around通知的返回值应该写Object
    @Around("pt()")
    public Object message(ProceedingJoinPoint joinPoint) throws Throwable {
        System.out.println("before " + System.currentTimeMillis());

        // 表示对原始操作的调用,拿到它的返回值
        Object proceed = joinPoint.proceed();

        System.out.println("after " + System.currentTimeMillis());

        // 然后把返回值返回出去
        return proceed;
    }
}

image.png

获取连接点的信息:

package com.akbar.aop;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;

@Component      // 必须被spring控制
@Aspect          //告诉spring 当iop来处理,不要当普通的Bean处理
public class MyAdvice {

    // 1.定义切入点
    // 当执行到void com.akbar.dao.BookDao.update()这个方法的时候做什么
    @Pointcut("execution(void com.akbar.dao.BookDao.update())")
    private void pt() {

    }

    // 2.共性功能
    // 方法的什么位置执行
    // 原始方法可能有返回值,所以@Around通知的返回值应该写Object
    @Around("pt()")
    public Object message(ProceedingJoinPoint joinPoint) throws Throwable {

        // joinPoint就是链接点,我们可以通过这个连接点获取方法各种信息
        Signature signature = joinPoint.getSignature();
        System.out.println(signature.getDeclaringType());   // 输出interface com.akbar.dao.BookDao
        System.out.println(signature.getName());            // 输出update,也就是方法名
        System.out.println(signature.getDeclaringTypeName());   // com.akbar.dao.BookDao

        System.out.println("before " + System.currentTimeMillis());

        // 表示对原始操作的调用,拿到它的返回值
        Object proceed = joinPoint.proceed();

        System.out.println("after " + System.currentTimeMillis());

        // 然后把返回值返回出去
        return proceed;
    }
}

可以慢慢研究:

// joinPoint就是链接点,我们可以通过这个连接点获取方法各种信息
Signature signature = joinPoint.getSignature();
System.out.println(signature.getDeclaringType());
// 输出interface com.akbar.dao.BookDao
System.out.println(signature.getName());
// 输出update,也就是方法名
System.out.println(signature.getDeclaringTypeName());
// com.akbar.dao.BookDao
  • 返回后通知:@AfterReturning

没有抛异常,正常结束以后运行。

  • 抛异常以后执行:@AfterThrowing

Spring事务

视频地址:

www.bilibili.com/video/BV1Fi…

Spring MVC

什么是spring MVC?

image.png

@ResponseBody

@ResponseBody 表示:
将 Controller 方法的返回值** 直接作为 HTTP 响应体返回给前端,而不是跳转到视图(页面)。

我们以前controller类上经常写@ResponseBody@Controller,但是这个可以简化:

@RestController // 等价于 @Controller + @ResponseBody
public class UserController {

    @RequestMapping("/hello")
    @ResponseBody
    public String hello() {
        return "Hello Spring!";
    }

    @RequestMapping("/user")
    @ResponseBody
    public User getUser() {
        return new User("张三", 20);
    }
}

异常处理

package com.akbar.exception;

import com.akbar.util.Result;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;

@RestControllerAdvice   // 声明这个类是处理异常的类
public class GlobalExceptionHandler {
    // 想处理什么异常,这里要指定
    @ExceptionHandler(Exception.class)
    public void handleException(Exception e) {
        System.out.printl("出异常了");
    }
}

注意,要保证这个类被spring扫描到。
这个全局异常处理的思想是,把所有异常都抛到表现层controller,由controller统一处理。

给前端返回个性化信息:

package com.akbar.exception;

import com.akbar.util.Result;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;

@RestControllerAdvice   // 声明这个类是处理异常的类
public class GlobalExceptionHandler {
    // 想处理什么异常,这里要指定
    @ExceptionHandler(Exception.class)
    public Result handleException(Exception e) {
        return Result.error("出异常了");
    }
}

项目中的异常分类

image.png