Spring学习入门:IoC、DI、bean配置和实例化、依赖注入、容器、注解开发、AOP、整合MyBatis、事务...【建议收藏】

壹、核心概念和注解:

1.1. IOC和DI

IOC(Inversion of Control)控制反转:对象的创建控制权由程序转移到外部,这种思想称为控制反转。/使用对象时,由主动new产生对象转换为由外部提供对象,此过程种对象创建控制权由程序转移到外部,此思想称为控制反转。/Spring提供了一个容器,称为IoC容器,用来充当Ioc思想中的“外部”。/IoC容器负责对象的创建、初始化等一系列工作,被创建或被管理的对象在IoC容器中统称为Bean。

DI(Dependency Injection)依赖注入:在容器中建立bean与bean之间的依赖关系的整个过程,,称为依赖注入。

Spring实现充分解耦:使用IoC容器管理bean+在IoC容器内将有依赖关系的bean进行关系绑定。

实现效果:使用对象时不仅可以直接从IoC容器中获取,并且获取到的bean已经绑定了所有的依赖关系。

1.2 Bean

Bean是由Spring容器管理的对象实例。

1.3 AOP

AOP(Aspect Oriented Programming)面向切面编程,是一种编程范式,指导开发者如何组织程序结构。

作用:在不惊动原始设计的基础上为其进行功能增强。

核心本质:代理(Proxy)模式。

连接点(JoinPoint):程序执行过程中的任意位置,粒度为执行方法、抛出异常、设置变量等。

在SpringAOP中,理解为任意方法的执行。

切入点(Pointcut):匹配连接点的式子,具有共性功能的方法描述。在SpringAOP中,一个切入点可以只描述一个具体方法,也可以匹配多个方法。

一个具体方法:com.itheima.dao包下的BookDao接口中的无形参无返回值的sava方法。

匹配多个方法:所有的sava方法,所有的get开头的方法,所有以Dao结尾的接口中的任意方法,所有带有一个参数的方法。

通知(Advice):若干个方法的共性功能在切入点处执行的操作,最终体现为一个方法。

在SpringAOP中,功能最终以方法的形式呈现。

通知类:定义通知的类。

切面(Aspect):描述通知与切入点的对应关系。

目标对象(Target):原始功能去掉共性功能对应的类产生的对象,这种对象是无法直接完成最终工作的。

代理(Proxy):目标对象无法直接完成工作,需要对其进行功能回填,通过原始对象的代理对象实现。

1.4 事务

事务作用:在数据层保障一系列的数据库操作同成功同失败。

Spring事务作用:在数据层或业务层保障一系列的数据库操作同成功同失败。

1.5 核心注解

1. @Configuration:用于设定当前类为配置类,用于代替配置文件。

2. @Controller:用于表现层(用于处理用户界面和用户输入)bean定义。

3. @Service:用于业务层(用于实现业务逻辑的处理和业务规则的执行)bean定义。

4. @Repository:用于数据层(负责与底层的数据存储系统进行交互)bean定义。

5. @Bean:将方法的返回值作为一个Bean注册到容器中,并将其纳入到Spring的管理和依赖注入体系中

6. @PropertySource:用于指定外部属性文件的位置。可以将外部的.properties或.yml文件加载到Spring的环境中。

7. @Import:用于将其他配置类或组件引入到当前配置类中(Config结尾的是配置类)。

贰、重要知识点

一、Bean的基础

1.1 <property>标签

<property>标签表示配置当前bean的属性,name属性表示配置哪一个具体的属性,ref属性表示参照哪一个bean。

1.2 给bean起别名

在<bean>标签的name属性中指定名称,多个别名之间可以用逗号、分号或者空格分隔。
在<>

1.3 bean的作用范围

通俗地说:看造的对象是单例的还是非单例的。

在<bean>标签的scope属性中进行修改,singleton是单例,prototype是非单例,默认是单例的。

使用FactoryBean实例化bean,想调整单例非单例:

public boolean isSingleton(){
    return true; //表示单例
}

Spring帮我们管理的是可以复用的对象,提高我们的效率。

适合交给容器进行管理的bean(可以反复用):表现层对象,业务层对象service,数据层对象dao,工具对象。不适合交给容器进行管理的bean(有状态的,会记录成员变量的属性值):封装实体的域对象。

1.4 bean的实例化

私有的东西能访问是通过反射。

方法1:通过构造方法来实例化对象

<bean id="bookDao" class="com.itheima.dao.impl.BookDaoImpl"/>

方法2:使用静态工厂创建对象

class配的是工厂类名,还需要添加工厂中真正用来造对象的方法名:

<bean id="orderDao" class="com.itheima.factory.OrderDaoFactory" factory-method="getOrderDao">

方法3:实例工厂初始化bean

先把工厂对象造出来:

<bean id="userFactory" class="com.itheima.factory.UserDaoFactory"/>

factory-bean写工厂的实例在哪,factory-method写工厂中的方法名:

<bean id="userDao factory-method="getUserDao" factory-bean="userFactory">

方法4:使用FactoryBean实例化bean

public class UserDaoFactoryBean implements FactoryBean<UserDao>{
    //代替原始实例工厂中创建对象的方法
    @Override
    public UserDao getObject() throws Exception {
        return new UserDaoImpl();
    }

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

1.5 bean的生命周期

bean生命周期:bean从创建到消亡的完整过程

初始化容器:1. 创建对象(内存分配)2.执行构造方法 3.执行属性诸如(set操作)4.执行bean初始化方法 / 使用bean:执行业务操作 / 关闭/销毁容器:执行bean销毁方法

在<bean>标签里,init-method属性填写初始化的方法,destroy-method填写销毁的方法。

想要调用销毁方法还要调用容器close方法(更加暴力),也可以调用registerShutdownHook方法。

在ServiceImpl类中可以,继承InitializingBean和DisposableBean

 1.6配置Spring文件

1.6.1 初始

管理什么?(Service与Dao)

如何将被管理的对象告知IoC容器?(配置)

被管理的对象交给IoC容器,如何获取到IoC容器?(接口)

IoC容器得到后,如何从容器中获取bean?(接口方法)

使用Spring导入哪些坐标?(pom.xml)

第1步:导入Spring坐标,记得刷新Maven如下:

<dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-context</artifactId>
      <version>5.2.10.RELEASE</version>
</dependency>

第2步:定义Spring管理的类(接口)

BookService如下:

public interface BookService {
    public void save();
}

BookServiceImpl如下:

public class BookServiceImpl implements BookService{
    private BookDao bookDao = new BookDaoImpl();
    public void save(){
        bookDao.save();
    }
}

第3步:创建Spring配置文件,配置对应类作为Spring管理的bean

点击resources然后New-XML Configuration File-Spring Config

 新建的.xml文件名为:applicationContext.xml

写入:

<!--2.配置bean-->
    <bean id="bookDao" class="com.itheima.dao.impl.BookDaoImpl"/>
    <bean id="bookService" class="com.itheima.service.impl.BookServiceImpl"/>

<bean>标签里的class属性表示给bean定义类型,id属性表示给bean起名字。

第4步:初始化IoC容器(Spring核心容器/Spring容器),通过容器获取bean

 右键java-New-Java Class,创建一个名为App的文件

public class App {
    public static void main(String[] args) {
        //获取IoC容器
        ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
        //获取bean
        BookDao bookDao = (BookDao)ctx.getBean("bookDao");
        bookDao.save();
        BookService bookService = (BookService) ctx.getBean("bookService");
        bookService.save();
    }
}
1.6.2 完善

第1步:删除使用new的形式创建对象的代码+第2步:提供依赖对象对应的setter方法

public class BookServiceImpl implements BookService{
    //5.删除业务层中使用new的方式创建的dao对象
    private BookDao bookDao;
    public void save(){
        bookDao.save();
    }
    //6.提供对应的set方法
    public void setBookDao(BookDao bookDao) {
        this.bookDao = bookDao;
    }
}

第3步:配置service与dao之间的关系

二、Bean的依赖注入和容器

2.1 Bean的依赖注入

依赖注入描述了在容器中建立bean与bean之间依赖关系的过程。

向一个类中传递数据的方式有几种?答:普通方法(set方法)和构造方法。

如果bean运行需要的是数字或字符串呢?答:引用类型;简单类型(基本数据类型域String).

依赖注入方式:

强制依赖用构造器注入,因为使用setter注入有概率不进行注入导致null对象出现。

可选依赖使用setter注入进行,灵活性强

Spring框架倡导使用构造器,第三方框架内部大多数采用构造器注入的形式进行数据初始化,相对严谨。

如果有必要刻意两者同时使用,实际开发过程中要根据实际情况分析,自己开发的模块推荐使用setter注入。

2.1.1 setter注入

在bean中定义引用类型属性并提供可访问的set方法:

配置中使用property标签value属性注入简单类型数据:

 

2.1.2 构造器注入

传统的经典写法:

但上面的写法会存在一个问题:就是当BookServiceImpl类中的构造方法的命名改变之后,配置文件就不能用了,也就是与形参名的耦合度太高。

以下是解决方法1:通过指定type类型来对应变量。

但假如有2个字符串类型的数据,便无法对应,该方式失效。

解决方法2:通过指定位置来对应变量。

2.1.3 依赖自动装配

IoC容器根据bean所依赖的资源在容器中自动查找并注入到bean中的过程称为自动装配。

自动装配方式:按类型(常用)、按名称、按构造方法、不启用自动装配。

借助set方法来自动装配:

 

 按名称装配是Impl中的变量名。

自动装配用于引用类型依赖注入,不能对简单类型进行操作。

按类型装配时(byType)必须保证容器中相同类型的bean唯一,推荐使用。

按名称装配时(byName)必须保障容器中具有指定名称的bean,因变量名与配置耦合,不推荐使用。

自动装配优先级低于setter注入与构造器注入,同时出现时自动装配配置失效。

2.1.4 集合注入
<bean id="bookDao" class="com.itheima.dao.impl.BookDaoImpl">
        <property name="array">
            <array>
                <value>100</value>
                <value>200</value>
                <value>300</value>
            </array>
        </property>
        <property name="list">
            <list>
                <value>itheima</value>
                <value>itcast</value>
                <value>boxuelu</value>
            </list>
        </property>
        <property name="set">
            <set>
                <value>itheima</value>
                <value>itcast</value>
                <value>boxuelu</value>
                <value>boxuelu</value>
            </set>
        </property>
        <property name="map">
            <map>
                <entry key="country" value="china" />
                <entry key="province" value="fujian" />
                <entry key="city" value="xiamen" />
            </map>
        </property>
        <property name="properties">
            <props>
                <prop key="country">china</prop>
                <prop key="province">henan</prop>
                <prop key="city">kaifeng</prop>
            </props>
        </property>
    </bean>

数组array,列表list,集合set(注意会自动去重)的注入方式如下(都是<value>):

map的注入方式如下:

 

properties的注入方式如下:

2.1.5 加载properties文件

第1步:开启一个新的namespace命名空间,名为context

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

第2步:使用context空间加载properties文件

property-placeholder属性占位符,用location指定要加载的文件

<context:property-placeholder location="jdbc.properties"/>

第3步:使用属性占位符${}读取properties文件中的属性

大括号里面写的是properties文件中的变量名

<bean class="com.alibaba.druid.pool.DruidDataSource">
     <property name="driverClassName" value="${jdbc.driver}"/>
     <property name="url" value="${jdbc.url}"/>
     <property name="username" value="${jdbc.username}"/>
     <property name="password" value="${jdbc.password}"/>
</bean>

注意在properties文件中,变量命名最好不要与系统命名冲突,否则系统命名的优先级会更高,修改会无效。如果想忽略系统命名,可以加入下面这段代码:

system-properties-mode="NEVER"

 加载多个properties文件可以用逗号分隔,也可以用classpath:*.来指明:

<context:property-placeholder location="jdbc.properties,jdbc2.properties" system-properties-mode="NEVER"/>    
<context:property-placeholder location="classpath:*.properties" system-properties-mode="NEVER"/>

如果在classpath后再加一个*,写成:classpath*:*表示不仅可以从当前工程中读还可以从依赖的jar包中读:

<context:property-placeholder location="classpath*:*.properties" system-properties-mode="NEVER"/>

2.2 容器

BeanFactory是IoC容器的顶层接口,初始化BeanFactory对象时,加载的bean延迟加载。

ApplicationContext接口是Spring容器的核心接口,初始化时bean立即加载。

ApplicationContext接口提供基础的bean操作相关方法,通过其他接口拓展其功能。

ApplicationContext接口常用初始化类:

ClassPathXmlApplicationContext

FileSystemXmlApplicationContext

2.2.1创建容器

方法1:类路径加载配置文件

ApplicationContexxt ctx = new ClassPathXmlApplicationContext("applicationContext.xml");

方法2:文件路径加载配置文件

ApplicationContext ctx = new FileSystemXmlApplicationContext("D:\\applicationContext.xml");

 方法3:加载多个配置文件

ApplicationContext ctx = new ClassPathXmlApplicationContext("bean1.xml","bean2.xml");
2.2.2 获取bean

方法1:使用bean名称获取

BookDao bookDao = (BookDao) ctx.getBean("bookDao");

方法2:使用bean名称获取并指定类型

BookDao bookDao = ctx.getBean("bookDao",BookDao.class);

 方法3:使用bean类型获取

BookDao bookDao = ctx.getBean(BookDao.class);

BeanFactory创建完毕后,所有的bean均为延迟加载。

三、Bean的注解开发

3.1 注解开发 

3.1.1 注解开发定义bean

@Component相当于是定义了一个bean语句,bookDao相当于Id,定义了名称。

 接下来有一个问题,Spring如何知道定义了一个注解?只需要在配置文件中写入如下的代码:

<context:component-scan base-package="com.itheima.dao.impl"/>

Spring提供@Component注解的三个衍生注解:

@Controller:用于表现层bean定义

@Service:用于业务层bean定义

@Repository:用于数据层bean定义

注意:下面是表现层的注解可以不写名称:

@Service
public class BookServiceImpl implements BookService {
    public void save() {
        System.out.println("book service save ...");
    }
}

数据层的注解一定要写名称:

@Repository("bookDao")
public class BookDaoImpl implements BookDao {
    public void save() {
        System.out.println("book dao save ...");
    }
}
3.1.2 纯注解开发模式

如果想要用Java类来代替配置文件,需要先创建一个类:

然后标注上注解@Configuration用于设定当前类为配置类,用于代替配置文件:

然后用注解@ComponentScan("com.itheima")来代替<context:component-scan base-package="com.itheima"/>即可,记得要把包名加上,代码如下:

@Configuration
@ComponentScan("com.itheima")
public class SpringConfig {
}

如果想要写多个包名,用数组的格式即可:

@Configuration
@CompinentScan({"com.itheima.service","com.itheima.dao"})
public class SpringConfig{
}

唯一的改动就是:将AnnotationConfigApplicationContext里改为SpringConfig.class。

ApplicationContext ctx = new AnnotationConfigApplicationContext(SpringConfig.class);

Spring框架将会基于SpringConfig类中的注解配置来初始化和加载应用程序上下文,使得定义的Bean可以被Spring容器管理和使用。

public class AppForAnnotation {
    public static void main(String[] args) {
        ApplicationContext ctx = new AnnotationConfigApplicationContext(SpringConfig.class);
        BookDao bookDao = (BookDao) ctx.getBean("bookDao");
        System.out.println(bookDao);
        BookService bookService = ctx.getBean(BookService.class);
        System.out.println(bookService);
    }
3.1.3 bean作用范围和生命周期

bean的作用范围通过@Scope标签设置。

 prototype是多例,singleton是单例。

设定生命周期的初始方法init()和终止方法destroy(),要添加如下依赖:

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

初始方法加注解@PostConstruct(彻底构造后),终止方法加注解@PreDestroy(彻底销毁前)。

如果要执行销毁操作,要添加ctx.close();

3.2 依赖注入

加上Autowired注解,完成自动装配:

如果bookDaoImpl没有名,说明是按类型装配的。

此时不需要再写set方法,只需要写成下面这样:

@Service
public class BookServiceImpl implements BookService {
    @Autowired
    private BookDao bookDao;
    public void save() {
        System.out.println("book service save ...");
        bookDao.save();
    }
}

自动装配基于反射设计创建对象并暴力反射对应属性为私有属性初始化数据,因此无需提供setter方法。 

自动装配建议使用无参构造方法创建对象(默认),如果不提供对应构造方法,请提供唯一的构造方法。

如果有多个数据层的实现,只能按名称装配,采用注解@Qualifier("填写名称"):

通过Value注解来注入简单类型:

如果@Value注解中引用的值在PropertySource中,则通过@PropertySource注解加载properties配置文件。

 name是property中的变量名:

 

如果要写多个就这么写:

@PropertySource({"jdbc.properties1","jdbc.propertie2","jdbc.propertie3"})

3.3 第三方bean管理和依赖注入

第1步:在SpringConfig类中先定义一个方法

第2步:添加@Bean表示当前方法的返回值是一个bean

@Configuration
public class SpringConfig {
    @Bean("dataSource")
    public DataSource dataSource(){
        DruidDataSource ds = new DruidDataSource();
        ds.setDriverClassName("com.mysql.jdbc.Driver");
        ds.setUrl("jdbc:mysql://localhost:3306/spriing_db");
        ds.setUsername("root");
        ds.setPassword("root");
        return ds;
    }
}

 然后调用打印即可:

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

对于Jdbc的配置类我们可以专门写在JdbcConfig类中,类似于下面这样:

public class JdbcConfig {
    @Bean("dataSource")
    public DataSource dataSource(){
        DruidDataSource ds = new DruidDataSource();
        ds.setDriverClassName("com.mysql.jdbc.Driver");
        ds.setUrl("jdbc:mysql://localhost:3306/spriing_db");
        ds.setUsername("root");
        ds.setPassword("root");
        return ds;
    }
}

 通过标签@Import来导入其它依赖,用数组形式如下:

@Configuration
@Import({JdbcConfig.class})
public class SpringConfig {
}

加入简单类型就通过@Value标签来给变量赋值:

public class JdbcConfig {
    @Value("com.mysql.jdbc.Driver")
    private String driver;
    @Value("jdbc:mysql://localhost:3306/spriing_db")
    private String url;
    @Value("root")
    private String userName;
    @Value("root")
    private String password;
    @Bean("dataSource")
    public DataSource dataSource(){

        DruidDataSource ds = new DruidDataSource();
        ds.setDriverClassName(driver);
        ds.setUrl(url);
        ds.setUsername(userName);
        ds.setPassword(password);
        return ds;
    }

 加入引用类型,直接给出形参即可,会自动装配,按类型进行检测而:

public DataSource dataSource(BookDao bookDao){
        System.out.println(bookDao);
        DruidDataSource ds = new DruidDataSource();
        ds.setDriverClassName(driver);
        ds.setUrl(url);
        ds.setUsername(userName);
        ds.setPassword(password);
        return ds;
    }

 ​​

四、AOP

4.1 AOP入门案例

先创建一个通知类,比如MyAdvic

1. 注解@Component是告诉Spring来加载我。

2. @Aspect是告诉Spring这个东西是用来做AOP的。

3. @Pointcut注解是定义切入点:给它一个参数,execution()。void是返回值,com包下,itheima包下,dao包下,BookDao接口,.update()方法。

4. method()方法是通知,执行的方法。

5. 通过@Before注解来绑定。意思是method()方法的执行在pt()方法的前面。

@Component //变成Spring控制的Bean,通知类必须配置成Spring管理的bean
//设置当前类为切面类类
@Aspect //知道是面向切面
public class MyAdvice {
    @Pointcut("execution(void com.itheima.dao.BookDao.update())") //定义切入点
    private void pt(){} //先写私有方法

    @Before("pt()")//想上面的方法在什么位置执行,如前面
    public void method(){ //执行的方法
        System.out.println(System.currentTimeMillis()); //共性功能
    }
}

 @EnableAspectJAutoProxy是为了告诉程序用注解开发AOP:

@Configuration
@ComponentScan("com.itheima")
@EnableAspectJAutoProxy //开启注解开发AOP功能
public class SpringConfig {
}

4.2 AOP工作流程

1. Spring容器启动。

2.读取所有切面配置中的切入点。@Pointcut注解定义的。

3.初始化bean,判定bean对应的类中的方法是否匹配到任意切入点。

匹配失败,创建对象

匹配成功,创建原始对象(目标对象)的代理(可以增强)对象

4. 获取bean执行方法

获取bean,调用方法并执行,完成操作。

获取的bean是代理对象时,根据代理对象的运行模式运行原始方法和增强内容。

4.3 AOP切入点表达式

切入点:要进行增强的方法。切入点表达式:要进行增强的方法的描述方式。】

方式1:执行com.itheima.dao包下的BookDao接口中的无参数update方法:execution(void com.itheima.dao.BookDao.update())

方式2:执行com.itheima.dao.impl包下的BookDaoImpl类中的无参数update方法:execution(void com.itheima.dao.impl.BookDaoImpl.update())

切入点表达式标准格式:动作关键字(访问修饰符 返回值 包名.类/接口名.方法名(参数)异常名)。其中访问修饰符是public、private等,可以省略。

一个程序中如果开发了很多功能,挨个写会极为困难。

通配符:

*:单个独立的任意符号(可以是一个范围,但必须要有一个值),可以独立出现;也可以作为前缀或者后缀(范围匹配)的匹配符出现。

匹配com.itheima包下的任意包中的UserService类或接口中所有find开头的带有一个参数的方法:execution(public * com.itheima.*.UserService.find*(*))

..:多个连续的任意符号(也可以不出现),可以独立出现,常用于简化包名与参数的书写。

匹配com包下的任意包中的UserService类或接口中所有名称为findById的方法:execution(public User com..UserService.findById(..))

+:专用于匹配子类类型

execution(* *..*Service+..*(..))

1.描述切入点通常描述接口,而不描述实现类(否则紧耦合)。

2.返回值类型对于增删改类使用精确类型加速匹配,对于查询类使用*通配快速描述。

3.包名书写尽量不使用..匹配,效率过低,通常*做单个包描述匹配,或精确匹配。

4.接口名/类名书写名称与模块相关的采用*匹配,例如UserService书写成*Service,绑定业务层接口。

5.方法名书写以动词进行精确匹配,名词采用*匹配,例如getById书写成getBy*,selectAll书写成selectAll。

6.通常不使用异常作为匹配规则。

4.4 AOP通知类型

AOP通知分为5种类型:

1. 前置通知@Before("填写")

2. 后置通知@After("填写")

3. 环绕通知@Around("填写")。要在前后语句中间加一个.proceed()方法。要强制抛出一个异常。

@Around("pt()")
    public Object around(ProceedingJoinPoint pjp) throws Throwable {
        System.out.println("around before advice ...");
        Object ret = pjp.proceed();//表示对原始操作的调用
        System.out.println("around after advice ...");
        return ret;
    }

环绕通知必须依赖形参ProceedingJoinPoint才能实现对原始方法的调用(否则会对原始操作作出隔离,将跳过原始方法的执行),进而实现原始方法调用前后同时添加通知。

对原始方法调用可以不接收返回值,通知方法设置成Void即可,如果接收返回值,必须要加上返回值Object,返回一个对象。

环绕通知可以模拟出其它所有通知,只在调用原始操作前做事情是前置通知,用try-catch在finally里写的东西是后置通知,在try-catch的try大括号结束之前,原始调用方法结束后模拟返回后通知。catch模拟抛出异常后通知。

4. 返回后通知(了解)@AfterReturning("填写")。只有当方法在正常运行没有抛异常的前提下才会执行语句。

5. 抛出异常后通知(了解)@AfterThrowing("填写")。

4.5 AOP案例

@Component
@Aspect
public class ProjectAdvice {
    @Pointcut("execution(* com.itheima.service.*Service.*(..))")//匹配业务层的所有方法
    private void servicePt(){};
    @Around("ProjectAdvice.servicePt()")
    public void runSpeed(ProceedingJoinPoint pjp) throws Throwable {
        Signature signature = pjp.getSignature();//一次执行的签名信息
        String className = signature.getDeclaringTypeName(); //获取类型名
        String methodName = signature.getName(); //获取执行方法名
        long start = System.currentTimeMillis(); //开始时间
        for(int i=0;i<10000;i++){
            Object ret = pjp.proceed(); //执行操作
        }
        long end = System.currentTimeMillis(); //结束时间
        System.out.println("万次执行"+className+"."+methodName+"————"+(end-start)+"ms"); //打印
    }
}

 下面是执行类:

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = SpringConfig.class)
public class AccountServiceTestCase {
    @Autowired
    private AccountService accountService;
    @Test
    public void testFindById(){
        Account ac = accountService.findById(2);
    }
    @Test
    public void testFindAll(){
        List<Account> all = accountService.findAll();
    }
}

 下面是百度网盘密码数据兼容处理:

@Component
@Aspect
public class DataAdvice {
    @Pointcut("execution(boolean com.itheima.service.ResourcesService.*(*,*))") //增强功能的对象文件
    private void servicePt(){}

    @Around("DataAdvice.servicePt()")
    public Object trimStr(ProceedingJoinPoint pjp) throws Throwable {
        Object[] args = pjp.getArgs(); //取出参数内容
        for(int i=0;i< args.length;i++){
            if(args[i].getClass().equals(String.class)) //比对是否是字符串
                args[i] = args[i].toString().trim(); //trim方法用于去掉字符串两段多余空格
            //修改参数内容
        }
        Object ret = pjp.proceed(args); //重新回填参数
        return ret; //返回
    }
}

4.6 AOP通知获取数据

1. 获取切入点方法的参数

JoinPoint:适用于前置、后置、返回后、抛出异常后通知。

//   @Before("pt()")
    public void before(JoinPoint jp) {
//JoinPoint:用于描述切入点的对象,必须配置成通知方法中的第一个参数,可用于获取原始方法调用的参数
        Object[] args = jp.getArgs();
        System.out.println(Arrays.toString(args));
        System.out.println("before advice ..." );
    }

ProceedJointPoint:适用于环绕通知。

@Around("pt()")
    public Object around(ProceedingJoinPoint pjp) {
//ProceedingJoinPoint:专用于环绕通知,是JoinPoint子类,可以实现对原始方法的调用
        Object[] args = pjp.getArgs();
        System.out.println(Arrays.toString(args));
        args[0] = 666; //传过来的参数如果有问题可以先处理一下
        //如果要字符串,如果传的不是字符串,可以先更改再赋予初始值
        Object ret = null;
        try {
            ret = pjp.proceed(args);
        } catch (Throwable t) {
            t.printStackTrace();
        }
        return ret;
    }

2. 获取切入点方法返回值(必须保证方法内部正常执行才会有返回值)

返回后通知和环绕通知可以拿到返回值。

//设置返回后通知获取原始方法的返回值,要求returning属性值必须与方法形参名相同
//@AfterReturning(value = "pt()",returning = "ret")
//定义一个参数变量用于接收返回值,returning="ret"
    public void afterReturning(JoinPoint jp,String ret) {
        System.out.println("afterReturning advice ..."+ret);
    }

3. 获取切入点方法运行异常信息

抛出异常后通知和环绕通知。

//设置抛出异常后通知获取原始方法运行时抛出的异常对象,要求throwing属性值必须与方法形参名相同
   @AfterThrowing(value = "pt()",throwing = "t")
    public void afterThrowing(Throwable t) {
        System.out.println("afterThrowing advice ..."+t);
    }
}

五、Spring事务 

5.1事务入门

步骤1:在业务层接口上添加Spring事务管理

public interface AccountService{
    @Transactional
    public void transfer(String out,String in,Double money);
}

注意1:通常添加在业务层的接口中而不会添加到业务层的实现类中,降低耦合。

注意2:注解可以添加到业务方法上表示当前方法开启事务,也可以添加到接口上表示当前接口所有方法开启事务。

步骤2:设置事务管理器

@Bean
public PlatformTransactionManager transationManager(DataSource dataSource){
    DataSourceTransactionManager ptm = new DataSourceTransactionManager();
    ptm.setDataSource(dataSource);
    return ptm;
}

 注意1:事务管理器要根据实现技术进行选择。Mybatis框架使用的是JDBC事务。

步骤3:开启注解式事务驱动

@EnablTransactionManagement
public class SpringConfig{
}

5.2 事务角色

 事务管理员:发起事务方,在Spring中通常指代业务层

 事务协调员:加入事务方,在Spring中通常指代数据层方法,也可以是业务层方法。

5.3 事务属性

rollbackFor遇到某类异常回滚事务 

会回滚的异常包括:Error系的,运行时异常。有些异常不会回滚,如:IOException。

try-finally结构:将一定要运行的代码放在finally里,将有可能出问题的代码放在try里。

popagation = Propagation.REQUIRES_NEW 表示开启一个新的日志。

 MANDATORY表示要想调用我必须携带事务。

NEVER表示千万不能携带事务。

六、Spring整合系列

4.1 Spring整合Mybatis

 4.1.1 整合的思路

 下面是原始开发:

public class App {
    public static void main(String[] args) throws IOException {
        // 1. 创建SqlSessionFactoryBuilder对象
        SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();
        // 2. 加载SqlMapConfig.xml配置文件
        InputStream inputStream = Resources.getResourceAsStream("SqlMapConfig.xml");
        // 3. 创建SqlSessionFactory对象
        SqlSessionFactory sqlSessionFactory = sqlSessionFactoryBuilder.build(inputStream);
        // 4. 获取SqlSession
        SqlSession sqlSession = sqlSessionFactory.openSession();
        // 5. 执行SqlSession对象执行查询,获取结果User
        AccountDao accountDao = sqlSession.getMapper(AccountDao.class);
        Account ac = accountDao.findById(1);
        System.out.println(ac);
        // 6. 释放资源
        sqlSession.close();
    }
}

 其中最核心的是SelSessionFactory对象:

 

 4.1.2 整合的具体操作

Spring中操作数据库的包:

<dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-jdbc</artifactId>
      <version>5.2.10.RELEASE</version>
</dependency>

Spring整合Mybatis的包:

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

 SqlSessionFactoryBean可以快速创建SqlSessionFactory对象。

框架的好处在于大量的东西采用默认值,不需要设置。

在MybatisConfig配置类中要设置原先sqlMapConfig.xml文件中的信息:

public class MybatisConfig {
    @Bean
    public SqlSessionFactoryBean sqlSessionFactory(DataSource dataSource){
        SqlSessionFactoryBean ssfb = new SqlSessionFactoryBean();
        ssfb.setTypeAliasesPackage("com.itheima.domin"); //需要修改的地方
        ssfb.setDataSource(dataSource);
        return ssfb;
    }

    @Bean
    public MapperScannerConfigurer mapperScannerConfigurer(){
        MapperScannerConfigurer msc= new MapperScannerConfigurer();
        msc.setBasePackage("com.itheima.dao"); //需要修改的地方
        return msc;
    }
}

下面是Configuration注解: 

@Configuration
@ComponentScan("com.itheima")
@PropertySource("classpath:jdbc.properties")
@Import({JdbcConfig.class,MybatisConfig.class})
public class SpringConfig {
}

 

4.2 Spring整合JUnit

先导入如下的依赖:

<dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>4.12</version>
      <scope>test</scope>
</dependency>
    
<dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-test</artifactId>
      <version>5.2.10.RELEASE</version>
</dependency>

 在src - test - java - com.itheima.service 下创建AccountServiceTest类:

@RunWith(SpringJUnit4ClassRunner.class)//设置类运行器
@ContextConfiguration(classes = SpringConfig.class)//设置Spring环境对应的配置类
public class AccountServiceTest {
    @Autowired //支持自动装配注入bean
    private AccountService accountService;
    @Test
    public void testFindById(){
        System.out.println(accountService.findById(1));
    }
    @Test
    public void testFindAll(){
        System.out.println(accountService.findAll());
    }
}

七、心得总结

5.1 学习复盘

待写... 

5.2 面试真题

spring:

1. 设计模式:spring中的设计模式有哪些?【Spring中使用到的设计模式?】分别在哪用到?Spring源码用了哪些设计模式?

2. IOC和AOP:IOC?spring AOP IOC 项目落脚?AOP有哪些应用场景呢?你在项目中用到aop了吗?Spring的IOC和AOP介绍一下?Spring AOP 实现日志记录功能,为什么用 AOP?

3. 事务:Spring事务是如何实现的?spring事务失效?

4. 注解:知道注解吗,知道自定义注解的原理吗?Spring @Autowired 和 @Resource 注解有什么区别?

5. 启动流程:Spring 容器的启动流程?

6. 依赖:Spring如何解决循环依赖问题?

7. 其它:说一下你对spring的理解?介绍一下Spring?使用Spring和直接使用Java语言面向对象开发,有哪些好处?spring和mybatis有什么区别?spring源码看过吗?

spring bean:

1. 创建bean:Spring中创建bean的方式?在 BeanDefinition 创建的过程前后做了什么事?Spring IOC容器创建Bean的流程?

2. 生命周期:生命周期bean(创建、初始化、使用、销毁)?​Bean生命周期中涉及的接口和方法域?

3. 单例多例:Bean默认是单例还是多例,怎么设置多例?IOC容器建的Bean是单例还是多例的?

4. BeanFactory和FactoryBean有啥区别? 什么是JavaBean?

5. 注解:bean,configuration,component的区别与问题?

6. 安全:Spring Bean的安全问题?
 

5.3 常见问题

  • 3
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值