Spring入门详解


typora-copy-images-to: upload


Spring入门详解

Spring框架是Java开发中最常用的框架,功能非常强大

源码下载:Spring Framework

jar包、文档下载:reposity

Spring核心概念

  • Ioc:Inversion of Control 控制反转
  • DI:Dependency Injection 依赖注入
  • AOP:Aspect Oriented Programming 面向切面编程

Spring下载

在reposity按照下图顺序依次点击

Spring入门详解_第1张图片

引言–为什么要使用Spring

假如你现在在学习Spring,那么之前肯定学过或了解过简单的web是如何开发的,我在这里给出一个大致的模板进行讲解(先不看factory目录)

Spring入门详解_第2张图片

我这里省略了bean目录,不创建具体的对象。

  • dao层:与数据库进行连接,并执行SQL语句
  • service层:业务层,几种dao层的操作封装起来组成一个新的操作
  • servlet层:处理网页请求
  • impl:实现类,采用面向接口编程,不同的实现方法我只需要用统一的接口就行,减少代码量

在这样的层次结构中,servlet中含有service对象,service中含有dao对象,尽管这样做可以将代码任务尽可能的划分开,但是耦合程度非常的高(耦合:我依赖你,你不见了,对我影响很大,我得改代码),impl层中可能有很多个实现类,我们要使用不同的实现类就需要频繁的修改代码

public class PersonServiceImpl implements PersonService {
    //	下面两个实现类更换需要改代码
    private PersonDao dao = new PersonDaoImpl();
    // private PersonDao dao = new PersonDaoImpl2();

    public boolean remove(Integer id) {
        return dao.remove(id);
    }
}

为了减少耦合性,我们就需要将new PersonDaoImpl()这个代码抽离出来,通过工厂进行生产送入这里,这样获取对象的时候我们直接修改工厂那里的代码就可以了

public class GeneralFactory {
    public static PersonDao getDao() {
    	return new PersonDaoImpl();
    }
  public static PersonService getService() {
    	return new PersonServiceImpl();
    }
}

但是这样子还是会修改代码,修改代码的话就需要重新编译生成字节码,非常的麻烦,所以我们将这一步放到配置文件中

service=com.harrison.service.impl.PersonServiceImpl
dao=com.harrison.dao.impl.PersonDaoImpl2

之后我们读取配置文件,通过类的完整路径反射出来一个实例化类对象直接传给接口

public class GeneralFactory {
    private static Properties properties;
    static {
        try(InputStream is = GeneralFactory.class.getClassLoader().getResourceAsStream("factory.properties")){
            properties = new Properties();
            properties.load(is);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    public static static <T> T get(String name) {
        try {
            //拿到类名
            String clsName = properties.getProperty(name);
            Class cls = Class.forName(clsName);
            //实例化对象
            return (T)cls.newInstance();
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }
}

这样我们在接口那里只需要private static PersonDao = GeneralFactory.get("dao")就可以完成任务,是不是非常的方便

为此,你就知道了,Spring框架就是干类似于这个事情的,帮助我们拿到一些对象。在maven中我们导入spring-context即可,也就是IoC容器

基本使用 - IoC容器 - Inversion of Control

添加Maven依赖

        <dependency>
            <groupId>org.springframeworkgroupId>
            <artifactId>spring-contextartifactId>
            <version>5.2.13.RELEASEversion>
        dependency>

简单使用Ioc

我们在resources目录下新建一个名为applicatinContext.xml的Spring Config配置文件

添加方式

Spring入门详解_第3张图片


<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:util="http://www.springframework.org/schema/util"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/util https://www.springframework.org/schema/util/spring-util.xsd">

    <bean id="personDao" class="com.harrison.dao.impl.PersonDaoImpl"/>
    <bean id="personService" class="com.harrison.service.impl.PersonServiceImpl">
        <property name="dao" ref="personDao"/>
    bean>
    <bean id="personServlet" class="com.harrison.servlet.PersonServlet">
        <property name="service" ref="personService"/>
    bean>
beans>

其中,bean就表示dao对象,service对象以及servlet对象,每个bean标签中我们加入property标签,通过ref属性链接bean的id属性,告诉编译器这个service对象里面还有一个dao对象

简单起见,我生成一个假的servlet来进行举例,其中的注释代表不使用Spring框架的写法,需要注意的是我们的propert标签需要有set方法才能链接上,比如说我的id为personService的bean对象里面还有一个dao对象,我使用property标签进行绑定,同时要在service中写一个setDao方法

public class PersonServlet {
//    private PersonService service = GeneralFactory.get("service");
    private PersonService service;

    public void setService(PersonService service) {
        this.service = service;
    }

    public void remove() {
        service.remove(1);
    }

    public static void main(String[] args) {
//        PersonServlet servlet = new PersonServlet();
        //读取配置文件
        ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
        PersonServlet servlet = (PersonServlet) ctx.getBean("personServlet");
        servlet.remove();
    }
}

logback日志输出

到这里我们应该可以发现Ioc的一个重要作用:解耦合

为了更直观的看到作用,Spring帮我们整合了一个日志框架logback,采用这个框架我们可以在控制台看到相关信息

在pom.xml文件中添加依赖:

        <dependency>
            <groupId>ch.qos.logbackgroupId>
            <artifactId>logback-classicartifactId>
            <version>1.2.3version>
        dependency>

Ioc作用

现在我们大概就可以体会到Ioc的一点点作用了,我们将原本自己手动new出来的实现类交给了Spring去控制创建时机完成,实现了控制反转

依赖注入 - Dependency Injection

Spring入门详解_第4张图片

Personservlet依赖于service对象,通过Ioc容器ref属性注入进去

注入类型

  • bean(自定义类型)
  • String、BigDecimal、基本数据类型
  • 集合类型(数组、Map、List)

注入方式

常见的注入方式有两种:

  • 基于setter的注入
  • 基于constructor的注入
基于setter的注入
bean

三种方法都可以,推荐1和3,1会共享personDao,3会注入一次new一个新的

<bean id="personDao" class="com.harrison.dao.PersonDaoImpl"/>
<bean id="personService" class="com.harrison.service.PersonServiceImpl">
		<property name="dao" ref="personDao"/>
bean>
<bean id="personDao" class="com.harrison.dao.PersonDaoImpl"/>
<bean id="personService" class="com.harrison.service.PersonServiceImpl">
		  <property name="dao" >
  	  		<ref bean="personDao"/>
  		property>
bean>
<bean id="personService" class="com.harrison.service.PersonServiceImpl">
		  <property name="dao" >
  	  		<bean class="com.harrison.dao.PersonDaoImpl"/>
  		property>
bean>
基本类型、String、BigDecimal
    <bean id="person" class="com.harrison.domain.Person">
        <property name="age" value="18"/>
        <property name="name" value="jack"/>
        <property name="dog">
            <bean class="com.harrison.domain.Dog"/>
        property>
    bean>
集合(数组、Map、List、Set)
    <bean id="person" class="com.harrison.domain.Person">
        <property name="nickName">
            <array>
                <value>Rosevalue>
                <value>Jackvalue>

            array>
        property>
    bean>

写入setter方法后,通过property标签给属性赋值:

  • 如果是自定义bean,就在property标签中嵌套bean标签/在外部声明bean标签,然后通过propert标签的ref属性引用

  • 如果是基本数据类型、String、BigDecimal,通过property标签的value属性直接赋值

  • 如果是集合,在property标签中嵌套array/list/set/map标签,并在array标签中根据数据类型选择value或者bean或者array(二维数组)

    • set标签默认创建的是linkedHashSet(按照添加顺序排列)

    • map标签里面用entry标签,属性使用key、value

      (value是其他数据类型,就删掉value属性,在entry标签中嵌套其他标签,比如说bean、list…)

  • 如果成员类型是Properties

        <bean id="person" class="com.harrison.domain.Person">
            <property name="friends">
                <props>
                  	<prop key="Jack">杰克prop>
                  	<prop key="Rose">罗斯prop>
                props>
            property>
        bean>
    
基于constructor的注入

假设我们现在只需要给姓名和年龄注入,则只需构造有这两个参数的构造方法即可

    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }

赋值操作只需要在constructor-arg标签中进行,一定要和构造函数里变量的顺序一致

    <bean id="person" class="com.harrison.domain.Person">
        <constructor-arg value="Jack"/>
        <constructor-arg value="18"/>
    bean>

如果变量太多记不住顺序,那么可以添加type属性,或者使用index属性指明这是哪个位置的参数

    <bean id="person" class="com.harrison.domain.Person">
        <constructor-arg value="Jack" type="java.lang.String"/>
        <constructor-arg value="18" type="int"/>
       
      
    bean>

如果这样都满足不了你,我们还可以再加name属性

首先先给构造方法添加注解@ConstructorProperties({"myName", "myAge"}),里面的变量名对应于name属性的名,我这里是为了做演示起了和构造函数内部的变量名不一样的名字,最好是写一样的,要不太乱了

    @ConstructorProperties({"myName", "myAge"})
    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }
    <bean id="person" class="com.harrison.domain.Person">
        <constructor-arg value="Jack" name="myName"/>
        <constructor-arg value="18" name="myAge"/>
    bean>

参数如果是bean,直接在constructor-arg标签中添加ref标签,或者嵌入bean标签,在bean的class属性中指出类全名

    <bean id="person" class="com.harrison.domain.Person">
        <constructor-arg ref="com.harrison.domain.Dog"/>
    bean>

    <bean id="person" class="com.harrison.domain.Person">
        <constructor-arg>
        	<bean class="com.harrison.domain.Person"/>
            <list>
          	............
            list>
        constructor-arg>
    bean>

复杂对象注入 – FactoryBean

复杂对象强调的是创建过程很复杂

FactoryBean 通常是用来创建比较复杂的bean,一般的bean 直接用xml配置即可,但如果一个bean的创建过程中涉及到很多其他的bean 和复杂的逻辑,用xml配置比较困难,这时可以考虑用FactoryBean

比如说我们想要拿到数据库的Connection,无法直接return这个对象,还需要先通过Class.forName,因此,我们可以通过FactoryBean实现

现在我们来看一看如何拿到数据库的Connection

静态工厂

我们新建一个工厂用来生产Connection,我这里用的是mysql8.x的连接方法,mysql5.x驱动器没有cj目录,url不需要写参数

public class ConnectionFactory {
    public static Connection getConn() throws Exception{
        Class.forName("com.mysql.cj.jdbc.Driver");
        return DriverManager.getConnection("jdbc:mysql://localhost:3306/school?useSSL=false&serverTimezone=Asia/Shanghai", "root", "root");
    }
}
    
    <bean id="conn" class="com.harrison.obj.ConnectionFactory" factory-method="getConn"/>

不写factory-method,这个bean就会new出来一个ConnectionFactory,就不是一个工厂

factory-method表示调用ConnectionFactory.getConn()

**注意:**我们这个getConn()方法是静态的

    @Test
    public void connect() {
        ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
        System.out.println(ctx.getBean("conn"));
    }
实例工厂
public class ConnectionFactory {
    public Connection getConn() throws Exception{
        Class.forName("com.mysql.cj.jdbc.Driver");
        return DriverManager.getConnection("jdbc:mysql://localhost:3306/school?useSSL=false&serverTimezone=Asia/Shanghai", "root", "root");
    }
}
    
    <bean id="factory" class="com.harrison.obj.ConnectionFactory"/>
    <bean id="conn" factory-bean="factory" factory-method="getConn"/>

bean先new出来一个ConnectionFactory对象,我们将其命名为factory

factory-method表示调用factory对象的.getConn()方法

**注意:**我们这个getConn()方法是动态的

    @Test
    public void connect() {
        ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
        System.out.println(ctx.getBean("conn"));
    }
实现一

**实例工厂的优点:**我们可以对实例对象进行注入,比如说将url, username, password通过注入的方式写进去,而不是直接在代码中写死

public class ConnectionFactory {
    private String driverClass;
    private String url;
    private String username;
    private String password;

    public void setDriverClass(String driverClass) {
        this.driverClass = driverClass;
    }

    public void setUrl(String url) {
        this.url = url;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public void setPassword(String password) {
        this.password = password;
    }

    public Connection getConn() throws Exception{
        Class.forName(driverClass);
        return DriverManager.getConnection(url, username, password);
    }
}
    <bean id="factory" class="com.harrison.obj.ConnectionFactory">
        <property name="driverClass" value="com.mysql.cj.jdbc.Driver"/>
        <property name="url" value="jdbc:mysql://localhost:3306/school?useSSL=false&serverTimezone=Asia/Shanghai"/>
        <property name="username" value="root"/>
        <property name="password" value="root"/>
    bean>
    <bean id="conn" factory-bean="factory" factory-method="getConn"/>
实现二----FactoryBean

实现一的写法太过于复杂,我们现在通过Spring提供的FactoryBean来实现

和实现一类似,我们只需要给我们的类实现FactoryBean接口,然后重写getObject和getObjectType两个方法即可

public class ConnectionFactoryBean implements FactoryBean<Connection> {
    private String driverClass;
    private String url;
    private String username;
    private String password;

    public void setDriverClass(String driverClass) {
        this.driverClass = driverClass;
    }

    public void setUrl(String url) {
        this.url = url;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public void setPassword(String password) {
        this.password = password;
    }

    @Override
    public Connection getObject() throws Exception {
        Class.forName(driverClass);
        return DriverManager.getConnection(url, username, password);
    }

    @Override
    public Class<?> getObjectType() {
        return Connection.class;
    }
}
    <bean id="conn" class="com.harrison.obj.ConnectionFactoryBean">
        <property name="driverClass" value="com.mysql.cj.jdbc.Driver"/>
        <property name="url" value="jdbc:mysql://localhost:3306/school?useSSL=false&serverTimezone=Asia/Shanghai"/>
        <property name="username" value="root"/>
        <property name="password" value="root"/>
    bean>

Spring检查class类型,发现ConnectionfactoryBean类实现了FactoryBean接口,于是便会先创建class中声明的类型ConnectionFactoryBean的对象,然后调用其调用getObject()方法

假如我们不改变上面这个代码,依旧希望拿到class声明的对象,即ConnectionFactoryBean而不是先调用getObject()方法后返回的那个对象,我们只需要在getBean中的参数名前添加&符号

    @Test
    public void connect() {
        ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
        System.out.println(ctx.getBean("&conn"));
    }
添加命名空间的方式

添加一行xmlns:p="http://www.springframework.org/schema/p"

然后p:name=“Jack” ,将Jack字符串fuzhi给name

如果要赋值一个对象,用p:dog-ref=“dog”

等价于在property标签中通过name和value或者ref的方式


<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:p="http://www.springframework.org/schema/p" 
       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="dog" class="com.harrison.domain.Dog"/>
    <bean id="person" class="com.harrison.domain.Person"
        p:name="Jack" p:age="18" p:dog-ref="dog"/>
beans>
和properties配置文件联合使用

首先要添加下面三行配置(含context字段),然后在context:property-placeholder中添加配置文件db.properties,value用${}来赋值


  //自己加

        <context:property-placeholder location="db.properties"/>
        <bean id="conn" class="com.harrison.obj.ConnectionFactoryBean">
            <property name="driverClass" value="${driverClass}"/>
            <property name="url" value="${url}"/>
            <property name="username" value="${username}"/>
            <property name="password" value="${password}"/>
        bean>
beans>
driverClass=value="com.mysql.cj.jdbc.Driver
url=jdbc:mysql://localhost:3306/school?useSSL=false&serverTimezone=Asia/Shanghai
username=root
password=010730whs

运行后发现会报错,原因是因为字段username在spring中是保留关键字,在我这台电脑上我的用户名是harrison,他就会将harrison赋值进去,而不是我数据库里的root用户名,所以我们不能用这个名字

修改properties变量名如下,上面的${}里面也要改一下

jdbc.driverClass=value="com.mysql.cj.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/school?useSSL=false&serverTimezone=Asia/Shanghai
jdbc.username=root
jdbc.password=010730whs

SpEL表达式

之前我们想在Person中注入一条狗,使用的方法是添加ref属性

    <bean id="person" class="com.harrison.domain.Person">
			<property name="dog" ref="dog"/>
    bean>

采用SpEL表达式的方法是使用#{}

    <bean id="person" class="com.harrison.domain.Person">
        <property name="dog" value="#{dog}"/>
    bean>

现在,我们希望person的name成员属性的值来自于dog类中的getPersonName()方法,实现如下

简化写法,我们可以将get省略,并且开头字母小写,和jsp中的写法很像

Scope

Scope默认是singleton的,IoC容器一创建就会创建这个对象

我们在上面的案例中对dog进行了创建,我们在用getBean获取dog对象时,无论调用几次这个方法,始终都会拿到同一个dog对象,这是因为在IoC容器中只为我们创建了一个dog对象,默认属性scope=“singleton”,代表单例(在同一个IoC容器中),如果不想设计成单例,可以修改为prototype,每次getBean都会创建新的对象

这个dog对象是在IoC容器创建好之后就生产出来的,也就是下面这句代码

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

如果单例情况下要实现懒加载,可以添加属性lazy-init=“true”,这样就会在getBean时才创建了

**注意:**这里的单例和单例设计模式是不一样的

单例设计模式是全局只有一份,而这里的单例是一个容器一个,但是全局我们能有很多个容器

也就是说:通过同一个id值,在一个IoC容器中获取的永远是同一个实例

Convertor转换器

假设我们的Person类有一个类型为Date的属性birthday,我们现在要对其赋值,可以使用,这里的"2020.5.3"是通过Spring内部实现的转换器Convertor来实现的,统一将字符串转为Date类型,但也仅限于以**.**作为分隔符假如我们要转换的格式为"2020-5-3",就需要我们自定义转换器了

Spring入门详解_第5张图片

首先我们要实现Converter<>泛型接口,注意要选红框里的这个,泛型第一个是String,第二个是你要转换的类型,我这里要转换Date类型,注意是java.util.Date,然后实现里面唯一的一个方法convert

public class DateConvertor implements Converter<String, Date>{
    private String format;

    public void setFormat(String format) {
        this.format = format;
    }

    @Override
    public Date convert(String s) {
        SimpleDateFormat fmt = new SimpleDateFormat(format);
        try {
            return fmt.parse(s);
        } catch (ParseException e) {
            e.printStackTrace();
            return null;
        }
    }
}

写完之后,我们在配置文件中告诉Spring我们有这个DateConvertor转换器

首先我们配置了一个ConversionServiceFactoryBean,他会返回一个conversionService对象,然后我们给这个对象的converters属性进行设置,此时Spring就会找到converters看看有哪个转换器

    <bean id="person" class="com.harrison.domain.Person" p:birthday="2021.5.3"/>
	
    <bean id="conversionService" class="org.springframework.context.support.ConversionServiceFactoryBean">
        <property name="converters">
            <set>
                <bean class="com.harrison.convertor.DateConvertor" p:format="yyyy-MM-dd"/>
            set>
        property>
    bean>

这种写法的缺点是只能配置这一种格式,为此,我们将它换成list

public class DateConverter implements Converter<String, Date>{
    private List<String> formats;

    public void setFormats(List<String> formats) {
        this.formats = formats;
    }

    @Override
    public Date convert(String s) {
        for(String format: formats) {
            SimpleDateFormat fmt = new SimpleDateFormat(format);
            try {
                return fmt.parse(s);
            } catch (ParseException e) {
                System.out.println("转换失败");
            }
        }
        return null;
    }
}
<bean id="person" class="com.harrison.domain.Person" p:birthday="2021.5.3"/>
    <bean id="conversionService" class="org.springframework.context.support.ConversionServiceFactoryBean">
        <property name="converters">
            <set>
                <bean class="com.harrison.converter.DateConverter">
                    <property name="formats">
                        <list>
                            <value>yyyy-MM-ddvalue>
                            <value>yyyy.MM.ddvalue>
                            <value>yyyy/MM/ddvalue>
                        list>
                    property>
                bean>
            set>
        property>
    bean>

整合MyBatis

MyBatis的问题

MyBatis中比较繁琐的地方:

  • 如果mapper文件比较多,那么就需要配置很多的mapper文件路径
  • 需要事先创建好SqlSessionFactory对象
  • 每次需要调用openSession方法,最后还要close掉session
添加依赖

Spring和mybatis的依赖都添加后,额外添加两个依赖mybatis-spring和spring-jdbc

        <dependency>
            <groupId>org.springframeworkgroupId>
            <artifactId>spring-jdbcartifactId>
            <version>5.2.14.RELEASEversion>
        dependency>
        <dependency>
            <groupId>org.mybatisgroupId>
            <artifactId>mybatis-springartifactId>
            <version>2.0.6version>
        dependency>

其他依赖:

  • 必要:spring-context、mysql-connector-java、mybatis
  • 可选:logback-classic、druid
配置

以前我们需要给MyBatis单独进行配置,现在只需要在Spring Config中统一配置即可


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

    <context:property-placeholder location="db.properties"/>
    
    <bean id="dataSource" 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>
    
    <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
        
        <property name="dataSource" ref="dataSource"/>
        
        <property name="typeAliasesPackage" value="com.harrison.domain"/>
        
        <property name="mapperLocations">
            <array>
                <value>mappers/*.xmlvalue>
            array>
        property>
    bean>
    
    <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
        
        <property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"/>
        
        <property name="basePackage" value="com.harrison.dao"/>
    bean>
beans>

文件列表如下

Spring入门详解_第6张图片
  • 数据源就是用来连接Druid连接池
  • SqlSessionFactory在之前Mybatis中用来创建SqlSessin
  • 扫描dao就等同于Mybatis中的session.getMapper()

然后在测试代码中,我们通过ctx.getBean拿到我们想要的dao,第一个id参数只需要把第二个参数比如说SKillDao的开头字母小写即可–>skillDao

    @Test
    public void get() {
        ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
        SkillDao dao = ctx.getBean("skillDao", SkillDao.class);
        List<Skill> skills = dao.list();
        System.out.println(skills);
    }
测试
public interface SkillDao {
    List<Skill> list();
    @Insert("INSERT INTO skill(name, id, level) VALUES(#{name}, #{id}, #{level})")
    boolean save(Skill skill);
}
public class MyTest {
    private ApplicationContext ctx;
    private SkillDao skillDao;
    {
        ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
        skillDao = ctx.getBean("skillDao", SkillDao.class);
    }
    @Test
    public void list() {
        List<Skill> skills = skillDao.list();
        System.out.println(skills);
    }
    @Test
    public void save() {
        Skill skill = new Skill("Jack", 12, 10);
        skillDao.save(skill);
    }
}

bean的生命周期

11个周期方法

一个bean从出生到死亡,经历的生命周期方法是:

  • 1、构造方法
  • 2、setter
  • 3、BeanNameAware的setBeanName
  • 4、ApplicationContextAware的setApplicationContext
  • 5、BeanPostProcessor的postProcessBeforeInitialization
  • 6、InitializingBean的afterPropertiesSet
  • 7、init-method
  • 8、BeanPostProcessor的postProcessAfterInitialization
  • 9、业务方法
  • 10、DisposableBean的destroy
  • 11、destroy-method
每个周期的作用

构造方法和setter用来创造对象,创建后通过配置信息设置beanName和applicationName,之后通过InitializingBean和init-method来监听是否创建完毕

然后执行业务方法

最后使用DisposableBean和destroy-method监听bean对象是否销毁,销毁是通过close方法关闭的,并不是IoC容器的销毁

public class UserServiceImpl implements
        BeanNameAware,
        ApplicationContextAware,
        UserService,
        InitializingBean,
        DisposableBean {
    private Integer age;

    public UserServiceImpl() {
        System.out.println("01 - UserServiceImpl");
    }
    public void setAge(Integer age) {
        this.age = age;
        System.out.println("02 - setAge - " + age);
    }
    @Override
    public void setBeanName(String name) {
        System.out.println("03 - BeanNameAware - setBeanName" + name);
    }

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        System.out.println("04 - ApplicationContextAware - setApplicationContext" + applicationContext);
    }
    @Override
    public void afterPropertiesSet() throws Exception {
        System.out.println("06 - InitializingBean");
    }
    public void init() {
        System.out.println("07 - init-method");
    }
    @Override
    public boolean login(String username, String password) {
        System.out.println("09 - UserServiceImpl- login");
        return false;
    }
    @Override
    public void destroy() throws Exception {
        System.out.println("10 - DisposableBean - destroy");
    }
    public void dealloc() {
        System.out.println("11 - destroy-method");
    }

public class MyProcessor implements BeanPostProcessor {
    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        System.out.println("05 - BeanPostProcessor - before - " + beanName);
        return bean;
    }

    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        System.out.println("08 - BeanPostProcessor - after - " + beanName);
        return bean;
    }
}
public interface UserService {
    boolean login(String username, String password); //业务代码
}
    @Test
    public void test() {
        ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
        UserService service = (UserService) ctx.getBean("userService");  //创建bean对象
        service.login("123", "456");  //执行业务方法
        ctx.close();  //销毁bean对象
    }
    <bean id="userService" class="com.harrison.impl.UserServiceImpl"
    p:age="18" init-method="init" destroy-method="dealloc"/>
    <bean class="com.harrison.processor.MyProcessor"/>

控制台输出如下,注释部分为代码讲解

  • // 构造方法

    01 - UserServiceImpl

  • // 属性输入

    02 - setAge - 18

  • // 让你知道一下你创建的bean的名字(id、name)

    03 - BeanNameAware - setBeanNameuserService

  • // 让你知道一下你在哪个容器里面创建的bean

    04 - ApplicationContextAware - setApplicationContextorg.springframework.context.support.ClassPathXmlApplicationContext@7a79be86

  • // 初始化方法调用之前想做的事放这里,拦截器

    05 - BeanPostProcessor - before - userService

  • // 构造、注入完毕之后调用,加载资源(实现类中的afterPropertiesSet方法)

    06 - InitializingBean

  • // 构造、注入完毕之后调用,加载资源(配置文件中的init-method属性指向的方法)

    07 - init-method

  • // 初始化方法调用之后想干的事放这里,拦截器

    08 - BeanPostProcessor - after - userService

  • // 业务方法

    09 - UserServiceImpl- login

  • // 销毁之前调用,释放资源(实现类中的destroy方法)

    10 - DisposableBean - destroy

  • // 销毁之前调用,释放资源(配置文件中的destryo-method属性指向的方法)

    11 - destroy-method

**注意:**如果将bean的scope改为prototype,那么就没有10、11两个生命周期,由垃圾回收机制进行释放对象,声命不交给容器管理

代理

在不修改目标类(目标方法)代码的前提下,为目标方法增加额外功能要求代理、目标对象实现同样的接口

代理的实现方案

  • 静态代理

    • 开发人员手动编写代理类
    • 基本上,一个目标类就要编写一个代理类
  • 动态代理

    程序运行过程动态生成代理类的字节码

静态代理

业务层的一些问题

业务层的主要内容

  • 业务代码:业务运算dao操作等(必要)
  • 附加代码:事务、日志、性能监控、异常处理(可选)

Spring入门详解_第7张图片

业务层加入附加代码会显得很臃肿、累赘,但是很多时候又好像不得不加

更改如下

Spring入门详解_第8张图片

解决方案

我们通过代理实现附加代码的插入,这样做不会影响我们本身业务代码的逻辑,为达到这样的效果,我们只需让代理层和业务层实现同一个接口,并且在代理层中拿到业务层的对象,实现完附加代码后,调用业务层的代码即可

如果业务层不是接口,就是一个普通的类,那么我们就让代理层继承业务层即可

接口

public interface UserService {
    boolean login(String username, String password);
}

业务层

public class UserServiceImpl implements UserService {
    @Override
    public boolean login(String username, String password) {
        System.out.println("09 - UserServiceImpl- login");
        return false;
    }
}

代理层

public class UserServiceProxy implements UserService {
    private UserService target;  //拿到业务层的对象

    public void setTarget(UserService target) {  //注入对象
        this.target = target;
    }

    @Override
    public boolean login(String username, String password) {
        System.out.println("日志--------1");  //插入附加代码

        boolean result = target.login(username, password);  //执行业务代码

        System.out.println("日志--------2");  //插入附加代码
        return result;
    }
}

Spring配置文件

    <bean id="userService" class="com.harrison.proxy.UserServiceProxy">
        <property name="target">
            <bean class="com.harrison.impl.UserServiceImpl"/>
        property>
    bean>

测试代码

    @Test
    public void test() {
        //创建容器
        ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
		// 业务代码
        UserService service = ctx.getBean("userService", UserService.class);  //通过配置文件拿到业务对象
        service.login("123", "456");  //业务对象给到代理层,实际调用的是代理层的login()方法
        //关闭容器
        ctx.close();
    }

动态代理

很多时候我们会发现,我们的附加代码长的都差不多,写的很多都是模版代码,几乎都是一模一样的,为此,我们引入动态代理

两种实现方案
  • JDK
  • CGLib
JDK方案

强调一点,JDK的实现方案必须要求目标对象是有接口的

Spring入门详解_第9张图片

在我这个结构中,SkillService是一个类,所以只有Userservice有UserServiceImpl实现类,也就只能拿到这一个的接口

<bean id="userService" class="com.harrison.service.impl.UserServiceImpl"/>
public class UserServiceImpl implements UserService{
    @Override
    public boolean login(String username, String password) {
        System.out.println("UserServiceImpl - login - " + username + password);
        return false;
    }
}
@Test
    public void test() {
        //创建容器
        ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
        //目标对象(这里本质上拿到的是UserServiceImpl对象,Userservice是其接口)
        UserService target = ctx.getBean("userService", UserService.class);
        //代理对象
        UserService userService = (UserService) Proxy.newProxyInstance(
                this.getClass().getClassLoader(), // 类加载器
                target.getClass().getInterfaces(),  // 代理类需要实现的接口,这里要求传入接口,所以没有接口的目标对象不能用
                new InvocationHandler() {   //附加代码(代理类的具体实现
                    @Override
                    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                        // proxy:代理对象
                        // method:目标方法
                        // args:目标方法的参数

                        System.out.println("proxy --1");

                        Object result = method.invoke(target, args);  // 把目标对象和参数传进去

                        System.out.println("proxy --2");
                        return result;
                    }
                }
        );
        userService.login("123", "456");

        //关闭容器
        ctx.close();
    }

我们的目标对象是UserserviceImpl,也可以叫业务对象,里面有业务方法,现在我们不直接调用这个对象的业务方法, 我们仅拿到他,然后通过Proxy.newProxyInstance拿到动态代理对象,之后用动态代理对象调用这个业务方法

代理对象创建:在Proxy.newProxyInstance方法中传入三个参数,类加载器、代理类需要实现的接口、附加代码

  • 类加载器:存在于JVM中,也就是ClassLoader通过class文件生成静态代理这个类的类对象;而在动态代理中,不需要class文件,字节码直接生成在JVM中,类加载器拿到字节码生成类对象,之后通过类对象才能new一个真正的实例
  • 代理类需要实现的接口:可以实现的业务方法
  • 附加代码:生成日志等信息

当运行到 userService.login(“123”, “456”);便会进入InvocationHandler方法内部执行相应的代码

代码优化

当我们执行ctx.getBean()方法,Spring便会去配置文件中找到相对应的bean对象并进行创建,创建过程遵循生命周期,在生命周期中提到过,BeanPostProcessor用来对创建完的bean进行进一步操作,并且每一个bean都会有且仅有这一步(两个函数会被调用,after和before),所以我们可以将代理对象的创建过程放到这里并返回(通常放到after方法中),因为我们一个bean只需要一个代理对象,最后,原本通过getBean拿到的是目标对象,经过这一操作就可以拿到代理对象了

/**
 * 会拦截每一个bean的生命周期
 */
public class LoginProcessor implements BeanPostProcessor {
    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        return bean;
    }

    @Override
    public Object postProcessAfterInitialization(Object target, String beanName) throws BeansException {
        return Proxy.newProxyInstance(  // 返回代理对象
                this.getClass().getClassLoader(),
                target.getClass().getInterfaces(),
                new LogInvocationHandler(target)
        );
    }
	// 单独写一个内部类中,代码可读性较高
    private static class LogInvocationHandler implements InvocationHandler{
        private final Object target;
        public LogInvocationHandler(Object target) {
            this.target = target;
        }
        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            System.out.println("proxy  -- 1");
            Object result = method.invoke(target, args);  // 执行业务代码,拿到其返回值
            System.out.println("proxy  -- 2");
            return result;   // 业务代码的返回值
        }
    }
}
    @Test
    public void test2() {
        ApplicationContext ctx =  new ClassPathXmlApplicationContext("applicationContext.xml");
        UserService userService = ctx.getBean("userService", UserService.class);  //直接拿到代理对象
        userService.login("123", "456");  // 拿到业务代码的返回值
    }
CGLib方案

这个是第三方库,Spring自带的,使用起来和JDK自带的几乎一致,只是可以额外给没有实现接口的对象也提供动态代理

    <bean class="com.harrison.processor.LoginProcessor2"/>
    <bean id="skillService" class="com.harrison.service.SkillService"/>
public class SkillService {
    public boolean save(Object skill){
        System.out.println("skillService - save");
        return true;
    }
}
/**
 * 会拦截每一个bean的生命周期
 */
public class LoginProcessor2 implements BeanPostProcessor {
    @Override
    public Object postProcessAfterInitialization(Object target, String beanName) throws BeansException {
        Enhancer enhancer = new Enhancer();
        enhancer.setClassLoader(this.getClass().getClassLoader());  // 可以省略,CGLib内部会帮我们自动传入类加载器
        enhancer.setSuperclass(target.getClass());
        enhancer.setCallback(new LogMethodInterceptor(target));
        return enhancer.create();
    }

    private static class LogMethodInterceptor implements MethodInterceptor {
        private Object target;
        public LogMethodInterceptor(Object target) {
            this.target = target;
        }
        @Override
        public Object intercept(Object o, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
            System.out.println("1-----------");
            Object result = method.invoke(target, args);
            System.out.println("2-----------");
            return result;
        }
    }
}

面向切面编程 - AOP - Aspect Riented Programming

添加依赖

<dependency>
    <groupId>org.aspectjgroupId>
    <artifactId>aspectjrtartifactId>
    <version>1.9.6version>
dependency>
<dependency>
    <groupId>org.aspectjgroupId>
    <artifactId>aspectjweaverartifactId>
    <version>1.9.6version>
dependency>

配置

实现MethodBeforeAdvice类

只能在业务代码执行后插入切面

public class LogAdvice implements MethodBeforeAdvice {
    @Override
    public void before(Method method, Object[] objects, Object o) throws Throwable {
        System.out.println("LogAdvice - before ----------");
    }
}
    
	<bean id="logAdvice" class="com.harrison.aop.LogAdvice"/>
    
    <aop:config>
        
        
        <aop:pointcut id="pc" expression="execution(* *(..))"/>
        
        <aop:advisor advice-ref="logAdvice" pointcut-ref="pc"/>
    aop:config>
    @Test
    public void test() {
        ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");

        SkillService skillService = ctx.getBean("skillService", SkillService.class);
        skillService.save(null);

        UserService userService = ctx.getBean("userService", UserService.class);
        userService.login("123", "456");
    }
实现MethodInterceptor类

在业务代码执行前后都可以插入切面

public class LogInterceptor implements MethodInterceptor {
    @Override
    public Object invoke(MethodInvocation methodInvocation) throws Throwable {
        System.out.println("1--------");
        // 调用目标对象的目标方法
        Object result = methodInvocation.proceed();
        System.out.println("2--------");
        return result;
    }
}
    
    <bean id="logInterceptor" class="com.harrison.aop.LogInterceptor"/>
    
    <aop:config proxy-target-class="false">
      
        
        
        <aop:pointcut id="pc" expression="execution(* *(..))"/>
        
        <aop:advisor advice-ref="logInterceptor" pointcut-ref="pc"/>
    aop:config>


这两行表示:先选取一个切面,也就是可以插入附加代码的方法,选取方法写在expression中,切面设计好后,选择想要添加的附加代码,通过advisor标签组合到一起

使用AOP技术,就无须考虑要用哪种方案实现代理

AOP底层实现

  • 如果目标类有实现接口,使用JDK实现
  • 如果目标类没有实现接口,使用CGLib实现
  • 在BeanPostProcessor的postProcessorAfterInitialization方法中创建代理对象(和我们之前写的是一样的,已经内置好了)
  • 可以通过proxy-target-class属性修改底层实现方案
    • true:强制使用CGLib
    • false:按照默认做法

AOP – 切入点表达式

完整写法;

expression=“execution(oublic String com.harrison.UserService.login(String, String))”

最简写法:

expression=“execution(* *(…))”

可以通过&&、||、!进行连接,如果拼接过程中有语法错误,前面如果是对的还是会运行前面的,后面的语法错误的就当不存在

  • 任意公共方法:execution(public * *(…))
  • 名字以set开头的任意方法:execution(* set*(…))
  • UserService接口定义的任意方法:execution(* com.harrison.service.UseService.*(…))
  • service包中定义的任意方法(com.harrison.service.test.testImpl不行):execution(* com.harrison.service.*.*(…))
  • service包中定义的任意方法(com.harrison.service.test.testImpl行):execution(* com.harrison.service…*.*(…))
  • 上面两个的区别是一个只能是service包下的第一个且是最后一个包,比如service下只能是test不能再有testImpl,而第二个就可以,不管有几层都行
  • 包含2个String参数的任意方法:execution(* *(String,String)),等价于args(String, String)
  • 只有1个Serializable参数的任意方法:args(java.io.Serializable),等价于execution(* *(java.io.Serializable))
  • service包中的任意方法:within(com.harrison.service.*)
  • service包中的任意方法(包括子包):within(com.harrison.service…*)
  • 带有自定义注解(Hehe)的方法(jdk自带的override之类的不可以):@annotation(com.harrison.aop.Hehe)

AOP – 细节

我们可能会遇到这样一种情景,在方法一中调用方法二,并且希望这两个方法的开始和结束都有日志打印,不难想到采用AOP技术即可轻松办到,但是实际中会发现,只有在方法一的开始和结束进行了日志打印,这是因为,我们是通过代理对象调用的方法一,而方法二是通过方法一调用的,两个方法内部本身是没有日志打印的,我们是在代理对象中添加的日志信息,由于代理对象只调用了方法一,所以只会有方法一的日志输出

public class LogInterceptor implements MethodInterceptor {
    @Override
    public Object invoke(MethodInvocation methodInvocation) throws Throwable {
        System.out.println("1--------");
        // 调用目标对象的目标方法
        Object result = methodInvocation.proceed();
        System.out.println("2--------");
        return result;
    }
}
public class UserServiceImpl implements UserService{
    @Override
    public boolean login(String username, String password) {  // 方法一
        System.out.println("UserServiceImpl - login - " + username + password);
        register(username, password);  // 方法一调用方法二
        return false;
    }
    @Override
    public boolean register(String username, String password) {  // 方法二
        System.out.println("UserServiceImpl - register - " + username + password);
        return false;
    }
}
    @Test
    public void test() {
        ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");

        UserService userService = ctx.getBean("userService", UserService.class);
        userService.login("123", "456");
    }

结果:

1--------
UserServiceImpl - login - 123456
UserServiceImpl - register - 123456
2--------

**解决方案:**让方法二也通过代理对象调用,而不是简单的直接调用

错误思路:new一个ApplicationContext对象ctx。这种重量级的玩意儿肯定是越少越好啊,要那么多干啥

正确思路:在前面的生命周期中,我介绍了ApplicationContextAware,第四个周期,通过这个就可以拿到ctx,而不需要重新new一个出来,getBean的时候,也可以不再写一遍userService,直接实现BeanNameAware拿到beanName就好了

public class UserServiceImpl implements UserService, BeanNameAware, ApplicationContextAware {
    private ApplicationContext ctx;
    private String beanName;
    // 方法一
    @Override
    public boolean login(String username, String password) {
        System.out.println("UserServiceImpl - login - " + username + password);
        ctx.getBean(beanName, UserService.class).register(username, password); // 调用代理对象
        return false;
    }
	// 方法二
    @Override
    public boolean register(String username, String password) {
        System.out.println("UserServiceImpl - register - " + username + password);
        return false;
    }
	// 拿到ctx
    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.ctx = applicationContext;
    }
	// 拿到beanName
    @Override
    public void setBeanName(String s) {
        this.beanName = s;
    }
}

结果:

1--------
UserServiceImpl - login - 123456
1--------
UserServiceImpl - register - 123456
2--------
2--------

声明式事务

配置

一般我们在Service层进行事务管理,很容易想到通过AOP在要执行SQL语句的方法前后加入事务管理,附加代码伪代码如下

public class TxInterceptor implements MethodInterceptor {
    @Override
    public Object invoke(MethodInvocation methodInvocation) throws Throwable {
        // 开启事务
      ...
        Object result = null;
        try{
            methodInvocation.proceed(); // 执行sql语句
            // 提交事务
          ...
        } catch(Exception e) {
            // 回滚事务
          ...
        }
        return result;
    }
}

然后将这个插入到切面中

    <bean id="txInterceptor" class="com.harrison.aop.TxInterceptor"/>
	<aop:config>
        <aop:pointcut id="pc" expression="within(com.harrison.service..*)"/>
        <aop:advisor advice-ref="txInterceptor" pointcut-ref="pc"/>
    aop:config>

Spring给我们提供了声明式事务,只需要在配置文件中配置

    
    <bean id="txMgr" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource"/>
    bean>
    
    <tx:advice id="txAdvice" transaction-manager="txMgr">  // 告诉事务管理器是谁
        <tx:attributes>  // 在这里要说清楚哪些方法要进行事务管理
            <tx:method name="list"/>
            <tx:method name="save"/>
            <tx:method name="update"/>
        tx:attributes>
    tx:advice>
    
    <aop:config>
        <aop:pointcut id="pc" expression="within(com.harrison.service..*)"/>
        <aop:advisor advice-ref="txAdvice" pointcut-ref="pc"/>
    aop:config>
事务传播行为

可以通过propagation属性设置事务的传播行为

  • 用于指定当事务嵌套时,如何管理实务
  • 当Service调用Service时,会出现事务嵌套

比如说我们设定了上面的list、save、update三个方法都是开启事务的,如果list中又调用了save和update,就称为事务嵌套,引发的问题就是,save是一个大事务,但是在save和update中各自开启了两个小事务,如果大事务中的小事务从开启到关闭期间都没有发生异常,那么就直接提交了,假如说第一个小事务没有异常,但是第二个小事务出现异常回滚了,本质上,我们是希望整个大事务都回滚的,希望其具有原子性,但现在面临的问题是由于事务嵌套而导致会有小事务提交,这时,我们通过设置事务传播行为即可解决

取值 当前有事务 当前无事务 常见使用场景
REQUIRED(默认) 支持当前事务 开启新事务 增、删、改
SUPPORTS 支持当前事务 不开启新事务 查询
MANDATORY 支持当前事务 抛出异常 要求当前一定要有事务
REQUIRES_NEW 挂起当前事务,开启新事务 开启新事务 日志记录
NOT_SUPPORTED 挂起当前事务 不开启新事务 不要事务,有事务也挂起
NEVER 抛出异常 不开启新事务
<tx:method name="list" propagation="SUPPORTS"/>
只读事务

如果一个事务只执行读操作,那么数据库可能会采取某些优化措施

  • read-onlu设置为true:告诉数据库这是个只读事务
  • 只适用于REQUIRED或REQUIRES_NEW
<tx:method name="list" propagation="REQUIRED" read-only="true"/>
超时

单位是秒,默认是-1(按照数据库默认情况处理),超时就会抛出异常

<tx:method name="list" timeout="4"/>
rollback-for、no-rollback-for

默认情况下,RuntimeException、Error会导致事务回滚,而Exception不会

  • rollback-for

    设置哪些异常会导致事务回滚(在RuntimeException、Error的基础上增加一些异常)

  • No-rollback-for

    设置哪些异常不会导致事务回滚(在Exception的基础上增加一些异常)

isolation
事务隔离级别
  • 多个事务同时操作同一份数据,可能引发以下问题
    • 脏读:一个事务读取到了另一个事务没有提交的数据
    • 不可重复读:一个事务范围内两个相同的查询却返回了不同的数据
    • 幻读:一个事务发现了之前本来确认不存在的数据
  • 可以设置隔离级别来解决上述问题(由上到下,性能依次降低)
    • READ UNCOMMITTED:什么也解决不了
    • READ COMMITED:可以防止脏读
    • REPEATABLE READ:可以防止脏读、不可重复读(MySQL的默认隔离级别)
    • SERIALIZABLE:可以防止脏读、不可重复读、幻读
<tx:method name="list" isolation="SERIALIZABLE"/>
查询隔离级别的SQL语句
SELECT @@TX_ISOLATION
设置隔离级别
SET GLOBAL TRANSACTION ISOLATION LEVEL 隔离级别(多个单词之间用空格隔开)

多个配置文件

如果Spring配置文件的内容过多,可以采取多个配置文件的形式

方法一
new ClassPathXmlApplicationContext("applicationContext.xml", "applicatinContext-aop.xml");
方法二
new ClassPathXmlApplicationContext("applicationContext.xml");
<import resource="applicationContext-aop.xml"/>

在主配置文件中import其他的配置文件,然后在java中只加载主配置文件即可

方法三
new ClassPathXmlApplicationContext("classpath*:applicationContext*.xml");

使用通配符,加载所有以applicationContext开头的配置文件,注意,前面必须加上classpath*:

注解

@Component

注解实现bean标签

添加注解@Component,相当于,将Skill对象放到IoC容器里面去等待被getBean

@Component
public class Skill {
}
component-scan

在配置文件中指明扫描范围,可以写多个,不写的话Spring不知道从哪里入手



也可以写一个,然后在扫描到的包内部随便找一个类,加上@ComponentScan扫描其他包

@Component
//@ComponentScan("com.harrison.dao")
@ComponentScans({
        @ComponentScan("com.harrison.service"),
        @ComponentScan("com.harrison.aop")
})
public class Skill {
}

通过getBean获取

ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
Skill skill = ctx.getBean("skill", Skill.class);

通过value属性设置bean的id,也就是getBean方法的第一个参数,默认会将类名的首字母小写形式作为bean的id

@Component(value="skill1")

Skill skill = ctx.getBean("skill1", Skill.class);

@Controller用于标识控制器层,@Service用于标识业务层,@Repository用于标识Dao层

这三个是从@Component衍生出来的,功能完全一样,只是为了便于区分

@Autowired

自动注入,只能是Bean类型的

@Component
public class User {
    private Dog dog;
    
    @Autowired
    public void setDog(Dog dog) {
        this.dog = dog;
    }
}

等同于

    <bean id="user" class="com.harrison.domain.User">
        <property name="dog" ref="dog"/>
    bean>

这一堆bean标签通过一个注解即可搞定,前提是要有set方法,并且参数中的Dog对象已经在IoC容器中(添加@Component注解或者添加标签)

如果连set方法都不想写,就添加到成员变量上

@Component
public class User {
    @Autowired
    private Dog dog;
}

或者直接写一个构造方法,但是一般不这么用

@Component
public class User {
    private Dog dog;
    public User(Dog dog){
        this.dog = dog;
    }
}

同时可以配合@Qualifier,设置需要注入的bean的id,不写的话Spring是按照setter参数中的类型去看IoC容器中是否有类型一致的,而不是去看id名(设置id名上面说了,是通过@Component(value=“id名”))

required设置为false(默认为true):找不到对应的bean时不会抛出异常

@Component
public class User {
    private Dog dog;

    @Autowired(required = false)
    @Qualifier("dog2")
    public void setDog(Dog dog) {
        this.dog = dog;
    }
}
@Value

自动注入,注入的类型必须是基本类型、BigDecimal、String

@PropetySource

读取配置文件,可搭配上面的使用,通过${ }引用配置文件中的内容

等价于

@Component
@PropertySource("user.properties")
public class User {
    private Dog dog;
    private String name;

    @Value("${name}")
    public void setName(String name) {
        this.name = name;
    }
}
// 配置文件放在resources文件夹下,否则要修改路径
// 配置文件中:name=Jack
// 如果不用配置文件的写法
@Value("Jack")
@Aspect

切面实现,里面的方法必须接受一个参数ProceedingJoinPoint并抛出Throwable异常

此外,必须添加@Component注解,将切面放入IoC容器中,否则无法调用目标方法

@Around

切入点实现,告诉Spring切入点在哪里

还需要加上标签,替代之前的

同样有proxy-target-class属性设置AOP的底层实现方案

<aop:aspectj-autoproxy/>
@Aspect
@Component
public class DefaultAspect {
    @Around("within(com.harrison)")
    public Object log(ProceedingJoinPoint point) throws Throwable {
        System.out.println("1---------");

        Object result = point.proceed();

        System.out.println("2---------");
        return result;
    }
}
@Service("userService")
public class UserServiceImpl implements UserService {
    @Override
    public void list() {
        System.out.println("UserServiceImpl -- list");
    }
}
@Test
public void test()  {
    ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
    UserService userService = ctx.getBean("userService", UserService.class);
    userService.list();
}
@Pointcut

现在有两个附加代码,它们的切入点是一样的,在@Around中写两遍一样的切入点不方便之后的修改,为此,可以将其提取出来

@Aspect
@Component
public class DefaultAspect {
    @Around("within(com.harrison)")
    public Object log(ProceedingJoinPoint point) throws Throwable {
        System.out.println("1---------");

        Object result = point.proceed();

        System.out.println("2---------");
        return result;
    }
    @Around("within(com.harrison)")
    public Object watch(ProceedingJoinPoint point) throws Throwable {
        System.out.println("3---------");

        Object result = point.proceed();

        System.out.println("4---------");
        return result;
    }
}

提取后

只需写一个空方法,我这里是pc,然后添加注解@Pointcut,将切入点写进去即可,之后的@Around注解里面直饮用pc()这个方法即可

@Aspect
@Component
public class DefaultAspect {
    @Pointcut("within(com.harrison)")
    public void pc() {}
    @Around("pc()")
    public Object log(ProceedingJoinPoint point) throws Throwable {
        System.out.println("1---------");

        Object result = point.proceed();

        System.out.println("2---------");
        return result;
    }
    @Around("pc()")
    public Object watch(ProceedingJoinPoint point) throws Throwable {
        System.out.println("3---------");

        Object result = point.proceed();

        System.out.println("4---------");
        return result;
    }
}

如果我们的配置文件里面的aop也想用这个切入点,不想写那么一大串文字,直接通过类名.方法名即可

    <aop:config>
        <aop:pointcut id="pc" expression="com.harrison.aop.DefaultAspect.pc()"/>
        <aop:advisor advice-ref="logInterceptor" pointcut-ref="pc"/>  // 其他附加代码
    aop:config>
@Transactional

自动管理事务,之前配置文件我们需要事务管理器,事务管理代码以及切面,通过注解的方式,只需要一个事务管理器以及注解驱动器和一个注解即可

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

    <bean id="dataSource" 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>
    
    <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
        
        <property name="dataSource" ref="dataSource"/>
        
        <property name="typeAliasesPackage" value="com.harrison.domain"/>
        
        <property name="mapperLocations">
            <array>
                <value>mappers/*.xmlvalue>
            array>
        property>
    bean>
    
    <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
        
        <property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"/>
        
        <property name="basePackage" value="com.harrison.dao"/>
    bean>

	// 从这里开始看
    <aop:aspectj-autoproxy/>
	// 事务管理器
    <bean id="txMgr" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource"/>
    bean>
	// 这一行取代了事务管理代码和切面
    <tx:annotation-driven transaction-manager="txMgr"/>
@Service("userService")
@Transactional
public class UserServiceImpl implements UserService {
    private UserDao dao;

    @Autowired
    public void setDao(UserDao dao) {
        this.dao = dao;
    }

    @Override
    @Transactional(isolation = Isolation.SERIALIZABLE)
    public void list() {
        dao.list();
    }
}

可以直接在整个实现类上添加注解,也可以像之前写事务管理代码一样给不同的方法写不同的事务管理

@Configuration、@Bean

使用@Configuration的类,可以取代applicationContext.xml,并且这个类会放入IoC容器中当作一个对象

@Configuration
public class BeanConfig {
}
@Test
public void test() {
  ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
  System.out.println(ctx.getBean("beanConfig"));
}

输出结果

com.harrison.cfg.BeanConfig$$EnhancerBySpringCGLIB$$3062806e@1165b38
bean对象注入

可以在@Configuration类中,使用@Bean修饰方法,进行bean对象的创建

@Configuration
public class BeanConfig {
    @Bean
    public Skill skill() {
        return new Skill();
    }
}
    @Test
    public void test() {
        ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
        System.out.println(ctx.getBean("skill"));
    }

默认情况下,方法名就是bean的id,也可以通过name、value属性设置bean的id

也就是说,getBean中填写的id就是@Bean的方法名

@Configuration
public class BeanConfig {
    @Bean(name = "skill123")
    public Skill skill() {
        return new Skill();
    }
}
    @Test
    public void test() {
        ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
        System.out.println(ctx.getBean("skill123"));
    }

可以配合@Scope设置bean的创建次数

@Configuration
public class BeanConfig {
    @Bean(name = "skill123")
    @Scope("prototype")
    @Lazy
    public Skill skill() {
        return new Skill();
    }
}

**注意:**如果有一个Person类,里面有一个成员dog,那么我们给person添加@Bean的时候,里面的dog需要先放入IoC容器中,注解或xml配置都可以,如果没有dog的话,person也是创建失败的

当@Bean注解出现在如下的Person方法中,参数dog也会自动注入,前提是dog已经在IoC容器中,所以我们在上面给Dog也加了一个Bean

@Configuration
public class BeanConfig {
    @Bean
    public Dog dog() {
        return new Dog();
    }
    @Bean
    public Person person(Dog dog) {
        Person person = new Person();
        person.setDog(dog);
        return person;
    }
}

此外,dog对象我们还可以直接调用dog方法拿到,并且多次调用也能保证是同一对象,也就是singleton的

原因是@Configuration底层使用了CGLib动态代理,对@Bean方法进行了增强处理,看到的是BeanConfig调用的person方法,实际是BeanConfig的代理对象,这个代理对象如果发现IoC容器里已经创建了dog对象,那么就会拦截new Dog(),直接返回之前创建的dog

    @Bean
    public Person person() {
        Person person = new Person();
        person.setDog(dog());
        return person;
    }
FactoryBean注入

通过注解实现实例工厂FactoryBean

public class DogFactoryBean implements FactoryBean<Dog> {
    @Override
    public Dog getObject() throws Exception {
        return new Dog();
    }

    @Override
    public Class<?> getObjectType() {
        return Dog.class;
    }
}
@Configuration
public class BeanConfig {
    @Bean
    public DogFactoryBean dog() {
        return new DogFactoryBean();
    }

    @Bean
    public Person person() throws Exception {
        Person person = new Person();
        person.setDog(dog().getObject());  // 如果在Person中@Autowired了dog对象,这个就不需要了
        return person;
    }
}

我们还可以和上面一样,直接把FactoryBean放到参数中

    @Bean
    public Person person(DogFactoryBean dogFactoryBean) throws Exception {
        Person person = new Person();
        person.setDog(dogFactoryBean.getObject());
        return person;
    }

FactoryBean和bean生成的区别就在于多了一个FactoryBean类和调用的时候是调用dog.getObject()而不是直接调用dog()

基本类型注入

我们给skill对象注入一个String类型的,而不是之前的bean类型的属性

普通方法

    @Bean
    public Skill skill() {
        Skill skill = new Skill();
        skill.setName("Jack");
        skill.setLevel(1);
        return skill;
    }

通过配置文件注入

name=Jack
level=1
@Configuration
@PropertySource("skill.properties")
public class BeanConfig {
    @Value("${name}")
    private String name;
    @Value("${level}")
    private Integer level;

    @Bean
    public Skill skill() {
        Skill skill = new Skill();
        skill.setName(name);
        skill.setLevel(level);
        return skill;
    }
}

纯注解开发

创建工厂的入口

  • 如果创建工厂的入口是XML,一般就用ClassPathXmlApplicationContext

    • 可以通过去扫描注解信息
    • 可以通过导入其他配置文件
    ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
    
  • 如果创建工厂的入口是注解,一般就用AnnotationConfigApplicationContext

    然后给类加上@Component

    ctx = new AnnotationConfigApplicationContext("com.harrison");
    // 单个类
    ctx = new AnnotationConfigApplicationContext(Person.class, User.class);
    

    如果想要通过一个类导入其他类,就像xml中的import一样

    • 我们使用@Import(全类名.class)
    • 也可以使用@ComponentScan(“com.harison”)

    如果想要导入配置文件,可以在入口的类里添加@ImportSource(“applicationContext.xml”)

    以上这些必须先是@Component(@Configuration也行,因为@Configuration本质是@Component)

bean创建方式

在Spring中,bean有3种常见的创建方式

  • @Component:常用于源码可修改、创建过程比较简单(直接调用构造方法即可)的类型
  • @Bean:常用于源码不可修改的类型(比如第三方库的类型)或者创建过程比较复杂的类型
  • :适用于所有类型

bean的优先级

当上述三种方式bean的id相同时,优先级如下:

  • 大于@Bean大于@Component

  • 因为可以用于覆盖以前@Bea@Component的内容,所以可以减少@Bean、@Component代码的变动

    假如现在的代码全是用注解完成的,这是我想给现有的@Bean修改一下属性,为了不更改之前的代码,可以直接新建一个类,并且新建一个配置文件,然后在新建的类中@ImportSource(新建的配置文件),在这个配置文件中对要修改的Bean进行修改,由于配置文件的bean优先级最高,因此可以覆盖之前在注解中的代码

纯注解实现AOP

之前我们通过注解@Aspect实现切面的插入,但是仍需要配置一个,现在通过纯注解开发方式@EnableAspectJAutoProxy,去掉这个

附加代码

@Component
@Aspect
public class DefaultAspect {
    @Pointcut("within(com.harrison.service..*)")
    public void pc() {}

    @Around("pc()")
    public Object log(ProceedingJoinPoint point) throws Throwable {
        System.out.println("log ----------");
        Object result = point.proceed();
        System.out.println("log ----------");
        return result;
    }
}

业务代码

@Service("userService")
public class UserServiceImpl implements UserService {
    @Override
    public void list() {
        System.out.println("UserServiceImpl -- list");
    }
}

通过总配置类进行配置@EnableAspectJAutoProxy,替代配置文件中的

<aop:aspectj-autoproxy/>

写到附加代码那个类也行,这个类相当于整合配置的类

@Configuration
@EnableAspectJAutoProxy
@ComponentScan("com.harrison")
public class AppConfig {
}
    @Test
    public void test1() {
        ApplicationContext ctx = new AnnotationConfigApplicationContext(AppConfig.class);
        UserService service = ctx.getBean("userService", UserService.class);
        service.list();
    }

纯注解整合MyBatis

在配置文件中,我们需要对MyBatis进行数据源Druid,SqlSession以及扫描dao三项进行配置,本质上就是创建了这三个对象


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

    <context:property-placeholder location="db.properties"/>
    
    <bean id="dataSource" 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>
    
    <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
        
        <property name="dataSource" ref="dataSource"/>
        
        <property name="typeAliasesPackage" value="com.harrison.domain"/>
        
        <property name="mapperLocations">
            <array>
                <value>mappers/*.xmlvalue>
            array>
        property>
    bean>
    
    <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
        
        <property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"/>
        
        <property name="basePackage" value="com.harrison.dao"/>
    bean>
beans>

在纯注解开发中,我们依次将这三个复杂对象通过@Bean的形式进行创建

文件列表如下

Spring入门详解_第10张图片
@Configuration
@PropertySource("db.properties")
@MapperScan("${mybatis.mapperScan}")
public class MyBatisConfig {
    @Value("${jdbc.driverClassName}")
    private String driverClassName;
    @Value("${jdbc.url}")
    private String url;
    @Value("${jdbc.username}")
    private String username;
    @Value("${jdbc.password}")
    private String password;
    @Value("${mybatis.typeAliasesPackage}")
    private String typeAliasesPackage;
    @Value("${mybatis.mapperLocations}")
    private String mapperLocations;

    @Bean
    public DataSource dataSource() {
        DruidDataSource ds = new DruidDataSource();
        ds.setDriverClassName(driverClassName);
        ds.setUrl(url);
        ds.setUsername(username);
        ds.setPassword(password);
        return ds;
    }

    @Bean
    public SqlSessionFactoryBean sqlSessionFactory() throws Exception {
        SqlSessionFactoryBean bean = new SqlSessionFactoryBean();
        bean.setDataSource(dataSource());
        bean.setTypeAliasesPackage(typeAliasesPackage);
        PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
        bean.setMapperLocations(resolver.getResources(mapperLocations));
        return bean;
    }
  
// 	扫描dao对象不用创建了,因为只是拿到两个name,sqlSessionFactory在上面创建好以后就会放入IoC容器中,而dao包的扫描可以交给@MapperScan注解来完成
  
//    @Bean
//    public MapperScannerConfigurer configurer() {
//        MapperScannerConfigurer configurer = new MapperScannerConfigurer();
//        configurer.setSqlSessionFactoryBeanName("sqlSessionFactory");
//        configurer.setBasePackage("com.mj.dao");
//        return configurer;
//    }
jdbc.driverClassName=com.mysql.cj.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/resume?useSSL=false&serverTimezone=Asia/Shanghai
jdbc.username=root
jdbc.password=root

mybatis.typeAliasesPackage=com.harrison.domain
mybatis.mapperLocations=mappers/*.xml
mybatis.mapperScan=com.harrison.dao
    @Test
    public void list() {
        ApplicationContext ctx = new AnnotationConfigApplicationContext("com.harrison");
        SkillDao dao = ctx.getBean("skillDao", SkillDao.class);
        List<Skill> list =  dao.list();
        System.out.println(list);
    }

注意一下好多类都是有好几个包,看仔细不要导错了,惨痛教训,导错了有些方法就会报错

纯注解事务管理

一开始,我们使用纯配置文件进行事务管理,在配置了MyBatis的数据源,SqlSessionFactory以及扫描dao以后,再配置事务管理器,附加代码和切面

 
    <bean id="txMgr" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource"/>
    bean>
    
    <tx:advice id="txAdvice" transaction-manager="txMgr">  // 告诉事务管理器是谁
        <tx:attributes>  // 在这里要说清楚哪些方法要进行事务管理
            <tx:method name="list"/>
            <tx:method name="save"/>
            <tx:method name="update"/>
        tx:attributes>
    tx:advice>
    
    <aop:config>
        <aop:pointcut id="pc" expression="within(com.harrison.service..*)"/>
        <aop:advisor advice-ref="txAdvice" pointcut-ref="pc"/>
    aop:config>

后面通过注解,我们把附加代码和切面这两个配置去掉了,新添加了一个

<tx:annotation-driven transaction-manager="txMgr"/>

然后给我们想要进行事务管理的类添加@Transactional,并且可以给里面的方法添加传播行为,隔离级别等

现在,我们进行纯注解配置事务,也就是把事务管理器放到IoC容器中配置事务管理器对象,这里的参数dataSource是配置MyBatis时就有的Druid连接池对象,会自动注入(报错不用管,idea的问题)

@Configuration
@EnableTransactionManagement
public class TxConfig {
    @Bean
    public DataSourceTransactionManager manager(DataSource dataSource) {
        DataSourceTransactionManager mgr = new DataSourceTransactionManager();
        mgr.setDataSource(dataSource);
        return mgr;
    }
}

然后配置想要进行事务管理的service

@Service("skillService")
@Transactional
public class SkilServiceImpl implements SkillService {
    private SkillDao dao;

    @Autowired
    public void setDao(SkillDao dao) {
        this.dao = dao;
    }

    @Override
    public List<Skill> list() {
        return dao.list();
    }

    @Override
    public boolean save(Skill skill) throws Exception {
        dao.save(skill);
        throw new Exception("sdkfj");
    }
}

然后就可以了

    @Test
    public void list() {
        ApplicationContext ctx = new AnnotationConfigApplicationContext("com.harrison");
        SkillService service = ctx.getBean("skillService", SkillService.class);
        try{
            service.save(null);
        } catch(Exception e) {
            e.printStackTrace();
        }
    }

JSR注解

作用

JSR是Java Specification Requests的缩写,译为Java规范提案

  • 是指向JCP(Java Community Process)提出新增一个标准化技术规范的正式请求

  • 任何人都可以提交JSR,以向Java平台增添新的API和服务

  • 一旦某个JSR通过了JCP的审核,它就变成了Java技术栈的一部分,可以安全地用于生产环境

  • JSR的审核过程确保了只有可靠稳定的技术才能变成Java的一部分,避免过度臃肿和膨胀

Spring也支持JSR规范中定义的一些注解

  • JSR 250:@Resource
  • JSR 350:@Inject
  • JSR 520:@PostConstruct、@PreDestroy

依赖

        <dependency>
            <groupId>javax.injectgroupId>
            <artifactId>javax.injectartifactId>
            <version>1version>
        dependency>
        <dependency>
            <groupId>javax.annotationgroupId>
            <artifactId>javax.annotation-apiartifactId>
            <version>1.3.2version>
        dependency>

@Resource

根据名称进行注入,可以用在成员变量、setter上

@Resource(name = “dog”)等价于@Autowired、@Qualifier(“dog”)

@Inject

根据类型进行注入,可以用在成员变量、setter、构造方法上

类似于@Autowired,基本上是一样的

在众多构造方法中,如果直接调用getBean(“person”),它会调用无餐构造方法,假如说现在希望它调用的是有参构造方法,可以加一个@Inject,由于是按类型注入,发现容器中有dog,就自动注入,如果希望按id名注入,就加一个@Named(id名),第二个参数String由于不是bean对象,不会存在于容器中,因此可以给通过@Value赋值,如果希望不赋值,就加一个@Nullable

@Component
public class Person {
    @Resource(name = "dog2")
    private Dog dog;

    private String name;

    public Person() {}

    @Inject
    public Person(@Named("dog2") Dog dog, @Value("Jack") String name) {
        this.dog = dog;
        this.name = name;
    }

    @Override
    public String toString() {
        return "Person{" +
                "dog=" + dog +
                '}';
    }
}

@PostConstruct、@PreDestroy

之前在生命周期提到过,bean标签有两个属性,init-method和destroy-method,分别对应生命周期的两个方法,下面要讲的这两个注解就是用来取代这两个属性的

  • @PostConstruct, 在InitializingBean的afterPropertiesSet方法之前调用

  • @PreDestroy,在DisposableBean的destroy方法之前调用

property-placeholder底层

底层使用了PropertySOurcesPlaceholderConfiguer类

可以直接使用这个类,取代、@PropertySources、@PropertySource

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

等同于

<bean class="org.springframework.context.support.PropertySourcesPlaceholderConfigurer">
  <property name="location" value="db.properties"/>
bean>

等同于

    @Bean
    public PropertySourcesPlaceholderConfigurer configurer() {
        PropertySourcesPlaceholderConfigurer configurer = new PropertySourcesPlaceholderConfigurer();
        configurer.setLocation(new ClassPathResource("db.properties"));
        return configurer;
    }

component-scan

之前讲@Copmponent时说过,可以使用下面这个标签来指明Spring可以扫描哪些包拿到注解

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

然而这么大的范围可能里面包含了一部分不希望被扫描到的包,可以通过来缩小范围

  • type指明assignable,后面跟不想被扫描到的类
    <context:component-scan base-package="com.harrison">
        <context:exclude-filter type="assignable" expression="com.harrison.domain.Person"/>
    context:component-scan>
  • type为aspects,后接切面表达式,符合表达式的就不扫描
    <context:component-scan base-package="com.harrison">
        <context:exclude-filter type="aspectj" expression="within(com.harrison.service..*)"/>
    context:component-scan>
  • type为regex,后面为正则表达式,符合表达式的就不扫描
    <context:component-scan base-package="com.harrison">
        <context:exclude-filter type="regex" expression=".*My.*"/>
    context:component-scan>
  • type为annotation,后面跟注解的类名,带有这个注解的类就不扫描
    <context:component-scan base-package="com.harrison">
        <context:exclude-filter type="annotation" expression="org.springframework.stereotype.Service"/>
    context:component-scan>
  • type为custom,后面跟类型过滤器
    <context:component-scan base-package="com.harrison">
        <context:exclude-filter type="custom" expression="com.harrison.filter.MyFilter"/>
    context:component-scan>

通过实现TypeFilter创建自定义过滤器,返回值就是想要过滤掉的类名,方法有很多。

实现的这个接口的方法是在扫描到一个类后就会调用一次

public class MyFilter implements TypeFilter {
    @Override
    public boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory) throws IOException {
        // 拿到类名
        String name = metadataReader.getClassMetadata().getClassName();
        // return name.contains("My");
        return name.matches(".*My.*");
    }
}

上面讲解的是不想要包含哪些标签,如果只想要哪些标签,用

但是要将use-default-filters设置为false,因为默认的话componeng-scan标签会扫描base-package包中的所有东西,配合exclude-filter标签排除一些类,include-filter此时是不起作用的

use-default-filters设置为false就代表默认不扫描包里任何东西,配合include-filter扫描一些包

    <context:component-scan base-package="com.harrison" use-default-filters="false">
        <context:include-filter type="assignable" expression="com.harrison.domain.Person"/>
    context:component-scan>

换成注解使用

@Component
@ComponentScan(basePackages = "com.harrison", excludeFilters = {
        @ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE, classes = {Dog.class}),
        @ComponentScan.Filter(type = FilterType.ANNOTATION, classes = Service.class),
        @ComponentScan.Filter(type = FilterType.CUSTOM, classes = MyFilter.class),
        @ComponentScan.Filter(type = FilterType.REGEX, pattern = ".*My.*"),
        @ComponentScan.Filter(type = FilterType.ASPECTJ, pattern = "com.harrison.service..*")
                })
public class Person {
}

你可能感兴趣的:(Java,spring,java)