Spring

2022/1/22 javaSSM框架

# 1. 快速入门

# 1.1 环境搭建

Spring依赖

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

Spring的配置文件

  • 配置文件的位置没有硬性要求
  • 配置文件的命名:建议applicationContext.xml

# 1.2 核心API

  • ApplicationContext(接口):用于对象的创建 (重量级资源,一个应用只会创建一个对象)

    ClassPathXmlApplicationContext:用于非web环境

    XmlWebApplicationContext:用于web环境(需要spring-webmvc依赖)

# 1.3 程序开发

# 1.3.1 案例展示

当前项目目录

  1. applicationContext.xml中配置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="person" class="com.meinil.demo.Person" />
    </beans>
    
  2. Main中获取对象

    public class Main {
        public static void main(String[] args) {
            ApplicationContext ctx = new ClassPathXmlApplicationContext("/applicationContext.xml");
            Person p1 = (Person) ctx.getBean("person");         // 返回Object需要强转
            Person p2 = ctx.getBean("person", Person.class);    // 可以直接传入类型
            Person p3 = ctx.getBean(Person.class);              // 如果采用此方式,Spring的配置文件中只能有一个Person的实例
            System.out.println(p1);
            System.out.println(p2);
            System.out.println(p3);
        }
    }
    

# 1.3.2 其他API

  1. 获取配置文件中所有beanid

    String[] names = ctx.getBeanDefinitionNames();   
    for (String name : names) {
        System.out.println(name);
    }
    
  2. 获取Person类型的所有beanid

    String[] names = ctx.getBeanNamesForType(Person.class); 
    for (String name : names) {
        System.out.println(name);
    }
    
  3. 判断某id值的bean是否存在

    boolean exist = ctx.containsBeanDefinition("person");
    System.out.println(exist);
    
  4. 与上述大概相同,但支持name属性

    boolean exist = ctx.containsBean("person");
    System.out.println(exist);
    

# 1.3.3 配置文件

  1. 仅配置class属性

    <bean class="com.meinil.demo.Person" />
    

    对于不配置id值的beanspring同样可以获取到,id默认值为com.meinil.demo.Person#0

    public class Main {
        public static void main(String[] args) {
            ApplicationContext ctx = new ClassPathXmlApplicationContext("/applicationContext.xml");
            Person person = ctx.getBean(Person.class);               
            System.out.println(person);
    
            String[] names = ctx.getBeanDefinitionNames();    
            for (String name : names) {
                System.out.println(name);
            }
        }
    }
    

    应用场景:如果这个bean只需要使用一次,那么就可以省略id值。如果这个bean会使用多次,或者被其他bean引用则需要设置id

  2. name属性配置别名

    <bean id="person" name="p" class="com.meinil.demo.Person" />
    
    public class Main {
        public static void main(String[] args) {
            ApplicationContext ctx = new ClassPathXmlApplicationContext("/applicationContext.xml");
            Person person = ctx.getBean("p", Person.class);
            System.out.println(person);
        }
    }
    

    区别:name可以定义多个,id只能定义一个。

    <bean name="p1, p2" class="com.meinil.demo.Person" />
    

# 1.3.4 其他细节

Spring工厂可以调用对象私有的构造方法创建对象

Spring管理的对象一般不包括实体类,因为实体类的数据是需要从数据库进行读取的,所以应该交给DAO层进行管理

# 2. Spring日志

Spring5.x默认日志框架是logbacklog4j2

  1. 引入相关依赖

    <dependency>
        <groupId>org.apache.logging.log4j</groupId>
        <artifactId>log4j-core</artifactId>
        <version>2.17.0</version>
    </dependency>
    <dependency>
        <groupId>org.apache.logging.log4j</groupId>
        <artifactId>log4j-api</artifactId>
        <version>2.17.0</version>
    </dependency>
    <dependency>
        <groupId>org.apache.logging.log4j</groupId>
        <artifactId>log4j-slf4j-impl</artifactId>
        <version>2.17.0</version>
    </dependency>
    <dependency>
        <groupId>org.slf4j</groupId>
        <artifactId>slf4j-api</artifactId>
        <version>1.7.32</version>
    </dependency>
    
  2. 新建log4j2.xml

    <?xml version="1.0" encoding="UTF-8"?>
    <!--设置日志级别 OFF > FATAL > ERROR > WARN > INFO > DEBUG > TRACE > ALL-->
    <configuration status="DEBUG">
    	<!--先定义所有的appender -->
    	<appenders>
    		<!--输出到控制台 -->
    		<Console name="Console" target="SYSTEM_OUT">
    			<!--输出日志的格式 -->
    			<PatternLayout pattern="%d{HH:mm:ss.SSS} %-5level %class{36} %L %M - %msg%xEx%n"/>
    		</Console>
    	</appenders>
    
    
    	<!--然后定义logger,只有定义了logger并引入的appender,appender才会生效 -->
    	<loggers>
    		<root level="DEBUG">
    			<appender-ref ref="Console"/>
    		</root>
    	</loggers>
    </configuration>
    
  3. 测试

    public class Main {
        private static final Logger log = LoggerFactory.getLogger(Main.class);
        public static void main(String[] args) {
            log.info("日志测试");
        }
    }
    

# 3. set注入

注入:通过Spring工厂及配置文件,为所创建对象的成员变量赋值

改造person

public class Person {
    private Integer id;
    private String name;

    // get set方法
}

# 3.1 为什么需要注入

通过编码的方式,为成员变量进行赋值,存在耦合

public class Main {
    public static void main(String[] args) {
        ApplicationContext ctx = new ClassPathXmlApplicationContext("/applicationContext.xml");
        Person person = ctx.getBean("person", Person.class);

        person.setId(1);			// 赋值语句存在耦合
        person.setName("小明");
        
        System.out.println(person);
    }
}

# 3.2 开发步骤

  1. 为类成员变量提供getset方法

  2. applicationContext.xml文件中配置bean实例

    <bean id="person" class="com.meinil.demo.Person">
        <property name="id" value="1" />
        <property name="name" value="小明" />
    </bean>
    
  3. 调用测试

    public class Main {
        public static void main(String[] args) {
            ApplicationContext ctx = new ClassPathXmlApplicationContext("/applicationContext.xml");
            Person person = ctx.getBean("person", Person.class);
            System.out.println(person);
        }
    }
    

Spring的注入是通过对象的set方法实现,所以对象的属性必须提供set方法

# 3.3 注入详解

set注入的属性不仅仅只是IntegerString类型,还可以有以下类型

当前项目结构


# 3.3.1 内置类型

要操作的类

public class Person {
    private Integer id;
    private String name;
    private String[] emails;
    private Set<String> tels;
    private List<String> addresses;
    private Map<String, String> qqs;
    private Properties teachers;
    
    // get set
}

String+8种基本类型直接使用value标签即可

<property name="id">
    <value>18</value>
</property>
<property name="name">
    <value>小明</value>
</property>

数组类型

<property name="emails">
    <array>
        <value>yuunn@163.com</value>
        <value>taotao@gmail.com</value>
        <value>567723234@qq.com</value>
    </array>
</property>

Set集合

<property name="tels">
    <set>
        <value>1223452342</value>
        <value>1234343335</value>
        <value>6786782342</value>
    </set>
</property>

List

<property name="addresses">
    <list>
        <value>北京</value>
        <value>上海</value>
        <value>广东</value>
    </list>
</property>

map

<property name="qqs">
    <map>
        <entry>
            <key>
                <value>张某</value>
            </key>
            <value>4566344</value>
        </entry>
        <entry>
            <key>
                <value>徐某</value>
            </key>
            <value>4564564</value>
        </entry>
    </map>
</property>

Properties特殊类型的Map,键和值都只能是String

<property name="teachers">
    <props>
        <prop key="生物">张老师</prop>
        <prop key="化学">李老师</prop>
    </props>
</property>

# 3.3.2 自定义类型

要操作的类

// Person.java
public class Person {
    private Student stu;
    // get set
}

// Student.java
public class Student {
    private String name;
    private Integer age;
    // get set
}

配置applicationContext.xml文件

<bean id="person" class="com.meinil.demo.Person">
    <property name="stu">
			<bean class="com.meinil.demo.Student">
				<property name="age" value="18" />
				<property name="name" value="小王吧" />
			</bean>
    </property>
</bean>

也可以使用引用的方式

<bean id="student" class="com.meinil.demo.Student">
    <property name="name">
        <value>大王</value>
    </property>
    <property name="age">
        <value>12</value>
    </property>
</bean>
<bean id="person" class="com.meinil.demo.Person">
    <property name="stu">
        <ref bean="student" />
    </property>
</bean>

# 3.3.3 简化写法

属性简化

value以及ref标签可以简化为属性写法

<bean id="student" class="com.meinil.demo.Student">
    <property name="name" value="大王"/>
    <property name="age" value="12"/>
</bean>
<bean id="person" class="com.meinil.demo.Person">
    <property name="stu" ref="student"/>
</bean>

命名空间简化

<bean id="student" class="com.meinil.demo.Student" p:name="大王" p:age="12" />
<bean id="person" class="com.meinil.demo.Person" p:stu-ref="student" />

实际上这个p指的就是property

# 4. 构造注入

构造注入:Spring通过调用构造方法为成员变量赋值

# 4.1 开发步骤

  1. 提供有参构造方法

    public class Customer {
        private String name;
        private Integer age;
    
        public Customer(String name, Integer age) {
            this.name = name;
            this.age = age;
        }
    }
    
  2. Spring配置文件进行配置

    <bean id="customer" class="com.meinil.demo.Customer">
        <constructor-arg>
            <value>石昊</value>
        </constructor-arg>
        <constructor-arg>
            <value>5</value>
        </constructor-arg>
    </bean>
    

# 4.2 构造方法重载

public class Customer {
    private String name;
    private Integer age;

    public Customer(String name, Integer age) {
        this.name = name;
        this.age = age;
    }
    public Customer(String name) {
        this.name = name;
    }
    public Customer(Integer age) {
        this.age = age;
    }
}

在配置Spring配置文件时,填写相应的参数并指定name即可,

<bean id="customer" class="com.meinil.demo.Customer">
    <constructor-arg name="name">
        <value>石昊</value>
    </constructor-arg>
</bean>

也可以通过指定参数类型来指定构造函数

<bean id="customer" class="com.meinil.demo.Customer">
    <constructor-arg type="java.lang.Integer">
        <value>3</value>
    </constructor-arg>
</bean>

# 5. 依赖注入

# 5.1 反转控制

反转控制IOC Inverse of Control:即对变量进行赋值的权利,不再由代码控制,而是交给Spring进行管理(即转移对象赋值的控制权)。

优势:解耦合

底层实现:工厂设计模式

# 5.2 依赖注入

依赖注入Dependency Injection:当一个类需要另一个类时,就意味着依赖,一旦出现依赖,就可以把另一个类作为本类的成员变量,最终通过Spring配置文件进行注入(赋值)

优势:解耦合

# 6. 复杂对象

简单对象:可以直接通过new创建出来的对象

复杂对象:不能直接通过new的方式创建的对象

# 6.1 FactoryBean

# 6.1.2 开发步骤

  1. 实现FactoryBean接口

    public class MyFactoryBean implements FactoryBean<Connection> {
        // 用于书写创建复杂对象的代码并把复杂对象作为方法的返回值返回
        @Override
        public Connection getObject() throws Exception {
            Class.forName("com.mysql.cj.jdbc.Driver");
            Connection conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/test", "root", "123456");
            return conn;
        }
    
        // 返回所创建复杂对象的Class独享
        @Override
        public Class<?> getObjectType() {
            return Connection.class;
        }
    
        // 对象是否单例 
        @Override
        public boolean isSingleton() {
            return false;
        }
    }
    
  2. 编写Spring的配置文件

    <bean id="conn" class="com.meinil.demo.MyFactoryBean" />
    

    如果class指定的类时FactoryBean的实现类,则通过id获取的对象不是此类,而是此类创建的类

  3. 测试

    public class Main {
        public static void main(String[] args) throws SQLException {
            ApplicationContext ctx = new ClassPathXmlApplicationContext("/applicationContext.xml");
            Connection conn = ctx.getBean("conn", Connection.class);
            System.out.println(conn);
            conn.close();
        }
    }
    

# 6.1.3 注意事项

  1. 如果想要获取FactoryBean对象,则需要在id前添加&即可

    public class Main {
        public static void main(String[] args) throws SQLException {
            ApplicationContext ctx = new ClassPathXmlApplicationContext("/applicationContext.xml");
            MyFactoryBean conn = ctx.getBean("&conn", MyFactoryBean.class);
            System.out.println(conn);
        }
    }
    
  2. isSingleton用于控制对象是否单例

    连接对象一般不建议单例,MybatisSqlSessionFactory可以单例

  3. 改进

    MyFactoryBean.java

    public class MyFactoryBean implements FactoryBean<Connection> {
        private String driveClassName;
        private String url;
        private String user;
        private String password;
    	
    	// get set
    	
        // 用于书写创建复杂对象的代码并把复杂对象作为方法的返回值返回
        @Override
        public Connection getObject() throws Exception {
            Class.forName(driveClassName);
            Connection conn = DriverManager.getConnection(url, user, password);
            return conn;
        }
    
        // 返回所创建复杂对象的Class独享
        @Override
        public Class<?> getObjectType() {
            return Connection.class;
        }
    
        // 对象是否单例
        @Override
        public boolean isSingleton() {
            return false;
        }
    }
    

    配置文件

    <bean id="conn" class="com.meinil.demo.MyFactoryBean">
        <property name="driveClassName" value="com.mysql.cj.jdbc.Driver" />
        <property name="url" value="jdbc:mysql://192.168.0.103/test" />
        <property name="user" value="root" />
        <property name="password" value="123456" />
    </bean>
    

# 6.2 实例工厂

应用场景:避免对Spring框架的依赖、整合遗留系统

假设现在有一个遗留的系统,它已经提供了ConnectionFactory,我们需要将这个类集成到Spring

public class ConnectionFactory {
    public Connection getConnection() {
        Connection conn = null;
        try {
            Class.forName("com.mysql.cj.jdbc.Driver");
            conn = DriverManager.getConnection("jdbc:mysql://192.168.0.103/test", "root", "123456");
        } catch (SQLException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
        return conn;
    }
}

只需要调整配置文件即可,factory-bean指定工厂对象,factory-method指定工厂方法

<bean id="connFactory" class="com.meinil.demo.ConnectionFactory" />
<bean id="conn" factory-bean="connFactory" factory-method="getConnection" />

测试

public class Main {
    public static void main(String[] args) throws SQLException {
        ApplicationContext ctx = new ClassPathXmlApplicationContext("/applicationContext.xml");
        Connection conn = ctx.getBean("conn", Connection.class);
        System.out.println(conn);
        conn.close();
    }
}

# 6.3 静态工厂

静态工厂与实例工厂的区别在于,获取工厂对象的方法是静态的

静态工厂类

public class StaticConnectionFactory {
    public static Connection getConnection() {
        Connection conn = null;
        try {
            Class.forName("com.mysql.cj.jdbc.Driver");
            conn = DriverManager.getConnection("jdbc:mysql://192.168.0.103/test", "root", "123456");
        } catch (SQLException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
        return conn;
    }
}

修改配置文件

<bean id="conn" class="com.meinil.demo.StaticConnectionFactory" factory-method="getConnection" />

# 7. 对象的创建次数

# 7.1 简单对象

假设有如下类

public class Account {
}

配置文件内容如下

<bean id="account" class="com.meinil.demo.Account" />

可以在bean标签内部指定属性scope来管理对象创建的次数

<bean id="account" scope="prototype" class="com.meinil.demo.Account" />

scope的取值有两个:singleton单例模式(默认)、prototype原型模式,创建次数无要求

# 7.2 复杂对象

在实现FactoryBean的类中,将isSingleton的返回值设置为true即为单例模式,反之创建次数无要求

# 8. 生命周期

生命周期:一个对象的创建、存活、消亡的过程

Spring管理的bean实例的三个重要阶段:

# 8.1 创建阶段

创建阶段:Spring工厂何时创建对象

singleton模式下,Spring工厂创建的同时,对象也会创建,也可以在bean标签中加入属性lazy-init="true"使其在获取时初始化

prototype模式下,Spring工厂会在获取对象的同时,创建对象

# 8.2 初始化阶段

初始化阶段:一般用于初始化数据库、IO、网络等资源的初始化

Spring工厂在创建万对象后,调用对象的初始化方法,完成对应的初始化操作

初始化方法由编码者提供,初始化方法由Spring工厂调用

  1. 实现InitializingBean接口

    public class Account implements InitializingBean {
        public Account() {
            System.out.println("创建");
        }
    
        @Override
        public void afterPropertiesSet() throws Exception {
            System.out.println("初始化操作");
        }
    }
    
  2. 提供普通方法,不需要实现InitializingBean接口

    public class Account {
        public Account() {
            System.out.println("创建");
        }
        
        // 方法名任意
        public void init() {
            System.out.println("自定义初始化方法");
        }
    }
    

    修改配置文件

    <bean id="account" class="com.meinil.demo.Account" init-method="init"/>
    

如果一个bean同时实现了上述的两种方法,则会先执行接口的初始化方法,再执行用户提供的方法

如果配置文件中配置了set注入,则初始化方法在set注入之后执行

# 8.3 销毁阶段

销毁阶段:Spring销毁对象前,会调用对象的销毁方法,完成销毁工作

Springbean实例会在Spring工厂关闭时销毁(调用close方法)

销毁方法由用户提供,由Spring调用

  1. 实现DisposableBean接口

    public class Account implements DisposableBean {
        @Override
        public void destroy() throws Exception {
            System.out.println("销毁方法");
        }
    }
    

    测试

    public class Main {
        public static void main(String[] args) {
            ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("/applicationContext.xml");
            Account account = ctx.getBean("account", Account.class);
            System.out.println(account);
            ctx.close();		// 必须调用close方法
        }
    }
    
  2. 自定义方法

    public class Account {
        public void myDestroy() throws Exception {
            System.out.println("自定义销毁方法");
        }
    }
    

    配置文件中需要指定销毁方法

    <bean id="account" class="com.meinil.demo.Account" destroy-method="myDestroy"/>
    

⭐️注意:销毁方法的操作只适用于scope="singleton"bean

销毁操作:一般用于资源的释放

# 9. 配置文件

Spring配置文件中需要经常修改的字符串信息,转移到一个更小的配置文件中

项目目录

MyFactoryBean.java

public class MyFactoryBean implements FactoryBean<Connection> {
    private String driveClassName;
    private String url;
    private String username;
    private String password;
	// get set     
    
    @Override
    public Connection getObject() throws Exception {
        Class.forName(driveClassName);
        Connection conn = DriverManager.getConnection(url, username, password);
        return conn;
    }

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

    @Override
    public boolean isSingleton() {
        return false;
    }
}

applicationContext.xml

<beans xmlns="http://www.springframework.org/schema/beans"
	   xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	   xmlns:p="http://www.springframework.org/schema/p"
	   xmlns:context="http://www.springframework.org/schema/context"
	   xsi:schemaLocation="http://www.springframework.org/schema/beans
	   					   http://www.springframework.org/schema/beans/spring-beans.xsd
						   http://www.springframework.org/schema/context
 						   http://www.springframework.org/schema/context/spring-context.xsd">
    <!-- 导入配置文件 -->
	<context:property-placeholder location="classpath:/db.properties" />
	<bean id="conn" class="com.meinil.demo.MyFactoryBean">
		<property name="driveClassName" value="${jdbc.driverClassName}" />
		<property name="url" value="${jdbc.url}" />
		<property name="username" value="${jdbc.username}" />
		<property name="password" value="${jdbc.password}" />
 	</bean>
</beans>

db.properties

jdbc.driverClassName=com.mysql.cj.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/test?useSSL=false
jdbc.username=root
jdbc.password=123456

Main测试

public class Main {
    public static void main(String[] args) throws SQLException {
        ApplicationContext ctx = new ClassPathXmlApplicationContext("/applicationContext.xml");
        Connection conn = ctx.getBean("conn", Connection.class);
        System.out.println(conn);
        conn.close();
    }
}

# 10. 类型转换器

类型转换器:Spring通过类型转换器把配置文件中字符串类型的数据,转换成了对象中成员变量对应类型的数据,进而完成了注入

对于某些特定类型(例如:Date),Spring并未提供类型转换器,需要用户自定义类型转换器

现在有如下类

public class Teacher {
    private String name;
    private Date birthday;
    
    // get set
}
  1. 创建自定义的类型转换器

    public class DateConverter implements Converter<String, Date> {
        @Override
        public Date convert(String source) {
            SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
            try {
                return sdf.parse(source);
            } catch (ParseException e) {
                e.printStackTrace();
            }
            return null;
        }
    }
    
  2. 注册类型转换器

    <!--创建自定义的转换器对象-->
    <bean id="dateConverter" class="com.meinil.demo.DateConverter" />
    <!--创建用于注册转换器的对象-->
    <bean id="conversionService" class="org.springframework.context.support.ConversionServiceFactoryBean">
        <property name="converters">
            <set>
                <!--注册转换器-->
                <ref bean="dateConverter" />
            </set>
        </property>
    </bean>
    
  3. 使用

    <bean id="teacher" class="com.meinil.demo.Teacher">
        <property name="name" value="小明" />
        <property name="birthday" value="2022-01-19"  />
    </bean>
    

⭐️对于ConversionServiceFactoryBean的实例对象,id只能是conversionService

Spring内置的日期类型转换器格式为

2022/01/19

# 11. 后置处理Bean

# 11.1 简介

BeanPostProcessor:对Spring工厂所创建的对象,进行再加工

BeanPostProcessor:是一个接口,它定义了两个方法

public interface BeanPostProcessor {
    // 在初始化方法之前调用
	@Nullable
	default Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
		return bean;
	}

    // 在初始化方法之后调用
	@Nullable
	default Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
		return bean;
	}

}

# 11.2 实例展示

  1. 待加工的类

    public class Category {
        private Integer id;
        private String name;
        
        // get set
    }
    
  2. 类实现BeanPostProcessor

    public class CategoryBeanPostProcessor implements BeanPostProcessor {
        @Override
        public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
            return bean;
        }
    
        @Override
        public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
            if (bean instanceof Category) {
                Category category = (Category) bean;
                category.setName("修改后的名字");
            }
            
            return bean;
        }
    }
    
  3. Spring配置文件中进行配置

    <bean id="categoryBeanPostProcessor" class="com.meinil.demo.CategoryBeanPostProcessor" />
    <bean id="category" class="com.meinil.demo.Category">
        <property name="id" value="100" />
        <property name="name" value="李华" />
    </bean>
    

⭐️BeanPostProcessor会对Spring中所有的对象进行加工,需要进行判断后再进行加工

# 12. AOP

# 12.1 代理设计模式

通过代理类,为原始类增加额外的功能。优势:利于原始类的维护

# 12.1.1 静态代理

代理类和原始类需要实现相同的接口

User.java

public class User {
}

UserService.java

public interface UserService {
    public void register(User user);
    public boolean login(String name, String password);
}

UserServiceImpl.java

public class UserServiceImpl implements UserService{
    @Override
    public void register(User user) {
        System.out.println("UserServiceImpl: register方法执行");
    }

    @Override
    public boolean login(String name, String password) {
        System.out.println("UserServiceImpl: login方法执行");
        return true;
    }
}

UserServiceIProxy.java

public class UserServiceProxy implements UserService{
    private UserService userService = new UserServiceImpl();        // 注入原始对象
    @Override
    public void register(User user) {
        System.out.println("UserServiceProxy: register方法执行");
        userService.register(user);
    }

    @Override
    public boolean login(String name, String password) {
        System.out.println("UserServiceProxy: login方法执行");
        return userService.login(name, password);
    }
}

Main.java用于测试

public class Main {
    public static void main(String[] args) {
        UserService userService = new UserServiceProxy();
        userService.register(new User());
        userService.login("小明", "1234");
    }
}

# 12.1.2 Spring动态代理

依赖

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-aop</artifactId>
    <version>5.3.14</version>
</dependency>
<dependency>
    <groupId>org.aspectj</groupId>
    <artifactId>aspectjrt</artifactId>
    <version>1.9.7</version>
</dependency>
<dependency>
    <groupId>org.aspectj</groupId>
    <artifactId>aspectjweaver</artifactId>
    <version>1.9.7</version>
</dependency>

项目结构

User.java

public class User {
}

UserService.java

public interface UserService {
    public void register(User user);
    public boolean login(String name, String password);
}

UserServiceImpl.java

public class UserServiceImpl implements UserService{
    @Override
    public void register(User user) {
        System.out.println("UserServiceImpl: register方法执行");
    }

    @Override
    public boolean login(String name, String password) {
        System.out.println("UserServiceImpl: login方法执行");
        return true;
    }
}

定义一个类并实现MethodBeforeAdivice接口

public class Before implements MethodBeforeAdvice {
    // 在执行原始方法之前执行
    @Override
    public void before(Method method, Object[] args, Object target) throws Throwable {
        System.out.println("--Method Before Advice");
    }
}

配置文件

<bean id="before" class="com.meinil.proxy.service.Before" />
<bean id="userService" class="com.meinil.proxy.service.impl.UserServiceImpl" />

配置文件切入点(额外功能加入的位置(方法)):以下定义含义为所有方法都为切入点

<aop:config>
    <aop:pointcut id="pc" expression="execution(* *(..))"/>
    <aop:advisor advice-ref="before" pointcut-ref="pc" />
</aop:config>

测试

public class Main {
    public static void main(String[] args) {
        ApplicationContext ctx = new ClassPathXmlApplicationContext("/applicationContext.xml");
        UserService userService = ctx.getBean("userService", UserService.class);
        userService.register(new User());
        userService.login("小明", "1234");
    }
}

# 12.2 动态代理详解

# 12.2.1 额外功能

  1. MethodBeforeAdvice接口:这个接口的额外功能是在原始方法执行之前,进行额外功能操作

    public class Before implements MethodBeforeAdvice {
        @Override
        public void before(Method method, Object[] args, Object target) throws Throwable {
            System.out.println("--Method Before Advice");
        }
    }
    

    Method:指额外功能附加给的那个原始方法

    Object[]:原始方法的参数

    Object:原始方法

  2. MethodInterceptor接口:方法拦截器,可以在原始方法执行之前和之后都执行一些操作⭐️

    新建Around.java

    public class Around implements MethodInterceptor {
        // MethodInvocation: 封装了原始方法
        // invoke的返回值: 表示原始方法的返回值
        @Override
        public Object invoke(MethodInvocation invocation) throws Throwable {
            System.out.println("原始方法执行之前");
            Object ret = invocation.proceed();// 执行原始方法
            System.out.println("原始方法执行之后");
            return ret;
        }
    }
    

    修改配置文件

    <bean id="around" class="com.meinil.proxy.service.Around" />
    <aop:config>
        <aop:pointcut id="pc" expression="execution(* *(..))"/>
        <aop:advisor advice-ref="around" pointcut-ref="pc" />
    </aop:config>
    

    应用场景:事务开启提交,性能统计...

    ⭐️Object ret = invocation.proceed()同样可以添加try..catch,用于异常处理

# 12.2.2 切入点

切入点:决定额外功能加入的位置(方法)

<aop:pointcut id="pc" expression="execution(* *(..))"/>

execution:切入点函数。(* *(..)):切入点表达式

第一个*:修饰符(方法的权限)

第二个*:方法名

(..):参数列表

  1. 方法切入点表达式

    • 定义login方法为切入点

      (* login(..))
      
    • 定义login(String, String)为切入点

      (* login(String, String))
      

      如果参数是自定义的类(非java.lang下的类),需要类的全类名

    • 支持可变参数

      (* login(String, ..))
      
    • 精准限制方法切入点

      (* com.meinil.proxy.service.impl.UserServiceImpl.login(String, ..))
      
  2. 类切入点

    • 考虑包的情况

      (* com.meinil.proxy.service.impl.UserServiceImpl.*(String, ..))
      
    • 不考虑包

      <aop:pointcut id="pc" expression="execution(* *.*.*.*.*.UserServiceImpl.*(..))"/>
      

      这种情况一个*仅能代表一层包

  3. 包切入点

    • 仅代表当前包

      <aop:pointcut id="pc" expression="execution(* com.meinil.proxy.service.*.*(..))"/>
      
    • 当前包及其子包

      <aop:pointcut id="pc" expression="execution(* com.meinil.proxy.service..*.*(..))"/>
      

# 12.2.3 切入点函数

切入点函数:用于执行切入点表达式

  1. execution

    execution:最重要的切入点函数,功能最完整。支持方法、类、包切入点表达式

  2. args:主要用于函数(方法)参数的匹配

    <aop:pointcut id="pc" expression="execution(* *(String, String))"/>
    等价
    <aop:pointcut id="pc" expression="args(String, String)"/>
    
  3. within:用于进行类、包切入点表达式的匹配

    execution(* *..UserServiceImpl.*(..))
    
    within(*..UserServiceImpl)
    
  4. @annotation:为具有特殊注解的方法或者类提供额外功能

    首先定义注解

    @Target(ElementType.METHOD)             // 用在哪儿
    @Retention(RetentionPolicy.RUNTIME)     // 什么时候起作用
    public @interface Log {
    
    }
    

    在想要执行的方法上添加此注解

    public class UserServiceImpl implements UserService {
        @Log
        @Override
        public void register(User user) {
            System.out.println("UserServiceImpl: register方法执行");
        }
    
        @Log
        @Override
        public boolean login(String name, String password) {
            System.out.println("UserServiceImpl: login方法执行");
            return true;
        }
    }
    

    配置文件

    <aop:pointcut id="pc" expression="@annotation(com.meinil.proxy.service.Log)"/>
    
  5. 切入点函数逻辑运算:多个切入点函数一起配合工作,进而完成更为复杂的需求

    and与操作:与操作不能使用同类型的切入点函数比如(execution and execution)

    <aop:pointcut id="pc" expression="execution(* login(..)) and args(String, String)"/>
    

    or或操作

    <aop:pointcut id="pc" expression="execution(* login(..)) or execution(* register(..))"/>
    

# 12.3 AOP编程

AOP(Aspect Oriented Programing):面向切面编程

# 12.3.1 JDK动态代理

Main.java

public class Main {
    public static void main(String[] args) {
        // 1. 创建原始对象
        final UserService userService = new UserServiceImpl();
        // 2. 创建JDK动态代理
        UserService userServiceProxy = (UserService)Proxy.newProxyInstance(userService.getClass().getClassLoader(), userService.getClass().getInterfaces(), new InvocationHandler() {
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                System.out.println("原始方法执行之前");
                Object ret = method.invoke(userService, args);
                System.out.println("原始方法执行之后");
                return ret;
            }
        });

        userServiceProxy.register(new User());
        userServiceProxy.login("小明", "123456");
    }
}

Proxy.newProxyInstance的几个参数

  1. classLoaderProxy.newProxyInstance采用动态字节码技术生成代理对象,JVM不会主动去加载它,需要借助别的类加载器进行加载
  2. interfaces:被代理对象的接口,因为代理对象需要与被代理对象实现相同的接口
  3. invocationHandler:需要代理对象实现的功能

# 12.3.2 CGlib动态代理

JDK动态代理要求,原始类实现一定的接口,并且JDK动态代理返回的代理类也会实现这些接口

CGlib对于原始类实现的接口没有要求,它要返回的代理类是继承自原始类

public class Main {
    public static void main(final String[] args) {
        final UserServiceImpl userService = new UserServiceImpl();

        Enhancer enhancer = new Enhancer();
        enhancer.setClassLoader(userService.getClass().getClassLoader());       // 设置classLoader,不设置有默认值
        enhancer.setSuperclass(userService.getClass());                         // 设置父类
        enhancer.setCallback(new MethodInterceptor() {                          // 设置增强功能
            @Override
            public Object intercept(Object o, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
                System.out.println("原始方法执行之前");
                Object ret = method.invoke(userService, args);
                System.out.println("原始方法执行之后");
                return ret;
            }
        });
        UserServiceImpl userServiceProxy = (UserServiceImpl)enhancer.create();
        userServiceProxy.register(new User());
        userServiceProxy.login("小明", "123");
    }
}

# 12.3.3 使用Spring

代理对象的创建一般在BeanPostProcessorafter方法中进行

项目结构

ProxyBeanPostProxy.java:其他类与上述相同

public class ProxyBeanPostProxy implements BeanPostProcessor {
    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        return bean;
    }

    @Override
    public Object postProcessAfterInitialization(final Object bean, String beanName) throws BeansException {
        if (bean instanceof UserService) {
            return Proxy.newProxyInstance(this.getClass().getClassLoader(), bean.getClass().getInterfaces(), new InvocationHandler() {
                @Override
                public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                    System.out.println("原始方法执行之前");
                    Object ret = method.invoke(bean, args);
                    System.out.println("原始方法执行之后");
                    return ret;
                }
            });
        }
        return bean;
    }
}

配置文件

<bean id="proxyBeanPostProcessor" class="com.meinil.proxy.service.factory.ProxyBeanPostProxy" />
<bean id="userService" class="com.meinil.service.impl.UserServiceImpl" />

测试

public class Main {
    public static void main(String[] args) {
        ApplicationContext ctx = new ClassPathXmlApplicationContext("/applicationContext.xml");
        UserService userService = ctx.getBean("userService", UserService.class);
        userService.register(new User());
        boolean isLogin = userService.login("小明", "123456");
        System.out.println(isLogin);
    }
}

# 12.4 注解开发

# 12.4.1 实例

MyAspect.java

@Aspect
public class MyAspect {

    @Around("execution(* *(..))")
    public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
        // joinPoint 原始方法
        System.out.println("注解开发之前");
        Object ret = joinPoint.proceed();
        System.out.println("注解开发之后");
        return ret;
    }
}

切面的方法名任意,ProceedingJoinPoint为原始方法

配置文件

<!--开启AOP注解开发-->
<aop:aspectj-autoproxy />
<bean id="around" class="com.meinil.aspect.aspect.MyAspect" />
<bean id="userService" class="com.meinil.aspect.service.impl.UserServiceImpl" />

# 12.4.2 注意细节

  1. @Pointcut:实现切入点表达式的复用

    @Aspect
    public class MyAspect {
        @Pointcut("execution(* *(..))")
        public void myPointcut() {}
    
        @Around(value = "myPointcut()")
        public Object around1(ProceedingJoinPoint joinPoint) throws Throwable {
            // joinPoint 原始方法
            System.out.println("注解开发之前1");
            Object ret = joinPoint.proceed();
            System.out.println("注解开发之后1");
            return ret;
        }
    
        @Around(value = "myPointcut()")
        public Object around2(ProceedingJoinPoint joinPoint) throws Throwable {
            // joinPoint 原始方法
            System.out.println("注解开发之前2");
            Object ret = joinPoint.proceed();
            System.out.println("注解开发之后2");
            return ret;
        }
    }
    
  2. 动态代理的创建方式

    默认AOP编程底层应用使用JDK动态代理,可以通过配置文件切换cglib

    注解形式下

    <aop:aspectj-autoproxy proxy-target-class="true" />
    

    传统配置文件下

    <bean id="around" class="com.meinil.proxy.service.Around" />
    <aop:config proxy-target-class="true">
        <aop:pointcut id="pc" expression="execution(* login(..)) or execution(* register(..))"/>
        <aop:advisor advice-ref="around" pointcut-ref="pc" />
    </aop:config>
    

# 12.5 注意事项

如果原始类内部方法中调用自己的方法,由于被调用的方法不是由代理对象调用的,则不会执行增强方法。如:下列的register方法

public class UserServiceImpl implements UserService {

    @Override
    public boolean login(String username, String password) {
        System.out.println("login");
        return false;
    }

    @Override
    public void register(User user) {
        System.out.println("register");
        // 此处为调用原始对象的login方法,并非Spring的代理方法
        login("李华", "3456");
    }
}

可以通过实现ApplicationContextAware接口获取Spring工厂,进而调用方法

public class UserServiceImpl implements UserService, ApplicationContextAware {
    private ApplicationContext ctx;

    @Override
    public boolean login(String username, String password) {
        System.out.println("login");
        return false;
    }

    @Override
    public void register(User user) {
        System.out.println("register");
        UserService userService = ctx.getBean("userService", UserService.class);
        userService.login("李华", "3456");
    }

    // 获取Spring工厂ApplicationContext
    @Override
    public void setApplicationContext(ApplicationContext ctx) throws BeansException {
        this.ctx = ctx;
    }
}

# 13. 整合持久层

Spring整合的持久层

# 13.1 整合MyBatis

项目目录

导入依赖

<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>8.0.27</version>
</dependency>
<dependency>
    <groupId>org.mybatis</groupId>
    <artifactId>mybatis</artifactId>
    <version>3.5.6</version>
</dependency>

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-jdbc</artifactId>
    <version>5.3.15</version>
</dependency>
<dependency>
    <groupId>org.mybatis</groupId>
    <artifactId>mybatis-spring</artifactId>
    <version>2.0.6</version>
</dependency>
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>druid</artifactId>
    <version>1.2.8</version>
</dependency>

数据库搭建

create database test;
use test;
create table t_users(
	id int primary key auto_increment,
	username varchar(12),
	password varchar(12)
);

实体类

public class User implements Serializable {
    private Integer id;
    private String username;
    private String password;
    
    // get set
}

DAO层接口

public interface UserDAO {
    public void save(User user);
}

UserMapper.xml文件

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
		PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
		"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.meinil.mybatis.dao.UserDAO">
	<insert id="save" parameterType="user">
		INSERT INTO t_users(username, password) VALUES (#{username}, #{password})
	</insert>
</mapper>

准备applicationContext.xml配置文件

<!--Druid连接池-->
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
    <property name="driverClassName" value="com.mysql.cj.jdbc.Driver" />
    <property name="url" value="jdbc:mysql://localhost:3306/test?serverTimezone=GMT&amp;allowPublicKeyRetrieval=true&amp;useSSL=false&amp;characterEncoding=utf8" />
    <property name="username" value="root" />
    <property name="password" value="123456" />
</bean>
<!--创建SqlSessionFactoryBean-->
<bean id="sqlSessionFactoryBean" class="org.mybatis.spring.SqlSessionFactoryBean">
    <!--指定数据源-->
    <property name="dataSource" ref="dataSource" />
    <!--指定实体包-->
    <property name="typeAliasesPackage" value="com.meinil.mybatis.model" />
    <!--指定mapper文件的位置 -->
    <property name="mapperLocations">
        <list>
            <value>classpath:com/meinil/mybatis/mapper/*Mapper.xml</value>
        </list>
    </property>
</bean>
<!--创建DAO对象-->
<bean id="scanner" class="org.mybatis.spring.mapper.MapperScannerConfigurer">
    <property name="sqlSessionFactoryBeanName" value="sqlSessionFactoryBean" />
    <property name="basePackage" value="com.meinil.mybatis.dao" />
</bean>

测试

public class Main {
    public static void main(String[] args) throws IOException {
        ApplicationContext ctx = new ClassPathXmlApplicationContext("/applicationContext.xml");
        UserDAO userDAO = ctx.getBean("userDAO", UserDAO.class);
        User user = new User();
        user.setUsername("李华");
        user.setPassword("9999");
        userDAO.save(user);
    }
}

⭐️Druid会自动控制事务,执行一条sql时会自动提交

# 13.2 Spring事务

事务的特点:ACID:即原子性、一致性、隔离性、持久性

Spring通过AOP方式进行事务开发控制

Spring默认已经提供了一个DataSourceTransactionManager用于管理事务,可以使用@Transactional注解用于控制事务

依赖

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-tx</artifactId>
    <version>5.3.14</version>
</dependency>

项目结构

创建UserService接口

public interface UserService {
    public void register(User user);
}

提供实现类要加@Transactional注解

@Transactional
public class UserServiceImpl implements UserService{
    private UserDAO userDAO;

    public UserDAO getUserDAO() {
        return userDAO;
    }

    public void setUserDAO(UserDAO userDAO) {
        this.userDAO = userDAO;
    }

    @Override
    public void register(User user) {
        userDAO.save(user);
    }
}

配置文件

<bean id="userService" class="com.meinil.mybatis.service.UserServiceImpl">
    <property name="userDAO" ref="userDAO" />
</bean>
<!--事务管理对象-->
<bean id="dataSourceTransactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
    <property name="dataSource" ref="dataSource" />
</bean>
<tx:annotation-driven transaction-manager="dataSourceTransactionManager" />

测试

public class Main {
    public static void main(String[] args) throws IOException {
        ApplicationContext ctx = new ClassPathXmlApplicationContext("/applicationContext.xml");
        UserService userService = ctx.getBean("userService", UserService.class);

        User user = new User();
        user.setUsername("Allan");
        user.setPassword("4567");
        userService.register(user);
    }
}

# 13.3 事务属性

事务属性:描述事务特征的一系列值

  1. 隔离属性
  2. 传播属性
  3. 只读属性
  4. 超时属性
  5. 异常属性

Spring@Transactional:中可以添加对应的属性

@Transactional(isloation = , propagation = , readOnly = ,timeout = , rollbackFor = , noRollbackFor = )

# 13.3.1 隔离属性

隔离属性:描述了事务解决并发问题的特征

事务并发产生的问题:

  1. 脏读:一个事务,读取了另一个事务中没有提交的数据。也会在本事务中产生数据不一致的问题

    解决方案

    @Transactional(isolation = Isolation.READ_COMMITTED)
    
  2. 不可重复读:一次事务中,多次读取相同的数据,但读取结果不一致,会在本事务中产生数据不一致的问题

    解决方案:加行锁

    @Transactional(isolation = Isolation.REPEATABLE_READ)
    
  3. 幻影读:一次事务中,多次对整表进行查询统计,但是结果不一样,会在本次事务中产生数据不一致的问题

    解决方案:加表锁

    @Transactional(isolation = Isolation.SERIALIZABLE)
    

并发安全:SERIALIZABLE>REPEATABLE_READ>READ_COMMITTED

运行效率:SERIALIZABLE<REPEATABLE_READ<READ_COMMITTED

隔离属性 MySQL Oracle
ISOLATION_READ_COMMITTED
ISOLATION_REPEATABLE_READ
ISOLATION_SERIALIZABLE

对于不可重复读,Oracle采用多版本比对的方式解决

如果不设置隔离属性,则默认为ISOLATION_DEFAULT,会调用不同数据库锁设置的默认隔离属性(一般默认即可)

MySQL: REPEATABLE_READ
Oracle: READ_COMMITTED

# 13.3.2 传播属性

传播属性:它描述了事务解决嵌套问题的特征

大事务中融入了很多小的事务,它们彼此影响,最终就会导致外部的大事务丧失原子性

传播属性的值 外部不存在事务 外部存在事务 用法 应用场景
REQUIRED 开启新的事务 融合到外部事务中 @Transactional(propagation = Propagation.REQUIRED) 增删改
SUPPORTS 不开始新的事务 融合到外部事务中 @Transactional(propagation = Propagation.SUPPORTS) 查询
REQUIRES_NEW 开启新的事务 挂起外部事务,创建新的事务 @Transactional(propagation = Propagation.REQUIRES_NEW) 日志记录
NOT_SUPPORTED 不开启新的事务 挂起外部事务 @Transactional(propagation = Propagation.NOT_SUPPORTED)
NEVER 不开启新的事务 抛出异常 @Transactional(propagation = Propagation.NEVER)
MANDATORY 抛出异常 融合到外部事务中 @Transactional(propagation = Propagation.MANDATORY)

默认传播属性:REQUIRED

# 13.3.3 只读属性

针对于只进行查询操作的业务方法,可以加入只读属性,提高运行效率

@Transactional(propagation = Propagation.SUPPORTS, readOnly = true)

# 13.3.4 超时属性

当前事务访问数据时,有可能访问的数据被别的事务进行加锁的处理,那么此时本事务就必须进行等待

@Transactional(timeout = 2)	// 单位 秒

默认由对应的数据库确定

# 13.3.5 异常属性

Spring事务处理中,

对于RuntimeException及其子类采用的是回滚的策略

对于Exception及其子类,采用的是提交策略

配置回滚以及不会滚的异常

@Transactional(rollbackFor = {java.lang.Exception.class}, noRollbackFor = {java.lang.RuntimeException.class})

# 14. 注解开发

项目结构

想要Spring支持注解需要在配置文件中添加如下配置

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

Spring会自动扫描base-package所指定的包及其子包

# 14.1 基础注解

# 14.1.1 对象创建

  1. @Component

    @Component
    public class User {
    }
    

    默认是类名为id值(首单词首字母小写),如需指定id值,则直接写在@Component中即可

    如果想要通过配置文件覆盖注解,则需要id值指定为注解设置的id

  2. @Component的衍生注解

    @Repository:一般用于dao层对象,⭐️如果是Spring整合Mybatis对象由Spring直接生成,无需配置

    @Controller:一般用于controller层对象

    @Service:一般用于service层对象

    它们的用法与Component注解相同,只不过是为了更好的区分不同bean的功能

  3. @Scope:控制简单对象的创建次数,默认为单例

    @Component
    @Scope("singleton")		// 对象为单例模式
    @Scope("prototype")		// 对象为多例模式
    public class User {
    }
    
  4. @Lazy:懒惰初始化,即在使用这个对象时再进行创建

    @Component
    @Lazy
    public class User {
    }
    
  5. 生命周期注解

    @Component
    public class User {
        @PostConstruct			// 指定初始化方法
        public void init() {}
        
        @PreDestroy				// 指定销毁方法
        public void destroy() {}
    }
    

# 14.1.2 注入

  1. 用户自定义类型注入

    dao层设计

    public interface UserDAO {
    }
    
    @Repository
    public class UserDAOImpl implements UserDAO{
    }
    

    service层设计

    public interface UserService {
    }
    
    @Service
    public class UserServiceImpl {
        private UserDAO userDAO;
    
        @Autowired
        public void setUserDAO(UserDAO userDAO) {
            this.userDAO = userDAO;
        }
    }
    

    @Autowired加入到set方法上或者直接加入到属性上

    默认是通过类型进行注入,也可以使用@Qualifier指定注入对象的id值(不推荐)

    @Service
    public class UserServiceImpl implements UserService{
        private UserDAO userDAO;
    
        @Autowired
        @Qualifier("userDAOImpl")
        public void setUserDAO(UserDAO userDAO) {
            this.userDAO = userDAO;
        }
    }
    

    JavaEE规范中也提供了用于注入的注解

    @Resource:可以使用名字或者类型进行注入

    @Service
    public class UserServiceImpl implements UserService{
        private UserDAO userDAO;
    
        @Resource(name = "userDAOImpl")
        public void setUserDAO(UserDAO userDAO) {
            this.userDAO = userDAO;
        }
    }
    
  2. JDK类型

    准备初始化对象

    @Component
    public class User {
        @Value("${id}")
        private Integer id;
        @Value("${name}")
        private String name;
        
        // get set
    }
    

    准备初始化参数

    id=10
    name=小明
    

    调整配置文件:不推荐

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

    ⭐️也可以直接在对应的类上添加相关注解

    @Component
    @PropertySource("classpath:/init.properties")
    public class User {
        @Value("${id}")
        private Integer id;
        @Value("${name}")
        private String name;
    	
        // get set
    }
    

    注意事项:@Value不能加载静态成员变量之上,不能注入集合类型

# 14.1.3 注解扫描

排除方式

如果需要排除某些包的注解扫描需要在配置文件中添加

	<context:component-scan base-package="com.meinil.annotation">
		<context:exclude-filter type="" expression=""/>
	</context:component-scan>

type的取值有五种

  1. assignable:排除特定的类型(类),expression指定类的全类名
  2. annotation:排除含有特定注解的类,expression指定注解的全类名
  3. aspectj:排除aspectj表达式所表示的类,expression指定切入点表达式(包或者类)
  4. regex:排除正则表达式所匹配的类,expression指定正则表达式
  5. custom:自定义排除策略

排除策略可以叠加使用

包含方式

与排除方式类似,指定要扫描的包及类,在配置文件中添加

<context:component-scan base-package="com.meinil.annotation" use-default-filters="false">
    <context:include-filter type="" expression=""/>
</context:component-scan>

type用法与排除方式相同

# 14.2 高级注解

项目目录

# 14.2.1 配置Bean

@Configuration:指定配置类用于替代xml的配置方式

@Configuration
public class ApplicationConfig {

}

获取Spring配置信息

public class Main {
    public static void main(String[] args) {
        // 通过类名获取Spring配置信息
        ApplicationContext ctx1 = new AnnotationConfigApplicationContext(ApplicationConfig.class);

        // 通过包名获取Spring配置信息
        ApplicationContext ctx2 = new AnnotationConfigApplicationContext("com.meinil.config");
    }
}

# 14.2.2 配置logback

导入依赖

<dependency>
    <groupId>org.slf4j</groupId>
    <artifactId>jcl-over-slf4j</artifactId>
    <version>1.7.33</version>
</dependency>
<dependency>
    <groupId>ch.qos.logback</groupId>
    <artifactId>logback-classic</artifactId>
    <version>1.2.10</version>
    <exclusions>
        <exclusion>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-api</artifactId>
        </exclusion>
    </exclusions>
</dependency>
<dependency>
    <groupId>org.logback-extensions</groupId>
    <artifactId>logback-ext-spring</artifactId>
    <version>0.1.5</version>
</dependency>

resources目录下配置logback.xml文件

<?xml version="1.0" encoding="UTF-8" ?>
<configuration>
	<appender name="consoleAppender" class="ch.qos.logback.core.ConsoleAppender">
		<encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
			<pattern>
				[%-5level] %d{yyyy-MM-dd HH:mm:ss.SSS} %c.%M:%L %thread %m %n
			</pattern>
		</encoder>
	</appender>
	<root level="ALL">
		<appender-ref ref="consoleAppender"/>
	</root>
</configuration>

# 14.2.3 @Bean

等同于配置文件中的bean标签,用于创建对象

  1. 简单对象

    @Configuration
    @Scope("prototype")		// 控制对象的创建次数(多例)	 singleton 单例
    public class ApplicationConfig {
        @Bean
        public User user() {
            return new User();
        }
    }
    

    注意事项:@Bean所添加的方法必须是public,返回值注入到Spring中,方法名为注入到Spring中的id值,@Bean中可以指定id

  2. 创建复杂对象

    @Configuration
    public class ApplicationConfig {
        @Bean
        public Connection conn() {
            Connection conn = null;
            try {
                Class.forName("com.mysql.cj.jdbc.Driver");
    
                conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/test?serverTimezone=GMT", "root", "123456");
            } catch (ClassNotFoundException e) {
                e.printStackTrace();
            } catch (SQLException e) {
                e.printStackTrace();
            }
            return conn;
        }
    }
    

    也可以使用FactoryBean的方式集成到@Bean,不推荐

    public class ConnectionFactoryBean implements FactoryBean<Connection> {
        @Override
        public Connection getObject() throws Exception {
            Class.forName("com.mysql.cj.jdbc.Driver");
    
            Connection conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/test?serverTimezone=GMT", "root", "123456");
            return conn;
        }
    
        @Override
        public Class<?> getObjectType() {
            return Connection.class;
        }
    
        @Override
        public boolean isSingleton() {
            return false;
        }
    }
    
    @Configuration
    public class ApplicationConfig {
        @Bean
        public Connection conn() {
            Connection conn = null;
            ConnectionFactoryBean factoryBean = new ConnectionFactoryBean();
            try {
                conn = factoryBean.getObject();
            } catch (Exception e) {
                e.printStackTrace();
            }
            return conn;
        }
    }
    

# 14.2.4 注入

用户自定义类型注入,作为参数传进去即可

@Configuration
public class ApplicationConfig {
    @Bean
    public UserDAO userDAO() {
        return new UserDAOImpl();
    }
    @Bean
    public UserService userService(UserDAO userDAO) {
        UserServiceImpl userService = new UserServiceImpl();
        userService.setUserDAO(userDAO);
        return userService;
    }
}

JDK类型注入:手动注入即可

@Configuration
public class ApplicationConfig {
    @Bean
    public User user() {
        User user = new User();
        user.setId(11);
        user.setName("22");
        return user;
    }
}

也可以采用配置文件的方式

@Configuration
@PropertySource("classpath:/init.properties")
public class ApplicationConfig {
    @Value("${id}")
    private int id;
    @Value("${name}")
    private String name;

    @Bean
    public User user() {
        User user = new User();
        user.setId(id);
        user.setName(name);
        return user;
    }
}

# 14.2.5 @ComponentScan

指定扫描注解的包

@Configuration
@ComponentScan(basePackages = "com.meinil.config")
public class ApplicationConfig {
}

排除扫描

@Configuration
@ComponentScan(basePackages = "com.meinil.config", 
               excludeFilters = {@ComponentScan.Filter(type = FilterType.ANNOTATION, value = {Service.class})})
public class ApplicationConfig {}

包含扫描

@Configuration
@ComponentScan(basePackages = "com.meinil.config",
               includeFilters = {@ComponentScan.Filter(type = FilterType.ANNOTATION, value = {Service.class})})
public class ApplicationConfig {}

优先级

@Component < @Bean < applicationContext.xml // 高优先级的配置可以覆盖低优先级的配置

# 14.2.6 整合多个配置信息

  1. 多配置Bean的整合

    假设现在有两个配置类

    @Configuration
    public class ApplicationConfig1 {
    }
    
    @Configuration
    public class ApplicationConfig2 {
    }
    

    直接指定配置Bean的包即可

    public class Main {
        public static void main(String[] args) {
            ApplicationContext ctx = new AnnotationConfigApplicationContext("com.meinil.config");
        }
    }
    

    或者将一个当做主配置类,导入另一个配置类即可

    @Configuration
    @Import(ApplicationConfig2.class)
    public class ApplicationConfig1 {
        @Autowired
        private UserDAO userDAO;
        
        @Bean
        public UserService userService() {
           UserServiceImpl userService = new UserServiceImpl();
           userService.setUserDAO(userDAO);
           return userService;
       }
    }
    
    @Configuration
    public class ApplicationConfig2 {
        @Bean
        public UserDAO userDAO() {
            return new UserDAOImpl();
        }
    }
    
  2. 配置Bean@Component相关注解的整合:指定扫描包即可

    @Configuration
    @ComponentScan(basePackages = "com.meinil.config")
    public class ApplicationConfig {
       
    }
    
  3. 配置Bean与配置文件整合:配置类上指定配置文件路径即可

    @Configuration
    @ImportResource("/applicationContext.xml")
    public class ApplicationConfig {
        
    }
    

# 14.2.7 注解AOP

导入AOP的包

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-aop</artifactId>
    <version>5.3.15</version>
</dependency>
<dependency>
    <groupId>org.aspectj</groupId>
    <artifactId>aspectjrt</artifactId>
    <version>1.9.7</version>
</dependency>
<dependency>
    <groupId>org.aspectj</groupId>
    <artifactId>aspectjweaver</artifactId>
    <version>1.9.7</version>
</dependency>

创建切面类

@Aspect
@Component
public class MyAspect {
    @Pointcut("execution(* *(..))")
    public void myPointcut() {}

    @Around(value = "myPointcut()")
    public Object around1(ProceedingJoinPoint joinPoint) throws Throwable {
        System.out.println("切面方法");
        return joinPoint.proceed();
    }
}

改写配置类

@Configuration
@EnableAspectJAutoProxy
@ComponentScan(basePackages = "com.meinil.config")
public class ApplicationConfig {

}

@EnableAspectJAutoProxy中可以指定代理对象的创建方式proxyTargetClass = true启用CGlib

# 14.2.8 注解MyBatis

@Configuration
@MapperScan(basePackages = "com.meinil.config.dao")     // 指明dao层包
@EnableTransactionManagement                            // 启用事务
public class ApplicationConfig1 {
    @Bean
    public DataSource dataSource() {
        DruidDataSource dataSource = new DruidDataSource();
        dataSource.setDriverClassName("com.mysql.cj.jdbc.Driver");
        dataSource.setUrl("jdbc:mysql://localhost:3306/test?serverTimezone=GMT&allowPublicKeyRetrieval=true&useSSL=false&characterEncoding=utf8");
        dataSource.setUsername("root");
        dataSource.setPassword("123456");
        return dataSource;
    }

    @Bean
    public SqlSessionFactoryBean sqlSessionFactoryBean(DataSource dataSource) {
        SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
        sqlSessionFactoryBean.setDataSource(dataSource);                        // 数据源
        sqlSessionFactoryBean.setTypeAliasesPackage("com.meinil.config.model"); // 实体类包
        sqlSessionFactoryBean.setMapperLocations(new ClassPathResource("com.meinil.config.mapper/*Mapper.xml"));
        return sqlSessionFactoryBean;
    }

    @Bean
    public DataSourceTransactionManager dataSourceTransactionManager(DataSource dataSource) {
        return new DataSourceTransactionManager(dataSource);
    }
}

直接使用dao接口即可,如需事务在对应类或方法上添加@Transactional即可

最后修改时间: 5 minutes ago