Spring6笔记

一、Spring引入

1.软件开发原则

1.OCP:软件七大开发原则中最基本的一个原则,开闭原则。对拓展开放,对修改关闭。        OCP开闭原则的核心:拓展系统功能的时候,没有修改以前的代码,就是符合OCP原则的。

2.DIP:依赖倒置原则,上层不依赖下层,就是符合依赖倒置原则的。 目的:降低程序的耦合度,提高拓展力。依赖倒置原则的核心:面向接口编程,面向抽象编程,不要面向具体编程

3.可以采用控制反转IoC(Inversion of Control)编程思想使程序符合OCP和DIP原则。不在程序中采用硬编码的方式来new对象和维护对象之间的关系了。

2.Spring框架

1.Spring框架是一个容器,Spring框架实现了控制反转IoC思想,Spring对象可以new对象和维护对象之间的关系。

2.依赖注入DI(Dependency Injection):是控制反转思想的具体实现,对象A和对象B的关系通过注入的手段来维护,包括set注入(执行set方法给属性赋值)和构造方法注入(执行构造方法给属性赋值)。

二、Spring概述

.简介

Spring是开源框架,是轻量级的IoC(控制反转)和AOP(面向切面)的容器框架,为简化开发而生,让程序员只关注核心业务的实现。

2.Spring八大模块

1.Spring Core模块:Spring框架的核心,负责控制反转(IoC)。

2.Spring AOP模块:面向切面编程。

3.Spring Web MVC模块:SSM中的Spring MVC。

4.Spring Webflux模块:Spring提供的响应式web框架。

5.Spring Web模块:支持集成其他的MVC框架。

6.Spring DAO模块:对JDBC操作的API。

7.Spring ORM模块:支持常见的ORM框架(Mybatis等)

8.Spring Context模块:Spring的上下文,包括国际化消息、事件传播、企业服务等。

3.Spring特点

1.轻量,非侵入式的。

2.控制反转IoC。

3.面向切面AOP。

4.容器。

5.框架。

4.Spring下载

https://spring.p2hp.com

docs:文档

libs:Spring框架的核心

schema:配置文件的约束

5.Spring的jar包

spring-aop:AOP的类

spring-aspects:AOP的支持

spring-beans:核心jar包

spring-context:为spring核心提供了大量的拓展

spring-core:spring框架自己的工具

spring-jdbc:spring自己的增删改查

spring-orm:集成ORM框架的支持

spring-tx:事务管理支持

spring-web:集成MVC框架的jar包

spring-webflux:响应式

spring-webmvc:SpringMVC框架的类库

如果只是使用SpringIoC功能的话,只需采用Maven引入context即可,在引入spring-context之后,会将需要的其他依赖全部引入,很方便。

三、第一个Spring程序

pom.xml

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

    <groupId>com.qiye</groupId>
    <artifactId>spring6-002-first</artifactId>
    <version>1.0-SNAPSHOT</version>
    <packaging>jar</packaging>
    <dependencies>
<!--        引入spring-context之后,就表示将spring的基础依赖引入了 -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>6.2.0</version>
        </dependency>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.13.1</version>
            <scope>test</scope>
        </dependency>

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

</project>

spring.xml

bean标签的两个重要属性:id和class

id:bean的唯一标识。

class:必须填写类的全限定类名。

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<!--spring的配置文件,IDEA为我们提供了这个模板-->
<!--    文件名随意,文件最好放到resources中-->

<!--    配置bean   -->
    <bean id="userBean" class="com.qiye.spring6.bean.User"/>
    
</beans>

测试 

ApplicationContext:应用上下文(Spring容器),是一个接口,它的一个实现类是ClassPathXmlApplicationContext,这个类是专门从类路径中加载Spring配置文件的一个Spring上下文对象。

 @Test
    public void testFirstSpring(){
        //1.获取Spring容器对象,这行代码会启动Spring容器,解析spring.xml文件并且实例化所有的bean对象并放到Spring容器中。
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.xml");
        //2.根据bean的id从Spring容器中获取bean对象
        Object userBean = applicationContext.getBean("userBean");
        System.out.println(userBean);
    }

细节

1.bean的id是唯一标识,不可以重复。

2.Spring框架底层创建对象的方法:使用反射机制调用无参数构造方法来创建对象。(所以bean类要保证有无参数构造方法)

Class clazz = Class.forName("com.qiye.spring6.bean.User");
Object obj = clazz.newInstance();

3.创建好的对象会存放到Map<String,Object>中,key为bean的id,value为bean对象。

4.Spring框架的配置文件的名字随意,可以有多个,在new ClassPathXmlApplicationContext时也可以写多个配置文件路径。

5.Spring配置文件中也可以配置非自定义的类,例如java.util.Date。

6.getBean时,如果id不存在,会报错。

7.getBean时,通过第二个参数可以指定返回的bean类的类型,我们就无需手动转型了。

User userBean = applicationContext.getBean("userBean", User.class);

8.ClassPathXmlApplicationContext要获取的文件如果不在类路径中,我们可以使用FileSystemXmlApplicationContext通过绝对路径来获取文件。

ApplicationContext applicationContext1 = new FileSystemXmlApplicationContext("d:/spring6.xml");

9.ApplicationContext接口的超级父接口为BeanFactory,是IoC容器的顶级接口。(Spring的IoC容器底层实际上使用了:工厂模式+反射机制+XML解析)

启用Log4j2日志框架

Spring底层支持集成的日志框架为Log4j2。

1.引入依赖

 <dependency>
            <groupId>org.apache.logging.log4j</groupId>
            <artifactId>log4j-core</artifactId>
            <version>2.19.0</version>
        </dependency>
        <dependency>
            <groupId>org.apache.logging.log4j</groupId>
            <artifactId>log4j-slf4j2-impl</artifactId>
            <version>2.19.0</version>
        </dependency>

2.在类路径下配置log4j2.xml文件

<?xml version="1.0" encoding="UTF-8"?>

<configuration>

    <loggers>
        <!--
            level指定日志级别,从低到高的优先级:
                ALL < TRACE < DEBUG < INFO < WARN < ERROR < FATAL < OFF
        -->
        <root level="DEBUG">
            <appender-ref ref="spring6log"/>
        </root>
    </loggers>

    <appenders>
        <!--输出日志信息到控制台-->
        <console name="spring6log" target="SYSTEM_OUT">
            <!--控制日志输出的格式-->
            <PatternLayout pattern="%d{yyyy-MM-dd HH:mm:ss SSS} [%t] %-3level %logger{1024} - %msg%n"/>
        </console>
    </appenders>

</configuration>

3.使用
 

 //获取FirstSpringTest类的日志记录器对象
        Logger logger = LoggerFactory.getLogger(FirstSpringTest.class);
        //记录日志,根据不同级别输出日志
        logger.info("消息");
        logger.debug("调试信息");
        logger.error("错误信息");

四、Spring对IoC的实现

1.概述

IoC控制反转思想:将对象的创建和对象之间的关系维护交给第三方容器负责。

依赖注入DI实现了IoC思想。

Spring通过依赖注入的方式来完成Bean管理。

Bean管理:Bean对象的创建以及Bean对象之间关系的维护。

依赖:对象之间的关联关系。

注入:一种数据传递行为,通过注入行为让对象之间产生关系。

依赖注入分为:set注入和构造方法注入。

2.set注入

基于set方法实现的,所以必须提供set方法。

UserDao

public class UserDao {
    private static final Logger logger = LoggerFactory.getLogger(UserDao.class);
    public void insert(){
        logger.info("数据库正在保存用户信息");
    }
}

UserService

public class UserService {
    private UserDao userDao;
    //set注入,必须提供set方法,Spring容器会调用这个set方法给userDao赋值。
    public void setUserDao(UserDao userDao) {
        this.userDao = userDao;
    }

    public void saveUser(){
        //保存用户信息到数据库
        userDao.insert();
    }
}

spring.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<!--    配置dao  -->
    <bean id="userDaoBean" class="com.qiye.spring6.dao.UserDao"/>

<!--    配置service  -->
    <bean id="userServiceBean" class="com.qiye.spring6.service.UserService">
<!--        想让Spring调用对应的set方法,需要配置property标签-->
<!--        name为set方法的方法名去掉get,将剩下的首字母变小写 -->
<!--        ref翻译为引用references 后面指定的是要注入的bean的id-->
        <property name="userDao" ref="userDaoBean"/>
    </bean>
</beans>

测试

 @Test
    public void testSetDI(){
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.xml");
        UserService userServiceBean = applicationContext.getBean("userServiceBean", UserService.class);
        userServiceBean.saveUser();
    }

3.构造注入

通过构造方法给属性赋值。

VipDao

public class VipDao {
    private static final Logger logger = LoggerFactory.getLogger(VipDao.class);
    public void insert(){
        logger.info("数据库正在保存Vip信息");
    }
}

CustomerService

public class CustomerService {
    private UserDao userDao;
    private VipDao vipDao;

    public CustomerService(UserDao userDao, VipDao vipDao) {
        this.userDao = userDao;
        this.vipDao = vipDao;
    }

    public void save(){
        userDao.insert();
        vipDao.insert();
    }
}

beans.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
    <bean id="userDaoBean" class="com.qiye.spring6.dao.UserDao"/>
    <bean id="vipDaoBean" class="com.qiye.spring6.dao.VipDao"/>
    <bean id="customerBean" class="com.qiye.spring6.service.CustomerService">
<!--        构造注入  index属性指定参数下标,第n个参数是n-1  ref属性指定注入的bean的id -->
<!--        指定构造方法的第一个参数,下标是0-->
        <constructor-arg index="0" ref="userDaoBean"/>
<!--        指定构造方法的第二个参数,下标是1-->
        <constructor-arg index="1" ref="vipDaoBean"/>
    </bean>

<!--    也可以通过名字获取参数 -->
    <bean id="customerBean2" class="com.qiye.spring6.service.CustomerService">
        <constructor-arg name="userDao" ref="userDaoBean"/>
        <constructor-arg name="vipDao" ref="vipDaoBean"/>
    </bean>
<!--    甚至可以不指定下标和名字,让Spring框架自动匹配,这种方式实际上是根据类型进行注入的-->
    <bean id="customerBean3" class="com.qiye.spring6.service.CustomerService">
        <constructor-arg ref="vipDaoBean"/>
        <constructor-arg ref="userDaoBean"/>
    </bean>
</beans>

测试

@Test
    public void testConstructorDI(){
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext("beans.xml");
        CustomerService customerBean = applicationContext.getBean("customerBean", CustomerService.class);
        customerBean.save();
        CustomerService customerBean2 = applicationContext.getBean("customerBean2", CustomerService.class);
        customerBean2.save();
        CustomerService customerBean3 = applicationContext.getBean("customerBean3", CustomerService.class);
        customerBean3.save();
    }

4.set注入专题

1.注入外部Bean和内部Bean

外部bean:bean定义到外边,使用ref属性来引入。

内部bean:在property标签中使用嵌套的bean标签。

public class OrderDao {
    //订单Dao
    private static final Logger logger = LoggerFactory.getLogger(OrderDao.class);
    public void insert(){
        logger.info("订单正在生成......");
    }
}
public class OrderService {
    private OrderDao orderDao;

    public void setOrderDao(OrderDao orderDao) {
        this.orderDao = orderDao;
    }

    public void generate(){
        orderDao.insert();
    }
}

set-di.xml 

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<!--    外部bean-->
    <bean id="orderDaoBean" class="com.qiye.spring6.dao.OrderDao"/>
    <bean id="orderServiceBean" class="com.qiye.spring6.service.OrderService">
<!--        注入外部bean-->
        <property name="orderDao" ref="orderDaoBean"/>
    </bean>
<!--    内部bean,在property标签中使用嵌套的bean标签-->
    <bean id="orderServiceBean2" class="com.qiye.spring6.service.OrderService">
        <property name="orderDao">
            <bean class="com.qiye.spring6.dao.OrderDao"/>
        </property>
    </bean>
</beans>

测试

 @Test
    public void testSetDI2(){
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext("set-di.xml");
        OrderService orderServiceBean = applicationContext.getBean("orderServiceBean", OrderService.class);
        orderServiceBean.generate();
        OrderService orderServiceBean2 = applicationContext.getBean("orderServiceBean2", OrderService.class);
        orderServiceBean2.generate();
    }

2.注入简单类型

简单类型:8种基本数据类型,8种包装类,枚举,String,Number(数字),Date,Temporal(时间时区),URI,URL,Locale(语言),Class

在实际开发中,我们一般不会把Date当作简单类型,一般用ref注入。

给简单类型赋值时,要使用value。

User

public class User {
    private String username;
    private String password;
    private int age;

    @Override
    public String toString() {
        return "User{" +
                "username='" + username + '\'' +
                ", password='" + password + '\'' +
                ", age=" + age +
                '}';
    }

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

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

    public void setAge(int age) {
        this.age = age;
    }
}
<!--    注入简单类型 -->
    <bean id="userBean" class="com.qiye.spring6.bean.User">
        <property name="username" value="张三"/>
        <property name="age" value="20"/>
        <property name="password" value="123456"/>
    </bean>
 @Test
    public void testSimpleSet(){
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext("set-di.xml");
        User userBean = applicationContext.getBean("userBean", User.class);
        System.out.println(userBean);
    }

简单类型的经典应用

把数据源交给Spring容器管理。
 

//数据源:提供Connection对象的
public class MyDateSource implements DataSource {

    private String driver;
    private String url;
    private String username;

    @Override
    public String toString() {
        return "MyDateSource{" +
                "driver='" + driver + '\'' +
                ", url='" + url + '\'' +
                ", username='" + username + '\'' +
                ", password='" + password + '\'' +
                '}';
    }

    private String password;

    public void setDriver(String driver) {
        this.driver = driver;
    }

    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 getConnection() throws SQLException {
        return null;
    }

    @Override
    public Connection getConnection(String username, String password) throws SQLException {
        return null;
    }

    @Override
    public PrintWriter getLogWriter() throws SQLException {
        return null;
    }

    @Override
    public void setLogWriter(PrintWriter out) throws SQLException {

    }

    @Override
    public void setLoginTimeout(int seconds) throws SQLException {

    }

    @Override
    public int getLoginTimeout() throws SQLException {
        return 0;
    }

    @Override
    public Logger getParentLogger() throws SQLFeatureNotSupportedException {
        return null;
    }

    @Override
    public <T> T unwrap(Class<T> iface) throws SQLException {
        return null;
    }

    @Override
    public boolean isWrapperFor(Class<?> iface) throws SQLException {
        return false;
    }
}
<!--    Spring管理数据源-->
    <bean id="myDataSource" class="com.qiye.spring6.jdbc.MyDateSource">
        <property name="driver" value="com.mysql.cj.jdbc.Driver"/>
        <property name="password" value="1234"/>
        <property name="username" value="root"/>
        <property name="url" value="jdbc:mysql://localhost:3306/spring6"/>
    </bean>
 @Test
    public void testMyDataSource(){
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext("set-di.xml");
        MyDateSource myDataSource = applicationContext.getBean("myDataSource", MyDateSource.class);
        System.out.println(myDataSource);
    }

3.级联属性赋值(了解)

级联属性赋值,需要提供get方法有顺序要求

public class Clazz {
    //班级
    private String name;

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

    public void setName(String name) {
        this.name = name;
    }

}
public class Student {
    //学生
    private String name;
    private Clazz clazz;//班级

    @Override
    public String toString() {
        return "Student{" +
                "name='" + name + '\'' +
                ", clazz=" + clazz +
                '}';
    }

    public Clazz getClazz() {
        return clazz;
    }

    public void setClazz(Clazz clazz) {
        this.clazz = clazz;
    }

    public void setName(String name) {
        this.name = name;
    }
}

cascade.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
    <bean id="studentBean" class="com.qiye.spring6.bean.Student">
        <property name="name" value="张三"/>
        <property name="clazz" ref="clazzBean"/>
<!--        在这里级联属性赋值,需要在Student中提供clazz的get方法
            clazz和clazz.name的顺序不能颠倒 -->
        <property name="clazz.name" value="高一二班"/>
    </bean>
    <bean id="clazzBean" class="com.qiye.spring6.bean.Clazz"/>
</beans>
 @Test
    public void testCascade(){
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext("cascade.xml");
        Student studentBean = applicationContext.getBean("studentBean", Student.class);
        Clazz clazzBean = applicationContext.getBean("clazzBean", Clazz.class);
        System.out.println(studentBean);
        System.out.println(clazzBean);
    }

4.注入数组

简单类型:通过array标签和value标签。

非简单类型:通过array标签和ref标签。

public class QiYe {
    private String[] hobbies;
    private Girl[] girls;

    @Override
    public String toString() {
        return "QiYe{" +
                "hobbies=" + Arrays.toString(hobbies) +
                ", girls=" + Arrays.toString(girls) +
                '}';
    }

    public void setGirls(Girl[] girls) {
        this.girls = girls;
    }

    public void setHobbies(String[] hobbies) {
        this.hobbies = hobbies;
    }
}
public class Girl {
    private String name;

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

    public void setName(String name) {
        this.name = name;
    }
}
<?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="g1" class="com.qiye.spring6.bean.Girl">
        <property name="name" value="小红"/>
    </bean>
    <bean id="g2" class="com.qiye.spring6.bean.Girl">
        <property name="name" value="小绿"/>
    </bean>
    <bean id="g3" class="com.qiye.spring6.bean.Girl">
        <property name="name" value="小蓝"/>
    </bean>
    <bean id="QiYe" class="com.qiye.spring6.bean.QiYe">
<!--        给数组中的简单类型赋值-->
        <property name="hobbies" >
            <array>
                <value>打球</value>
                <value>下棋</value>
                <value>玩游戏</value>
            </array>
        </property>
<!--        给数组中的非简单类型赋值 -->
        <property name="girls">
            <array>
                <ref bean="g1"/>
                <ref bean="g2"/>
                <ref bean="g3"/>
            </array>
        </property>
    </bean>
</beans>
  @Test
    public void testArray(){
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring-array.xml");
        QiYe qiYe = applicationContext.getBean("QiYe", QiYe.class);
        System.out.println(qiYe);
    }

5.注入List集合和Set集合

使用list和set标签,简单类型用value,非简单类型用ref。

public class Person {
    private List<String> names;
    private Set<String> addrs;

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

    public void setNames(List<String> names) {
        this.names = names;
    }

    public void setAddrs(Set<String> addrs) {
        this.addrs = addrs;
    }
}
<?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="personBean" class="com.qiye.spring6.bean.Person">
        <property name="names">
            <list>
                <value>张三</value>
                <value>李四</value>
                <value>王五</value>
                <value>王五</value>
                <value>张三</value>
            </list>
        </property>
        <property name="addrs">
            <set>
                <value>天庭</value>
                <value>沙漠</value>
                <value>平原</value>
                <value>平原</value>
                <value>平原</value>
            </set>
        </property>
    </bean>
</beans>
@Test
    public void testCollection(){
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring-collection.xml");
        Person personBean = applicationContext.getBean("personBean", Person.class);
        System.out.println(personBean);
    }

6.注入Map集合和Properties

使用map标签,如果key和value是简单类型,用key和value,否则用key-ref和value-ref。

private Map<Integer,String> phones;
 <property name="phones">
            <map>
                <entry key="1" value="111"/>
                <entry key="2" value="112"/>
                <entry key="3" value="113"/>
            </map>
        </property>

注入Properties 

使用props标签和prop标签

private Properties properties;//注入属性类,Properties本质上也是Map集合,因为它的父类是HashTable,HashTable实现了Map接口。
 <property name="properties">
<!--            注入Properties属性类对象,Properties的key和value只能是字符串-->
                <props>
                    <prop key="driver">com.mysql.cj.jdbc.Driver</prop>
                    <prop key="username">root</prop>
                    <prop key="password">123</prop>
                </props>
        </property>

7.注入null和空字符串

不给属性注入值,就是null,也可以通过<null/>标签手动赋null。

value中什么都不写,就是赋空字符串,也可以通过<value/>标签手动赋空字符串。

public class Dog {
    private String name;
    private int age;

    @Override
    public String toString() {
        return "Dog{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }

    public void setName(String name) {
        this.name = name;
    }

    public void setAge(int age) {
        this.age = age;
    }
}
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<!--    外部bean-->
    <bean id="orderDaoBean" class="com.qiye.spring6.dao.OrderDao"/>
    <bean id="orderServiceBean" class="com.qiye.spring6.service.OrderService">
<!--        注入外部bean-->
        <property name="orderDao" ref="orderDaoBean"/>
    </bean>
<!--    内部bean,在property标签中使用嵌套的bean标签-->
    <bean id="orderServiceBean2" class="com.qiye.spring6.service.OrderService">
        <property name="orderDao">
            <bean class="com.qiye.spring6.dao.OrderDao"/>
        </property>
    </bean>

<!--    注入简单类型 -->
    <bean id="userBean" class="com.qiye.spring6.bean.User">
        <property name="username" value="张三"/>
        <property name="age" value="20"/>
        <property name="password" value="123456"/>
    </bean>
<!--    Spring管理数据源-->
    <bean id="myDataSource" class="com.qiye.spring6.jdbc.MyDateSource">
        <property name="driver" value="com.mysql.cj.jdbc.Driver"/>
        <property name="password" value="1234"/>
        <property name="username" value="root"/>
        <property name="url" value="jdbc:mysql://localhost:3306/spring6"/>
    </bean>
    <bean id="dogBean" class="com.qiye.spring6.bean.Dog">
<!-- 不给属性注入值,就是null,也可以手动赋null-->
<!--  <property name="name" value=""/>这样写就是赋空字符串 -->
<!--        <property name="name">
            <value/>
        </property>    这样写也可以赋空字符串-->
        <property name="name">
            <null/>
        </property>

        <property name="age" value="3"/>
    </bean>
</beans>
  @Test
    public void testDog(){
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext("set-di.xml");
        Dog dogBean = applicationContext.getBean("dogBean", Dog.class);
        System.out.println(dogBean);
    }

8.注入的值含有特殊符号

XML中五个特殊符号:< > ' " &

方式一:可以用转移字符来表示特殊符号

>  &gt;

<  &lt;

'  &apos;

"  &quot;

&  &amp;

方式二:使用<![CDATA[]]>    这里面的代码不会被XML解析。

public class MathBean {
    @Override
    public String toString() {
        return "MathBean{" +
                "result='" + result + '\'' +
                '}';
    }

    public void setResult(String result) {
        this.result = result;
    }

    private String result;
}
  <bean id="mathBean" class="com.qiye.spring6.bean.MathBean">
<!--        第一种方案:实体符号代替特殊符号    -->
<!--        <property name="result" value="2 &lt; 3"/>-->
<!--        第二种方案:使用<![CDATA[]]>-->
        <property name="result">
            <value><![CDATA[2 < 3]]></value>
        </property>
    </bean>
 @Test
    public void testSpecial(){
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext("set-di.xml");
        MathBean mathBean = applicationContext.getBean("mathBean", MathBean.class);
        System.out.println(mathBean);
    }

5.p命名空间注入

底层是set注入,只不过p命名空间注入可以让配置更加简单。

首先要在spring配置文件的头部加上p命名空间

xmlns:p="http://www.springframework.org/schema/p"

xml为如下格式(Dog类有String name,String age,Date birth三个属性,且都提供了set方法)

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:p="http://www.springframework.org/schema/p"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
    <bean id="dogBean" class="com.qiye.spring6.bean.Dog" p:name="爆米花" p:age="3" p:birth-ref="birthBean"/>
<!--    获取当前系统时间   -->
    <bean id="birthBean" class="java.util.Date"/>
</beans>
@Test
    public void testP(){
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring-p.xml");
        Dog dogBean = applicationContext.getBean("dogBean", Dog.class);
        System.out.println(dogBean);
    }

6.c命名空间注入

c命名空间注入是简化构造方法注入的。

首先要在spring配置文件的头部加上c命名空间

xmlns:c="http://www.springframework.org/schema/c"

xml(People类有String name,int age,boolean sex三个属性,People类中包含构造方法) 

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:c="http://www.springframework.org/schema/c"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
    <bean id="peopleBean" class="com.qiye.spring6.bean.People" c:_0="张三" c:_1="31" c:_2="true"/>
<!--  另一种写法  -->
<!--    <bean id="peopleBean" class="com.qiye.spring6.bean.People" c:name="李四" c:age="20" c:sex="false"/>-->
</beans>
 @Test
    public void testC(){
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring-c.xml");
        People peopleBean = applicationContext.getBean("peopleBean", People.class);
        System.out.println(peopleBean);
    }

7.util命名空间

util命名空间可以让配置复用。

引入util命名空间,头部添加两行。

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns: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 http://www.springframework.org/schema/util/spring-util.xsd">
<!--    配置util-->
    <util:properties id="prop">
        <prop key="driver">com.mysql.cj.jdbc.Driver</prop>
        <prop key="url">jdbc:mysql://localhost:3306/spring6</prop>
        <prop key="username">root</prop>
        <prop key="password">123456</prop>
    </util:properties>
<!--    数据源1   -->
    <bean id="myDataSource1" class="com.qiye.spring6.jdbc.MyDataSource1">
        <property name="properties" ref="prop"/>
    </bean>
<!--    数据源2    -->
    <bean id="myDataSource2" class="com.qiye.spring6.jdbc.MyDataSource2">
        <property name="properties" ref="prop"/>
    </bean>
</beans>
@Test
    public void testUtil(){
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring-util.xml");
        MyDataSource1 md1 = applicationContext.getBean("md1", MyDataSource1.class);
        MyDataSource2 md2 = applicationContext.getBean("md2", MyDataSource2.class);
        System.out.println(md2);
        System.out.println(md1);
    }

8.基于XML的自动装配

Spring可以完成自动化的注入,又称为自动装配,可以根据名字或者类型自动装配。

根据名称自动装配,底层为set注入。

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<!--    根据名字自动装配时,被注入的id要为get方法去掉get然后首字母小写     -->
    <bean id="orderDao" class="com.qiye.spring6.dao.OrderDao"/>
    <!--     根据名字自动装配    -->
    <bean id="orderService" class="com.qiye.spring6.service.OrderService" autowire="byName"/>

</beans>
@Test
    public void testAutowire(){
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring-autowire.xml");
        OrderService orderService = applicationContext.getBean("orderService", OrderService.class);
        orderService.generate();
    }

根据类型自动装配,基于set方法

<!--    根据类型自动装配-->
    <bean class="com.qiye.spring6.dao.VipDao"/>
    <bean class="com.qiye.spring6.dao.UserDao"/>
    <bean id="cs" class="com.qiye.spring6.service.CustomerService" autowire="byType"/>
 @Test
    public void testAutowire2(){
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring-autowire.xml");
        CustomerService cs = applicationContext.getBean("cs", CustomerService.class);
        cs.save();
    }

9.引入外部属性配置文件

username要加点别的东西,否则会默认取系统username名。

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
                           http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
    
<!--   先更改xml的头部信息,再引入外部的properties文件-->
    <context:property-placeholder location="jdbc.properties"/>
<!--    配置数据源 value通过${}来写 -->
    <bean id="ds" class="com.qiye.spring6.jdbc.MyDateSource">
        <property name="driver" value="${driverClass}"/>
        <property name="url" value="${url}"/>
        <property name="username" value="${username2}"/>
        <property name="password" value="${password}"/>
      </bean>
</beans>
@Test
    public void testPro(){
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring-properties.xml");
        MyDateSource ds = applicationContext.getBean("ds", MyDateSource.class);
        System.out.println(ds);
    }

五、Bean的作用域

1.单例和多例

Spring默认情况下Bean是单例(singleton)的,在Spring上下文初始化的时候实例化,每次调用getBean时得到的都是同一个对象。

我们可以在Spring配置文件中通过scope将Bean设置为多例(prototype)的,此时每调用一次getBean方法,就实例化一个对象。

<bean id="sb" class="com.qiye.spring6.bean.SpringBean" scope="prototype"/>

2.scope的其他值 

request和session这两个值只有在web项目中才存在。

request:一次请求一个Bean

session:一次会话一个Bean

global session:portlet应用专用的。

application:一个应用对应一个Bean(WEB项目)

websocket:一个websocket生命周期对应一个Bean。(WEB项目)

自定义scope:很少使用。

了解:自定义scope

自定义一个线程一个Bean。

一、自定义Scope(实现Scope接口)。 二、将自定义的Scope注册到Spring容器中。

三、使用自定义的Scope。

<?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 class="org.springframework.beans.factory.config.CustomScopeConfigurer">
        <property name="scopes">
            <map>
                <entry key="threadScope">
                    <bean class="org.springframework.context.support.SimpleThreadScope"/>
                </entry>
            </map>
        </property>
    </bean>
<!--    使用-->
    <bean id="sb" class="com.qiye.spring6.bean.SpringBean" scope="threadScope"/>

</beans>
 @Test
    public void testThreadScope(){
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring-scope.xml");
        SpringBean sb = applicationContext.getBean("sb", SpringBean.class);
        System.out.println(sb);
        //启动新线程
        new Thread(new Runnable() {
            @Override
            public void run() {
                SpringBean sb1 = applicationContext.getBean("sb", SpringBean.class);
                System.out.println(sb1);
            }
        }).start();
    }

六、GoF之工厂模式

Spring底层使用了大量的工厂模式,我们有必要了解一下工厂模式。

1.设计模式概述

设计模式:一种可以被重复利用的解决方案。

GoF:Gang of Four 四人组,四个人合著了一本书《设计模式》,所以GoF指23种设计模式

GoF三大类:

创建类:解决对象创建问题

单例模式,工厂方法模式,抽象工厂模式,建造者模式,原型模式

结构类:一些类或对象组合在一起的经典结构

代理模式,装饰模式,适配器模式,组合模式,享元模式,外观模式,桥接模式

行为类:解决类或对象之间交互问题

策略模式,模板方法模式,责任链模式,观察者模式,迭代子模式,命令模式,备忘录模式,状态模式,访问者模式,中介者模式,解释器模式

2.工厂模式三种形态

1.简单工厂模式。  不属于23种设计模式,又叫做静态工厂方法模式,是工厂方法模式的一种特例。

2.工厂方法模式。  23种设计模式之一。

3.抽象工厂模式。  23种设计模式之一。

3.简单工厂模式

简单工厂模式(Simple Factory Pattern )

简单工厂模式有三个角色:抽象产品角色,具体产品角色,工厂类角色。

优点:对于客户端来说,无需知道产品的生产细节,直接向工厂索要即可。生产者和消费者分离。

缺点:1.需要拓展新产品时,工厂类要修改,违背了OCP原则。

2.工厂类的责任重大,不能出现任何问题,称为全能/上帝类,工厂类一旦有问题,那么整个系统都会瘫痪。

例子:

抽象产品:

public abstract class Weapon {
//    抽象产品角色

    /**
     * 所有的武器都能攻击
     */
    public abstract void attack();
}

具体产品:

public class Tank extends Weapon{
    //具体产品角色 坦克
    @Override
    public void attack() {
        System.out.println("开炮!!!");
    }
}
public class Fighter extends Weapon{
    //具体产品角色 战斗机
    @Override
    public void attack() {
        System.out.println("抛出核弹!!!");
    }
}
public class Dagger extends Weapon{
    //具体产品角色 匕首
    @Override
    public void attack() {
        System.out.println("暗杀!!!");
    }
}

工厂

public class WeaponFactory {
    //工厂类角色

    /**
     * 静态方法,获取什么产品,就传什么参数。
     * @param weaponType Tank/Fighter/Dagger
     * @return 对应产品
     */
    public static Weapon get(String weaponType){
        if("TANK".equals(weaponType)){
            return new Tank();
        }else if("DAGGER".equals(weaponType)){
            return new Dagger();
        }else if("FIGHTER".equals(weaponType)){
            return new Fighter();
        }else{
            throw new RuntimeException("不支持该武器");
        }

    }
}

测试

public class Test {
    //客户端程序
    public static void main(String[] args) {
        //对于客户端来说,无需知道产品的生产细节,直接向工厂索要即可。生产者和消费者分离。
        //需要坦克
        Weapon tank = WeaponFactory.get("TANK");
        tank.attack();
        //需要匕首
        Weapon dagger = WeaponFactory.get("DAGGER");
        dagger.attack();
        //需要战斗机
        Weapon fighter = WeaponFactory.get("FIGHTER");
        fighter.attack();
    }
}

4.工厂方法模式

工厂方法模式解决了简单工厂模式的OCP原则问题,一个工厂对应生产一种产品,这样工厂就不是全能/上帝类了,也符合了OCP原则

工厂方法模式中的角色:抽象产品角色,具体产品角色,抽象工厂角色,具体工厂角色。

优点:拓展产品时,只需添加具体产品类和具体工厂类即可,符合OCP原则。

缺点:每增加一个产品,都需要增加一个具体类和对象实现工厂,导致类爆炸。

例子:

抽象产品角色:

public abstract class Weapon {
    //抽象产品角色
    public abstract void attack();
}

具体产品角色:

public class Dagger extends Weapon{
    //具体产品角色
    @Override
    public void attack() {
        System.out.println("暗杀!!!");
    }
}
public class Gun extends Weapon{
    //具体产品角色
    @Override
    public void attack() {
        System.out.println("射击!!!");
    }
}

抽象工厂角色:

public abstract class WeaponFactory {
    //抽象工厂角色
    public abstract Weapon get();
}

具体工厂角色:

public class DaggerFactory extends WeaponFactory{
    //具体工厂角色
    @Override
    public Weapon get() {
        return new Dagger();
    }
}
public class GunFactory extends WeaponFactory{
    //具体工厂角色
    @Override
    public Weapon get() {
        return new Gun();
    }
}

测试

public class Test {
    public static void main(String[] args) {
        WeaponFactory daggerFactory = new DaggerFactory();
        Weapon dagger = daggerFactory.get();
        dagger.attack();

        WeaponFactory gunFactory = new GunFactory();
        Weapon gun = gunFactory.get();
        gun.attack();
    }
}

七、Bean的实例化/获取

Spring为Bean提供了多种实例化方式,这些底层用的其实都是构造方法实例化。

1.通过构造方法实例化。

2.通过简单工厂模式实例化。

3.通过factory-bean(工厂方法模式)实例化。

4.通过FactoryBean接口实例化。

方式一、通过构造方法实例化

在spring配置文件中直接配置全路径,Spring会自动调用该类的无参数构造方法来实例化Bean。

public class SpringBean {
    public SpringBean() {
        System.out.println("SpringBean的无参构造方法执行了!");
    }
}
<?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="sb" class="com.qiye.spring6.bean.SpringBean"/>
</beans>
 @Test
    public void testInstantiation1(){
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.xml");
        SpringBean sb = applicationContext.getBean("sb", SpringBean.class);
        System.out.println(sb);
    }

方式二、通过简单工厂模式实例化

通过简单工厂模式实例化,需要指定工厂类调用的方法

public class Star {
    public Star() {
        System.out.println("Star的无参数构造方法执行");
    }
}
public class StarFactory {
    //简单工厂模式中的工厂角色
    public static Star get(){
        return new Star();
    }
}
<!--    通过简单工厂模式实例化,需要指定工厂类和调用的方法  -->
    <bean id="starBean" class="com.qiye.spring6.bean.StarFactory" factory-method="get"/>
 @Test
    public void testInstantiation2(){
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.xml");
        Star starBean = applicationContext.getBean("starBean", Star.class);
        System.out.println(starBean);
    }

方式三、通过factory-bean(工厂方法模式)实例化

和简单工厂模式不同的是,工厂方法模式的工厂要实例化。

public class Gun {
    //具体产品角色
    public Gun() {
        System.out.println("Gun的无参构造方法执行~");
    }
}
public class GunFactory {
    //具体工厂角色
    public Gun get(){
        return new Gun();
    }
}
<!--    通过工厂方法模式实例化  factory-bean属性和factory-method属性 调用哪个对象的哪个方法 -->
    <bean id="gunFactory" class="com.qiye.spring6.bean.GunFactory"/>
    <bean id="gun" factory-bean="gunFactory" factory-method="get"/>
 @Test
    public void testInstantiation3(){
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.xml");
        Gun gun = applicationContext.getBean("gun", Gun.class);
        System.out.println(gun);
    }

方式四、通过FactoryBean接口实例化

以上的三种方式中,factory-bean和factory-method都是我们自己定义的。

在Spring中,编写的类直接实现FactoryBean接口时,factory-bean和factory-method就无需我们指定了。

factory-bean会自动指向实现FactoryBean的类,factory-method会自动指向getObject()方法。

public class Person {
    //Bean
    public Person() {
        System.out.println("Person的无参构造方法执行");
    }
}
//工厂Bean
public class PersonFactoryBean implements FactoryBean<Person> {
    //重写该方法
    @Override
    public Person getObject() throws Exception {
        return new Person();
    }

    @Override
    public Class<?> getObjectType() {
        return null;
    }
    //这个方法在接口中有默认实现,指定是否为单例的,默认为true
    @Override
    public boolean isSingleton() {
        return true;
    }
}
<!--    通过FactoryBean接口实现,实际上是第三种方式的简化版,通过工厂Bean来返回一个普通的Bean -> Person对象  -->
    <bean id="person" class="com.qiye.spring6.bean.PersonFactoryBean"/>
  @Test
    public void testInstantiation4(){
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.xml");
        Person person = applicationContext.getBean("person", Person.class);
        System.out.println(person);
    }

BeanFactory和FactoryBean的区别

BeanFactory是Bean工厂,负责创建Bean对象。是Spring IoC容器的顶级对象。

FactoryBean是一个Bean,是能够辅助Spring实例化其他Bean对象的一个Bean。

注入自定义Date

java.util.Date一般是简单类型,但是注入时有格式要求 -> Mon Apr 07 22:39:39 CST 2025

java.util.Date也可以是非简单类型。

public class Student {
    private Date birth;
    @Override
    public String toString() {
        return "Student{" +
                "birth=" + birth +
                '}';
    }
    public void setBirth(Date birth) {
        this.birth = birth;
    }
}
public class DateFactoryBean implements FactoryBean<Date> {
    //工厂Bean辅助创建普通Bean
    private String strDate;

    public DateFactoryBean(String strDate) {
        this.strDate = strDate;
    }

    @Override
    public Date getObject() throws Exception {
        SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd");
        Date date = simpleDateFormat.parse(strDate);
        return date;
    }

    @Override
    public Class<?> getObjectType() {
        return null;
    }
}
 <bean id="date" class="com.qiye.spring6.bean.DateFactoryBean">
        <constructor-arg index="0" value="1999-10-01"/>
    </bean>
    <bean id="student" class="com.qiye.spring6.bean.Student">
        <property name="birth" ref="date"/>
    </bean>
@Test
    public void testDate(){
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.xml");
        Student student = applicationContext.getBean("student", Student.class);
        System.out.println(student);
    }

八、Bean的生命周期

1.五步

Bean生命周期可以粗略的划分为5大步:实例化Bean,Bean属性赋值,初始化Bean,使用Bean,销毁Bean。

public class User {
    private String name;

    public void setName(String name) {
        System.out.println("第二步,给对象的属性赋值");
        this.name = name;
    }

    public User(){
        System.out.println("第一步,无参构造方法执行");
    }
    public void initBean(){
        System.out.println("第三步,初始化Bean");
    }
    public void destroyBean(){
        System.out.println("第五步,销毁Bean");
    }
}
<!--    需要手动指定初始化和销毁方法-->
    <bean id="user" class="com.qiye.spring6.bean.User" init-method="initBean" destroy-method="destroyBean">
        <property name="name" value="张三"/>
    </bean>
   @Test
    public void testBeanLifecycleFive(){
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.xml");
        User user = applicationContext.getBean("user", User.class);
        System.out.println("第四步,使用Bean"+user);
        //关闭Spring容器才会销毁Bean
        ClassPathXmlApplicationContext context = (ClassPathXmlApplicationContext) applicationContext;
        context.close();
    }

2.七步

Bean生命周期还可以划分为7大步:实例化Bean,Bean属性赋值,初始化Bean,使用Bean,销毁Bean之外可以在第三步的前后添加代码,加入Bean后处理器

需要编写类实现BeanPostProcessor类,并且重写before和after方法。

实例化Bean,Bean属性赋值,Bean后处理器的before方法,初始化Bean,Bean后处理器的after方法,使用Bean,销毁Bean

Bean后处理器:

public class LogBeanPostProcessor implements BeanPostProcessor {
    //第一个参数为创建的bean对象,第二个参数为bean的名字
    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        System.out.println("执行Bean后处理器的before方法");
        return BeanPostProcessor.super.postProcessBeforeInitialization(bean, beanName);
    }

    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        System.out.println("执行Bean后处理器的after方法");
        return BeanPostProcessor.super.postProcessAfterInitialization(bean, beanName);
    }
}

spring.xml中配置 :

<!--    配置Bean后处理器,将作用于配置文件中所有的Bean-->
    <bean class="com.qiye.spring6.bean.LogBeanPostProcessor"/>

3.十步

两步加到Bean后处理器的before方法前后,一步加到销毁Bean之前(使用Bean之后)。

实例化Bean,Bean属性赋值,检查Bean是否实现了Aware的相关接口并设置相关依赖,Bean后处理器的before方法,检查Bean是否实现了InitializingBean接口并调用接口方法,初始化Bean,Bean后处理器的after方法,使用Bean,检查Bean是否实现了DisposableBean接口并调用接口方法,销毁Bean

Aware有三个接口:BeanNameAware, BeanClassLoaderAware, BeanFactoryAware 分别为Bean的名字,Bean的类的加载器,生产Bean的工厂对象。

InitializingBean接口有一个方法:afterPropertiesSet。

DisposableBean接口的方法:destroy。

public class User implements BeanNameAware, BeanClassLoaderAware, BeanFactoryAware , InitializingBean,DisposableBean {
    private String name;

    public void setName(String name) {
        System.out.println("第二步,给对象的属性赋值");
        this.name = name;
    }

    public User(){
        System.out.println("第一步,无参构造方法执行");
    }
    public void initBean(){
        System.out.println("第三步,初始化Bean");
    }
    public void destroyBean(){
        System.out.println("第五步,销毁Bean");
    }

    @Override
    public void setBeanClassLoader(ClassLoader classLoader) {
        System.out.println("Bean类的加载器:"+classLoader);
    }

    @Override
    public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
        System.out.println("生产Bean的工厂对象:"+beanFactory);
    }

    @Override
    public void setBeanName(String name) {
        System.out.println("这个Bean的名字"+name);
    }

    @Override
    public void afterPropertiesSet() throws Exception {
        System.out.println("InitializingBean的afterPropertiesSet执行了");
    }

    @Override
    public void destroy() throws Exception {
        System.out.println("DisposableBean的destroy方法执行了");
    }
}

4.Spring容器只对单例的Bean进行完整的生命周期管理

如果是Prototype的Bean,Spring只负责将Bean创建完毕(前八步),当客户端获取到Bean后,Spring就不再管理该对象的生命周期了。

5.自己new的对象让Spring容器管理

通过DefaultListableBeanFactory对象来注册自己new的Bean。

   @Test
    public void testRegisterBean(){
        //自己new的对象,此时并没有被Spring容器管理
        Student student = new Student();
        //将自己new的对象交给Spring容器管理
        // 获取DefaultListableBeanFactory对象
        DefaultListableBeanFactory factory = new DefaultListableBeanFactory();
        //注册自己new的Bean
        factory.registerSingleton("studentBean",student);
        //获取Bean
        Student studentBean = factory.getBean("studentBean", Student.class);
        System.out.println(studentBean == student);///true
    }

九、Bean的循环依赖问题

A对象中有B属性,B对象中有A属性,这就是循环依赖。

1.singleton下的set注入产生的循环依赖

singleton + setter模式的循环依赖没有任何问题,原因是在这种模式下,Spring对Bean的管理分为清晰的两个阶段,1.在Spring容器加载时实例化Bean,只要其中任意一个Bean实例化之后,马上进行"曝光"。2."曝光"之后再进行属性的赋值。

核心解决方案是:实例化对象和对象的属性赋值分阶段执行。

只有在scope是singlton的情况下,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">
<!--    singleton + setter模式的循环依赖没有任何问题-->
    <bean id="husbandBean" class="com.qiye.spring6.bean.Husband" scope="singleton">
        <property name="name" value="张三"/>
        <property name="wife" ref="wifeBean"/>
    </bean>
    <bean id="wifeBean" class="com.qiye.spring6.bean.Wife" scope="singleton">
        <property name="name" value="李四"/>
        <property name="husband" ref="husbandBean"/>
    </bean>
</beans>

2.prototype下的set注入产生的循环依赖

两个Bean都为prototype时会出现异常。

多例在getBean时才会给属性赋值,在赋值时会循环赋值,出现BeanCurrentlyInCreationException异常。

new husband会导致new wife new wife又会导致new husband 死循环。

将一个prototype改为singleton就能解决问题。

<?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">
<!--    prototype + setter模式的循环依赖会出现问题-->
    <bean id="husbandBean" class="com.qiye.spring6.bean.Husband" scope="prototype">
        <property name="name" value="张三"/>
        <property name="wife" ref="wifeBean"/>
    </bean>
    <bean id="wifeBean" class="com.qiye.spring6.bean.Wife" scope="prototype">
        <property name="name" value="李四"/>
        <property name="husband" ref="husbandBean"/>
    </bean>
</beans>
@Test
    public void testCD(){
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.xml");
        Husband husbandBean = applicationContext.getBean("husbandBean", Husband.class);
        Wife wifeBean = applicationContext.getBean("wifeBean", Wife.class);
        System.out.println(husbandBean);
        System.out.println(wifeBean);
    }

3.构造注入模式下的循环依赖

构造注入,此时是在构造函数中赋值,即使是singleton,也无法"曝光",所以构造注入模式下的循环依赖是无法解决的。

Husband和Wife都提供了构造方法,此时会出现异常。

<?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="h" class="com.qiye.spring6.bean2.Husband" scope="singleton">
        <constructor-arg index="0" value="张三"/>
        <constructor-arg index="1" ref="w"/>
    </bean>
    <bean id="w" class="com.qiye.spring6.bean2.Wife" scope="singleton">
        <constructor-arg index="0" value="李四"/>
        <constructor-arg index="1" ref="h"/>
    </bean>
</beans>

4.Spring解决循环依赖的原理

只能在set+singleton下解决,此时可以将实例化Bean给Bean属性赋值分开。

实例化Bean时,调用无参数构造方法,先不给属性赋值,而是提前将Bean对象(的工厂Bean)"曝光"出去

通过setter方法给Bean属性赋值。

两个步骤完全是分开完成的,且可以在不同的时间点上完成。

Bean是单例的,我们可以先把所有的单例Bean实例化并放到一个Map集合中(称之为缓存),然后再慢慢的调用setter方法给属性赋值,解决了循环依赖问题。

缓存分别为:三个缓存都为Map,key都为Bean的id

singletonObjects 一级缓存,存储完整的单例Bean对象(属性赋完值了)。

earlySingletonObjects 二级缓存,存储早期Bean对象(属性未赋值)。

singletonFactories 三级缓存,单例工厂对象,存储了大量的工厂对象,存储创建单例对象时对应的单例工厂对象。

十、反射机制回顾

1.回顾反射机制

public class SomeService {
    public void doSome(){
        System.out.println("doSome~");
    }
    public String doSome(String s){
        System.out.println("doSome String s~");
        return s;
    }
    public String doSome(String s,int i){
        System.out.println("doSome(String s,int i)~");
        return s + i;
    }

}
public class Test {
    //使用反射机制调用方法
    public static void main(String[] args) throws Exception{
//    调用一个方法,应该有四个要素 调用哪个对象 调用哪个方法 调用方法时传什么参数 返回结果
        //1.获取类
        Class<?> clazz = Class.forName("com.qiye.reflect.service.SomeService");
        //2.获取方法
        Method doSomeMethod = clazz.getDeclaredMethod("doSome", String.class, int.class);
        //3.调用方法
        Object o = clazz.getDeclaredConstructor().newInstance();
        String s = (String)doSomeMethod.invoke(o, "乔巴", 1);
        System.out.println(s);
    }
}

2.SpringDI核心实现

知道了类名和属性名,就可以赋值。

public class Test2 {
    public static void main(String[] args) throws Exception {
        //有com.qiye.reflect.service.User类
        //这个类符合javaBean规范(属性私有化,提供公开的setter和getter方法)
        //这个类中有一个属性,属性名是age,age的类型是int

        //采用反射机制调用set方法给User对象的age属性赋值。
        String className = "com.qiye.reflect.service.User";
        String propertyName = "age";
        //反射机制调用setAge方法
        //获取类
        Class<?> clazz = Class.forName(className);
        //获取方法名
        String setMethodName = "set" + propertyName.toUpperCase().charAt(0) + propertyName.substring(1);
        //根据属性名获取属性类型
        Field field = clazz.getDeclaredField(propertyName);
        //获取方法
        Method setMethod = clazz.getDeclaredMethod(setMethodName, field.getType());
        //准备对象
        Object obj = clazz.getConstructor().newInstance();
        //调用方法
        setMethod.invoke(obj,8);
        System.out.println(obj);
    }
}

十一、Spring IoC注解式开发

注解式开发:简化XML配置。

Spring6倡导全注解式开发。

1.回顾注解

//自定义注解
//标注注解的注解,称为元注解
//使用注解时,只有一个属性且为value,value可以省略。如果数组只有一个元素,{}可以省略。
@Target({ElementType.TYPE,ElementType.FIELD})//表示该注解可以出现在TYPE和FIELD上(类和属性)
@Retention(RetentionPolicy.RUNTIME)//元注解@Retention,RUNTIME用来标注该注解最终保留到class文件中,且可以被反射机制读取。
public @interface Component {
    //定义注解的属性
    String value();
    String name();
    String[] names();

}
@Component(value = "userBean",name = "77",names={"66","77"})
public class User {

}
public class ReflectAnnotation1 {
    public static void main(String[] args) throws Exception{
        //反射机制读取注解
        //获取类
        Class<?> clazz = Class.forName("com.qiye.factory.bean.User");
        //判断类上有无该注解
        if (clazz.isAnnotationPresent(Component.class)) {
            //获取类上的注解
            Component annotation = clazz.getAnnotation(Component.class);
            //访问注解属性
            System.out.println(annotation.value());
            System.out.println(annotation.name());
        }
    }
}
public class ComponentScan {
    public static void main(String[] args) throws Exception{
        Map<String,Object> beanMap = new HashMap<>();
        //只知道包名,要求扫描包下所有的类,当这个类上有Component注解时,实例化该对象并放到Map集合中。
        String packageName = "com.qiye.factory.bean";
        //扫描 .正则表达式代表任意字符,这里的.必须是普通的字符 \.代表普通的.  而在java中\\代表\ 所以要写\\.
        String replace = packageName.replaceAll("\\.", "/");
        URL url = ClassLoader.getSystemClassLoader().getResource(replace);
        String path = url.getPath();
        //获取绝对路径下的所有文件
        File file = new File(path);
        File[] files = file.listFiles();
        Arrays.stream(files).forEach(f->{
            try {
                String clasName = packageName + "." + f.getName().split("\\.")[0];
                Class<?> clazz = null;
                clazz = Class.forName(clasName);
                if (clazz.isAnnotationPresent(Component.class)) {
                    Component annotation = clazz.getAnnotation(Component.class);
                    String id = annotation.value();
                    Object obj = clazz.getDeclaredConstructor().newInstance();
                    beanMap.put(id,obj);
                }
            } catch (ClassNotFoundException e) {
                e.printStackTrace();
            } catch (InvocationTargetException e) {
                throw new RuntimeException(e);
            } catch (InstantiationException e) {
                throw new RuntimeException(e);
            } catch (IllegalAccessException e) {
                throw new RuntimeException(e);
            } catch (NoSuchMethodException e) {
                throw new RuntimeException(e);
            }

        });
        System.out.println(beanMap);
    }
}

2.声明Bean的注解

声明Baen使用以下四个注解都可以。

@Component   组件

@Controller     控制器 ,表示层

@Service      业务,业务层

@Repository   仓库Dao,持久层

@Controller,@Service ,@Repository都是@Component的别名,目的是增强程序的可读性。

3.Spring注解使用

要保证加入了aop依赖。

1.配置文件添加context命名空间

2.在配置文件中指定要扫描的包

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
                           http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
<!--指定要扫描哪些包中的类-->
    <context:component-scan base-package="com.qiye.spring6.bean"/>
</beans>

3.使用注解

value的默认值为首字母变小写后的类名。

@Component("userBean")
public class User {
}

上方注解相当于:

    <bean id="userBean" class="com.qiye.spring6.bean.User"/>
@Controller("vipBean")
public class Vip {
}
@Service("orderBean")
public class Order {
}
@Repository("studentBean")
public class Student {
}

测试

 @Test
    public void testBeanComponent(){
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.xml");
        User userBean = applicationContext.getBean("userBean", User.class);
        Vip vipBean = applicationContext.getBean("vipBean", Vip.class);
        Order orderBean = applicationContext.getBean("orderBean", Order.class);
        Student studentBean = applicationContext.getBean("studentBean", Student.class);
        System.out.println(userBean);
        System.out.println(vipBean);
        System.out.println(orderBean);
        System.out.println(studentBean);
    }

4.多个包扫描

1.在配置文件中指定多个包,用,隔开。

<context:component-scan base-package="com.qiye.spring6.bean,com.qiye.spring6.dao"/>

2.可以指定这多个包共同的父包(扫描范围变大,会牺牲效率)

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

5.选择性实例化Bean

我们可以选择性的实例化Bean,例如只实例化@Controller注解的Bean。

方式一:

use-default-filters="false"表示让对应包下,所有声明Bean的注解全部失效。

context:include-filter标签可以指定生效的注解,注解要写全限定类名。

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
                           http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">

    <context:component-scan base-package="com.qiye.spring6.bean2" use-default-filters="false">
        <context:include-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
    </context:component-scan>
</beans>

方式二:

use-default-filters="true"表示让对应包下,所有声明Bean的注解全部生效。(true为默认值)

context:exclude-filter标签可以指定失效的注解,注解要写全限定类名。

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
                           http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">

    <context:component-scan base-package="com.qiye.spring6.bean2">
        <context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
    </context:component-scan>
</beans>

6.负责注入的注解

@Value  属性是简单类型时,可以使用@Value注解进行注入。

@Autowired  注入非简单类型,被翻译为自动装配(自动连线)

@Qualifier 配合Autowired注解实现按名字装配

@Resource 

@Value

使用@Value注解时可以不提供set方法。

@Value注解也可以使用到set方法和构造方法上。

@Component
public class MyDataSource implements DataSource {
    @Value("com.mysql.cj.jdbc.Driver")
    private String Driver;
    @Value("jdbc:mysql://localhost:3306/spring6")
    private String url;
    @Value("root")
    private String username;
    @Value("123456")
    private String password;

    @Override
    public String toString() {
        return "MyDataSource{" +
                "Driver='" + Driver + '\'' +
                ", url='" + url + '\'' +
                ", username='" + username + '\'' +
                ", password='" + password + '\'' +
                '}';
    }

    public void setDriver(String driver) {
        Driver = driver;
    }

    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 getConnection() throws SQLException {
        return null;
    }

    @Override
    public Connection getConnection(String username, String password) throws SQLException {
        return null;
    }

    @Override
    public PrintWriter getLogWriter() throws SQLException {
        return null;
    }

    @Override
    public void setLogWriter(PrintWriter out) throws SQLException {

    }

    @Override
    public void setLoginTimeout(int seconds) throws SQLException {

    }

    @Override
    public int getLoginTimeout() throws SQLException {
        return 0;
    }

    @Override
    public Logger getParentLogger() throws SQLFeatureNotSupportedException {
        return null;
    }

    @Override
    public <T> T unwrap(Class<T> iface) throws SQLException {
        return null;
    }

    @Override
    public boolean isWrapperFor(Class<?> iface) throws SQLException {
        return false;
    }
}
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
                           http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
    <context:component-scan base-package="com.qiye.spring6.bean3"/>
</beans>
   @Test
    public void testDIByAnnotation(){
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring-di-annotation.xml");
        MyDataSource myDataSource = applicationContext.getBean("myDataSource", MyDataSource.class);
        System.out.println(myDataSource);
    }

@Autowired

@Autowired只能根据类型装配(byType)。

@Autowired无需提供set方法。

@Autowired可以写到set方法、构造方法、构造方法的参数上。

在只有一个构造方法且构造方法的参数和类的属性一致时,@Autowired还可以省略。

@Autowired无需任何属性,效果是根据类型进行自动装配

public interface OrderDao {
    void insert();
}
@Repository
public class OrderDaoImplForMySQL implements OrderDao {
    @Override
    public void insert() {
        System.out.println("MySQL数据库正在保存订单信息...");
    }
}
@Service
public class OrderService {
    @Autowired
    private OrderDao orderDao;
    public void generate(){
        orderDao.insert();
    }
}
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
                           http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
    <context:component-scan base-package="org.qiye"/>

</beans>
 @Test
    public void testAutowired(){
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring-autowired.xml");
        OrderService orderService = applicationContext.getBean("orderService", OrderService.class);
        orderService.generate();
    }

@Autowired与@Qualifier

如果想按名字装配,需要@Autowired与@Qualifier配合使用

指定名字。

@Service
public class OrderService {
    @Autowired
    @Qualifier("orderDaoImplForMySQL")
    private OrderDao orderDao;
    public void generate(){
        orderDao.insert();
    }
}

@Resource

@Resource注解也可以完成非简单类型的注入,官方建议使用Resource注解,该注解是java标准规范JSR-250中的类,更加具有通用性。

@Resource注解是按名字装配的,未指定name时,将属性名作为name,找不到属性名时根据类型装配。

@Resource注解可以用到属性和setter方法上。

需要引入该依赖

 <dependency>
      <groupId>jakarta.annotation</groupId>
      <artifactId>jakarta.annotation-api</artifactId>
      <version>2.1.1</version>
</dependency>
public interface StudentDao {
    void deleteById();
}
@Repository
public class StudentDaoImpl implements StudentDao {
    @Override
    public void deleteById() {
        System.out.println("mysql数据库删除学生信息~");
    }
}
@Service
public class StudentService {
    @Resource(name="studentDaoImpl")
    private StudentDao studentDao;
    public void deleteStudent(){
        studentDao.deleteById();
    }
}
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
                           http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
    <context:component-scan base-package="cn.qiye"/>

</beans>
   @Test
    public void testResource(){
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring-resource.xml");
        StudentService studentService = applicationContext.getBean("studentService", StudentService.class);
        System.out.println(studentService);
    }

7.全注解式开发

写一个类代替Spring配置文件

@Configuration为声明该类为配置文件的注解,@ComponentScan注解下写具体要扫描的包

@Configuration
@ComponentScan({"cn.qiye.dao","cn.qiye.service"})
public class SpringConfig {
    //代替Spring框架的配置文件
}

 此时要new AnnotationConfigApplicationContext

@Test
    public void testNoXML(){
        AnnotationConfigApplicationContext annotationConfigApplicationContext = new AnnotationConfigApplicationContext(SpringConfig.class);
        StudentService studentService = annotationConfigApplicationContext.getBean("studentService", StudentService.class);
        studentService.deleteStudent();
    }

十二、JDBCTemplate

Spring提供的一个JDBC模板类,是对JDBC的封装,简化JDBC代码。

但是一般来说会让Spring集成其他的ORM,例如MyBatis。

要引入mysql驱动,spring-jdbc依赖。

数据准备:建表t_user,创建User类,MyDataSource类。

1.环境准备

public class MyDataSource implements DataSource {
    private String driver;
    private String url;
    private String username;
    private String password;

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

    public void setDriver(String driver) {
        this.driver = driver;
    }

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


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

    @Override
    public Connection getConnection() throws SQLException {
        return null;
    }

    @Override
    public Connection getConnection(String username, String password) throws SQLException {
        try {
            //注册驱动
            Class.forName(driver);
            //获取数据库连接对象
            Connection connection = DriverManager.getConnection(url, username, password);
            return connection;
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
        return null;
    }

    @Override
    public PrintWriter getLogWriter() throws SQLException {
        return null;
    }

    @Override
    public void setLogWriter(PrintWriter out) throws SQLException {

    }

    @Override
    public void setLoginTimeout(int seconds) throws SQLException {

    }

    @Override
    public int getLoginTimeout() throws SQLException {
        return 0;
    }

    @Override
    public Logger getParentLogger() throws SQLFeatureNotSupportedException {
        return null;
    }

    @Override
    public <T> T unwrap(Class<T> iface) throws SQLException {
        return null;
    }

    @Override
    public boolean isWrapperFor(Class<?> iface) throws SQLException {
        return false;
    }
}
<?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="ds" class="com.qiye.spring.bean.MyDataSource">
        <property name="driver" value="com.mysql.cj.jdbc.Driver"/>
        <property name="url" value="jdbc:mysql://localhost:3306/spring6"/>
        <property name="username" value="root"/>
        <property name="password" value="1234"/>
    </bean>
    <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
        <property name="dataSource" ref="ds"/>
    </bean>
</beans>

2.增删改查

insert

  @Test
    public void testInsert(){
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.xml");
        JdbcTemplate jdbcTemplate = applicationContext.getBean("jdbcTemplate", JdbcTemplate.class);
        String sql = "insert into t_user(id,real_name,age) values(null,?,?)";
        //在jdbcTemplate中只要是,insert delete update语句,都是调用update方法
        int count = jdbcTemplate.update(sql,"66",66);
        System.out.println("插入了"+count+"条记录");
    }

delete

 @Test
    public void testDelete(){
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.xml");
        JdbcTemplate jdbcTemplate = applicationContext.getBean("jdbcTemplate", JdbcTemplate.class);
        String sql = "delete from t_user where id = ?";
        int count = jdbcTemplate.update(sql, 2);
        System.out.println(count);
    }

update

 @Test
    public void testDelete(){
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.xml");
        JdbcTemplate jdbcTemplate = applicationContext.getBean("jdbcTemplate", JdbcTemplate.class);
        String sql = "delete from t_user where id = ?";
        int count = jdbcTemplate.update(sql, 2);
        System.out.println(count);
    }

query一个

new BeanPropertyRowMapper<>()的参数为查询结果封装的类型。

jdbcTemplate.queryForObject(sql, new BeanPropertyRowMapper<>(User.class), 4);
 @Test
    public void testQueryOne(){
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.xml");
        JdbcTemplate jdbcTemplate = applicationContext.getBean("jdbcTemplate", JdbcTemplate.class);
        String sql = "select id,real_name,age from t_user where id = ?";
        User user = jdbcTemplate.queryForObject(sql, new BeanPropertyRowMapper<>(User.class), 4);
        System.out.println(user);
    }

query多个

    @Test
    public void testQueryAll(){
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.xml");
        JdbcTemplate jdbcTemplate = applicationContext.getBean("jdbcTemplate", JdbcTemplate.class);
        String sql = "select id,real_name,age from t_user";
        List<User> query = jdbcTemplate.query(sql, new BeanPropertyRowMapper<>(User.class));
        System.out.println(query);
    }

查一个值

    @Test
    public void testQueryOneValue(){
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.xml");
        JdbcTemplate jdbcTemplate = applicationContext.getBean("jdbcTemplate", JdbcTemplate.class);
        String sql = "select count(*) from t_user";
        Integer integer = jdbcTemplate.queryForObject(sql, int.class);
        System.out.println(integer);
    }

批量添加

batchUpdate

    @Test
    public void testBatchInsert(){
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.xml");
        JdbcTemplate jdbcTemplate = applicationContext.getBean("jdbcTemplate", JdbcTemplate.class);
        String sql = "insert into t_user(id,real_name,age) values(null,?,?)";
        //准备数据
        Object[] o1 = {"小白",2};
        Object[] o2 = {"小新",6};
        Object[] o3 = {"美伢",29};
        Object[] o4 = {"广志",30};
        List<Object[]> list = new ArrayList<>();
        list.add(o1);
        list.add(o2);
        list.add(o3);
        list.add(o4);
        int[] ints = jdbcTemplate.batchUpdate(sql, list);
        System.out.println(Arrays.toString(ints));
    }

批量更新

batchUpdate

 @Test
    public void testBatchUpdate(){
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.xml");
        JdbcTemplate jdbcTemplate = applicationContext.getBean("jdbcTemplate", JdbcTemplate.class);
        String sql = "update t_user set real_name = ?,age = ? where id = ?";
        //准备数据
        Object[] o1 = {"神犬小白",2,5};
        Object[] o2 = {"野原新之助",6,6};
        List<Object[]> list = new ArrayList<>();
        list.add(o1);
        list.add(o2);
        int[] ints = jdbcTemplate.batchUpdate(sql, list);
        System.out.println(Arrays.toString(ints));
    }

批量删除

batchUpdate

  @Test
    public void testBatchDelete(){
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.xml");
        JdbcTemplate jdbcTemplate = applicationContext.getBean("jdbcTemplate", JdbcTemplate.class);
        String sql = "delete from t_user where id=?";
        Object[] o1 ={8};
        Object[] o2 ={9};
        Object[] o3 ={10};
        Object[] o4 ={11};
        List<Object[]> list = new ArrayList<>();
        list.add(o1);
        list.add(o2);
        list.add(o3);
        list.add(o4);
        int[] ints = jdbcTemplate.batchUpdate(sql, list);
        System.out.println(Arrays.toString(ints));
    }

3.jdbcTemplate中使用回调函数(了解)

如果想写JDBC代码,就可以写在回调函数中。

 @Test
    public void testCallback(){
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.xml");
        JdbcTemplate jdbcTemplate = applicationContext.getBean("jdbcTemplate", JdbcTemplate.class);
        String sql = "select id,real_name,age from t_user where id = ?";
        User user = jdbcTemplate.execute(sql, new PreparedStatementCallback<User>() {
            @Override
            public User doInPreparedStatement(PreparedStatement ps) throws SQLException, DataAccessException {
                ps.setInt(1,6);
                ResultSet resultSet = ps.executeQuery();
                User user = null;
                if (resultSet.next()) {
                    int id = resultSet.getInt("id");
                    String name = resultSet.getString("real_name");
                    int age = resultSet.getInt("age");
                    user = new User(id,name,age);
                }
                return user;
            }
        });
        System.out.println(user);
    }

4.JdbcTemplate整合德鲁伊连接池

<!--        引入德鲁伊连接池    -->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid</artifactId>
            <version>1.2.13</version>
        </dependency>
<?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="ds" class="com.alibaba.druid.pool.DruidDataSource">
<!--注意该name的值-->
        <property name="driverClassName" value="com.mysql.cj.jdbc.Driver"/>
        <property name="url" value="jdbc:mysql://localhost:3306/spring6"/>
        <property name="username" value="root"/>
        <property name="password" value="1234"/>
    </bean>
    <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
        <property name="dataSource" ref="ds"/>
    </bean>
</beans>

十三、GOF之代理模式

1.概述

代理模式是23种设计模式之一,属于结构型设计模式。

代理模式是面向切面编程AOP的底层实现原理。

作用:

1.当一个对象需要受到保护时,可以考虑使用代理对象去完成某个行为。

2.需要给某个对象的功能进行功能增强时,可以考虑找一个代理进行增强。

3.A对象无法直接和B对象直接交互时,也可以使用代理模式。

代理模式三大角色:目标对象代理对象目标对象和代理对象的公共接口(目标对象和代理对象应该具有相同的行为动作,因为要让客户端在使用代理对象时就像使用目标对象一样)。

2.静态代理

类和类之间的关系中的泛化关系:继承 is a。

关联关系:一个类(的对象)是另一个类(的对象)的一个属性 has a。

泛化关系的耦合度比关联关系高,优先选择使用关联关系。

缺点:代码复用性不强,导致类爆炸,且不好维护。

目标对象和代理对象的公共接口

public interface OrderService {//公共接口
    //订单业务接口
    //生成订单
    void generate();
    //修改订单
    void modify();
    //查看订单详情
    void detail();
}

目标对象

public class OrderServiceImpl implements OrderService{//目标对象
    @Override
    public void generate() {//目标方法
        //模拟网络延时
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("订单已生成");
    }

    @Override
    public void modify() {//目标方法
        //模拟网络延时
        try {
            Thread.sleep(500);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("订单已修改");
    }

    @Override
    public void detail() {//目标方法
        //模拟网络延时
        try {
            Thread.sleep(200);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("查看订单详情");
    }
}

代理对象 

public class OrderServiceProxy implements OrderService{//代理对象,和目标对象实现同一些接口
    //将目标对象作为代理对象的一个属性,为关联关系
    private OrderService target;//目标对象一定实现了OrderService这个公共接口。
    //创建代理对象时,需要传一个目标对象作为参数。
    public OrderServiceProxy(OrderService target) {
        this.target = target;
    }

    @Override
    public void generate() {//代理方法
        long begin = System.currentTimeMillis();
        target.generate();
        long end = System.currentTimeMillis();
        System.out.println("耗时"+(end - begin)+"毫秒");
    }

    @Override
    public void modify() {//代理方法
        long begin = System.currentTimeMillis();
        target.modify();
        long end = System.currentTimeMillis();
        System.out.println("耗时"+(end - begin)+"毫秒");
    }

    @Override
    public void detail() {//代理方法
        long begin = System.currentTimeMillis();
        target.detail();
        long end = System.currentTimeMillis();
        System.out.println("耗时"+(end - begin)+"毫秒");
    }

}

测试

public class Test {
    public static void main(String[] args) {
        OrderService target = new OrderServiceImpl();
        OrderService proxy = new OrderServiceProxy(target);
        proxy.generate();
        proxy.modify();
        proxy.detail();
    }
}

3.动态代理

使用了字节码生成技术,解决了静态代理的缺点。

内存中生成类的技术:

1.JDK动态代理技术:只能代理接口。

2.CGLIB(Code Generation Library)动态代理技术:高性能,高质量的Code生成类库。既可以代理接口又可以代理类。(底层是通过继承的方式实现的,所以性能强于JDK动态代理技术)(底层有一个字节码处理框架ASM)

3.Javassist动态代理技术。

JDK动态代理

public interface OrderService {//公共接口
    //订单业务接口
    //生成订单
    void generate();
    //修改订单
    void modify();
    //查看订单详情
    void detail();
    String getName();
}
public class OrderServiceImpl implements OrderService{//目标对象
    @Override
    public void generate() {//目标方法
        //模拟网络延时
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("订单已生成");
    }

    @Override
    public void modify() {//目标方法
        //模拟网络延时
        try {
            Thread.sleep(500);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("订单已修改");
    }

    @Override
    public void detail() {//目标方法
        //模拟网络延时
        try {
            Thread.sleep(200);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("查看订单详情");
    }

    @Override
    public String getName() {
        System.out.println("getName执行了");
        return "K.O";
    }
}

创建代理对象 Proxy.newProxyInstance() 第一个参数为类加载器,第二个参数为代理类要实现的接口,第三个参数为调用处理器。

newProxyInstance()翻译为新建代理对象,通过调用这个方法可以创建代理对象。

类加载器ClassLoader loader 必须和目标类的类加载器一致。

代理类要实现的接口Class<?>[] interfaces 代理类和目标类要实现同一些接口。

调用处理器InvocationHandler h ,是一个接口,在该接口的实现类中编写增强代码。

public class Client {//客户端程序
    public static void main(String[] args) {
        //创建目标对象
        OrderService target = new OrderServiceImpl();
        //创建代理对象
        OrderService proxyObj = (OrderService)Proxy.newProxyInstance(target.getClass().getClassLoader(),target.getClass().getInterfaces(),new TimerInvocationHandler(target));
        //调用代理对象的代理方法
        proxyObj.generate();
        proxyObj.modify();
        proxyObj.detail();
        String a = proxyObj.getName();
        System.out.println(a);
    }
}

invoke方法何时被调用:当代理对象调用代理方法的时候,注册在InvocationHandler调用处理器中的invoke()方法会被调用。

invoke方法的三个参数:

Object proxy:代理对象的引用,使用的少。

Method method:目标方法。

Object[] args:目标方法的实参。

public class TimerInvocationHandler implements InvocationHandler {//专门负责计时的调用处理器
    private Object target;
    public TimerInvocationHandler(Object target) {
        this.target = target;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        long begin = System.currentTimeMillis();
        //调用目标对象的目标方法
        Object retValue = method.invoke(target,args);
        long end = System.currentTimeMillis();
        System.out.println("耗时"+(end-begin)+"毫秒");
        //继续向上返回
        return retValue;
    }
}

我们可以封装这一段代码,简化代码。

public class ProxyUtil {//封装Proxy
    public static OrderService newProxyInstance(Object target){
        return (OrderService) Proxy.newProxyInstance(target.getClass().getClassLoader(),target.getClass().getInterfaces(),new TimerInvocationHandler(target));
    }
}

CGLIB动态代理

引入依赖

<dependency>
      <groupId>cglib</groupId>
      <artifactId>cglib</artifactId>
      <version>3.3.0</version>
</dependency>

jdk8以上要配置

--add-opens java.base/java.lang=ALL-UNNAMED 

--add-opens java.base/sun.net.util=ALL-UNNAMED 

public class UserService {//目标类
    //目标方法
    public boolean login(String username,String password){
        System.out.println("系统正在验证身份");
        if ("admin".equals(username) && "123".equals(password)) {
            return true;
        }
        return false;
    }
    //目标方法
    public void logout(){
        System.out.println("系统正在退出...");
    }
}

public class Client {
    public static void main(String[] args) {
        //创建字节码增强器对象
        //这个对象是CGLIB库中的核心对象,依靠它来生成代理类。
        Enhancer enhancer = new Enhancer();
        //告诉CGLIB目标类是哪个
        enhancer.setSuperclass(UserService.class);
        //设置回调,等同于JDK动态代理机制的InvocationHandler
        enhancer.setCallback(new MethodInterceptor() {
            @Override
            public Object intercept(Object target, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
                long begin = System.currentTimeMillis();
                //调用目标对象的目标方法
                Object retValue = methodProxy.invokeSuper(target, objects);
                long end = System.currentTimeMillis();
                System.out.println(end-begin);
                return retValue;
            }
        });
        //创建代理对象,这一步会在内存中生成UserService类的子类并创建代理对象。
        UserService userServiceProxy = (UserService)enhancer.create();
        boolean admin = userServiceProxy.login("admin", "123");
        System.out.println(admin ? "登陆成功":"登陆失败");
        userServiceProxy.logout();
    }
}

十四、面向切面编程

1.概述

动态代理就是面向切面编程的一种实现。

AOP(Aspect Oriented Programming)面向切面编程:把业务逻辑代码看成纵向,将和业务逻辑无关的通用代码提取出来,形成一个横向的切面,以交叉业务的方式应用到业务流程的过程。

AOP是OOP(面向对象编程)的一个补充。

切面:程序中和业务逻辑无关的通用代码。

优点:代码可复用,易于维护,使开发者更专注于业务逻辑。

2.AOP的七大术语

1.连接点Joinpoint:在程序的整个执行流程中,可以织入切面的位置。(方法执行前后、异常抛出之后等位置)

2.切点Pointcut:在程序执行流程中,真正织入切面的方法。(一个切点对应多个连接点)

3.通知Advice:又称为增强,就是具体要织入的代码

通知又分为:

前置通知(只放到目标代码(切点)之前)

后置通知(只放到目标代码(切点)之后)

环绕通知(目标代码(切点)前后都有)

异常通知(放在了catch中的通知)

最终通知(放在finally中的通知)

4.切面Aspect:切点+通知

5.织入Weaving:把通知应用到目标对象上的过程。

6.代理对象Proxy:一个目标对象被织入通知后产生的新对象。

7.目标对象Target:被织入通知的对象。

3.切点表达式

切点表达式:定义通知往哪些方法上切入。

格式:

execution([访问控制权限修饰符] 返回值类型 [全限定类名]方法名(形参列表) [异常])

 访问控制权限修饰符:没写时代表4个权限都包括。

返回值类型:必填,*表示随意。

全限定类名:省略表示所有的类,..代表当前包以及子包下的所有类。

方法名:必填,*代表全部,delete*表示所有以delete开始的方法。

形参列表:..代表参数随意,*代表只有一个参数,*,String代表第一个参数随意,第二个参数是String。

异常:省略时表示任意异常。

4.使用Spring的AOP

方式一和二:Spring框架结合AspectJ框架,基于注解或XML

方式三:Spring框架自己实现的AOP,基于XML(用的少,基本不用)

AspectJ:Eclipse组织的一个支持AOP的框架。

方式一:基于注解

准备工作

需要引入spring-context,spring-aop,spring-aspects依赖。

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

Spring配置文件中要添加context和aop命名空间。

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xsi:schemaLocation="http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
                           http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd
                           http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">

</beans>

 在配置文件中进行组件扫描

<context:component-scan base-package="com.qiye.spring6.service"/>
实现步骤

目标类

@Service("userService")
public class UserService {//目标类
    public void login(){//目标方法
        System.out.println("登录~~");
    }
}

切面

@Before注解标注的方法就是一个前置通知,参数要写切点表达式

@Aspect注解标注的类就是一个切面

开启aspectj的自动代理,在扫描类的时候,会给带有@Aspect注解的类生成一个代理。proxy-target-class="true"代表强制使用CGLIB动态代理。

<!--    开启aspectj的自动代理-->
    <aop:aspectj-autoproxy proxy-target-class="true"/>
@Component("logAspect")
@Aspect
public class LogAspect {//切面
    //切面 = 通知 + 切点
    //通知(增强)Advice,@Before注解标注的方法就是一个前置通知,参数要写切点表达式。
    @Before("execution(* com.qiye.spring6.service.UserService.*(..))")
    public void beforeAdv(){
        System.out.println("我是通知,是增强代码~~");
    }
}

测试

  @Test
    public void testBefore(){
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.xml");
        UserService userService = applicationContext.getBean("userService", UserService.class);
        userService.login();
    }
其他的通知

@AfterReturning 后置通知

 //后置通知
    @AfterReturning("execution(* com.qiye.spring6.service.UserService.*(..))")
    public void afterReturningAdvice(){
        System.out.println("后置通知~~");
    }

@Around 环绕通知

环绕是范围最大的通知

 //环绕通知,需要一个连接点参数
    @Around("execution(* com.qiye.spring6.service.UserService.*(..))")
    public void aroundAdvice(ProceedingJoinPoint joinPoint) throws Throwable {
        //前面代码
        System.out.println("前环绕");
        //执行目标
        joinPoint.proceed();
        //后面代码
        System.out.println("后环绕");
    }

@AfterThrowing 异常通知

添加一个异常

@Service("userService")
public class UserService {//目标类
    public void login(){//目标方法
        System.out.println("登录~~");
        if(1 == 1){
            throw new RuntimeException("运行时异常");
        }
    }
}

@After 最终通知

 //最终通知
    @After("execution(* com.qiye.spring6.service.UserService.*(..))")
    public void afterAdvice(){
        System.out.println("最终通知");
    }

顺序:前环绕通知、前置通知、目标、后置通知、最终通知、后环绕通知

如果发生了异常:前环绕通知、前置通知、目标、异常通知、最终通知

切面执行顺序

可以在切面上添加@Order注解,参数为数字,数字小的先执行。

@Order(1) //数字越小,优先级越高
通用切点

我们可以定义一个通用切点的方法,提高代码复用性。

@Component("logAspect")
@Aspect
@Order(1)
public class LogAspect {//切面
    @Pointcut("execution(* com.qiye.spring6.service.UserService.*(..))")
    public void 通用切点(){
        //方法名随意,且方法体中无需写代码,在@Pointcut注解中定义通用的切点表达式即可
    }
    //通知(增强)Advice,@Before注解标注的方法就是一个前置通知,参数要写切点表达式。
    @Before("通用切点()")
    public void beforeAdv(){
        System.out.println("我是通知,是增强代码~~");
    }
    //后置通知
    @AfterReturning("通用切点()")
    public void afterReturningAdvice(){
        System.out.println("后置通知~~");
    }
    //环绕通知,需要一个连接点参数
    @Around("通用切点()")
    public void aroundAdvice(ProceedingJoinPoint joinPoint) throws Throwable {
        //前面代码
        System.out.println("前环绕");
        //执行目标
        joinPoint.proceed();
        //后面代码
        System.out.println("后环绕");
    }
    //异常通知
    @AfterThrowing("通用切点()")
    public void afterThrowingAdvice(){
        System.out.println("异常通知");
    }
    //最终通知
    @After("通用切点()")
    public void afterAdvice(){
        System.out.println("最终通知");
    }
}
关于连接点

除了环绕通知之外,别的通知也可以传入一个连接点参数JoinPoint joinPoint。

我们可以通过这个参数获取方法的签名。通过这个签名可以获取方法的一些具体信息。

        Signature signature = joinPoint.getSignature();
        System.out.println(signature);
全注解开发
@Configuration //代替spring.xml配置文件
@ComponentScan({"com.qiye.spring6.service"}) //配置扫描的包
@EnableAspectJAutoProxy(proxyTargetClass = true) //启用aspectj自动代理
public class Spring6Config {
}
  @Test
    public void TestAllAnnotation(){
        ApplicationContext applicationContext = new AnnotationConfigApplicationContext(Spring6Config.class);
        UserService userService = applicationContext.getBean("userService", UserService.class);
        userService.login();
    }

方式二:基于XML(了解)

public class UserService {//目标对象
    public void logout(){//目标方法
        System.out.println("系统正在安全退出~~~");
    }
}
public class TimerAspect {//切面
    //通知
    public void aroundAdvice(ProceedingJoinPoint joinPoint) throws Throwable {
        long begin = System.currentTimeMillis();
        joinPoint.proceed();
        long end = System.currentTimeMillis();
        System.out.println("耗时"+(end-begin)+"毫秒");
    }
}
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
                           http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
                           http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd">
<!--    纳入spring ioc-->
    <bean id="userService" class="com.qiye.spring.service.UserService"/>
    <bean id="timerAspect" class="com.qiye.spring.service.TimerAspect"/>
<!--    aop配置-->
    <aop:config>
<!--        切点表达式-->
        <aop:pointcut id="mypointcut" expression="execution(* com.qiye.spring.service..*(..))"/>
<!--        切面:通知+切点-->
        <aop:aspect ref="timerAspect">
            <aop:around method="aroundAdvice" pointcut-ref="mypointcut"/>
        </aop:aspect>
    </aop:config>

</beans>
   @Test
    public void testXML(){
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.xml");
        UserService userService = applicationContext.getBean("userService", UserService.class);
        userService.logout();
    }

5.实际案例:事务处理

编程式事务处理

@Service
public class AccountService {//目标对象
    public void transfer(){//转账的业务方法
        System.out.println("银行账户正在转账操作~~~");
    }
    public void withdraw(){//取款
        System.out.println("银行账户正在取款~~~");
    }
}
@Service
public class OrderService {//目标对象
    public void generate(){
        System.out.println("正在生成订单~~");
    }
    public void cancel(){
        System.out.println("订单已取消~~~");
    }
}
@Component
@Aspect
public class TransactionAspect {//切面
    @Around("execution(* com.qiye.spring.service..*(..))")
    public void aroundAdvice(ProceedingJoinPoint joinPoint){
        try {
            //前环绕
            System.out.println("开启事务");
            //执行目标
            joinPoint.proceed();
            //后环绕
            System.out.println("提交事务");
        } catch (Throwable e) {
            System.out.println("回滚事务");
        }
    }
}
@Configuration
@ComponentScan({"com.qiye.spring.service"})
@EnableAspectJAutoProxy(proxyTargetClass = true)
public class SpringConfig {
}
    @Test
    public void testAccount(){
        ApplicationContext applicationContext = new AnnotationConfigApplicationContext(SpringConfig.class);
        AccountService accountService = applicationContext.getBean("accountService", AccountService.class);
        OrderService orderService = applicationContext.getBean("orderService", OrderService.class);
        accountService.transfer();
        accountService.withdraw();
        orderService.cancel();
        orderService.generate();
    }

6.实际案例:安全日志

@Service
public class UserService {//目标对象
    public void saveUser(){
        System.out.println("新增~~");
    }
    public void deleteUser(){
        System.out.println("删除~~");
    }
}
@Service
public class VipService {//目标对象
    public void modifyVip(){
        System.out.println("修改");
    }
    public void getVip(){
        System.out.println("查找");
    }
}
@Component
@Aspect
public class SecurityLogAspect {//切面
    @Pointcut("execution(* com.qiye.spring.biz..save*(..))")
    public void savePointcut(){}
    @Pointcut("execution(* com.qiye.spring.biz..delete*(..))")
    public void deletePointcut(){}
    @Pointcut("execution(* com.qiye.spring.biz..modify*(..))")
    public void modifyPointcut(){}
    @Before("savePointcut() || deletePointcut() || modifyPointcut()")
    public void beforeAdvice(JoinPoint joinPoint){
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss SSS");
        String format = sdf.format(new Date());
        System.out.println(format + "。。。" + joinPoint.getSignature().getDeclaringTypeName() + "." + joinPoint.getSignature().getName());
    }
}
@Configuration
@ComponentScan({"com.qiye.spring.biz"})
@EnableAspectJAutoProxy(proxyTargetClass = true)
public class SprCon {
}

十五、Spring对事务的支持

1.回顾事务

事务transaction:多条DML要么同时成功,要么同时失败。

事务四个特性:ACID 原子性(事务是最小的工作单位,不可再分)、一致性(要么同时成功,要么同时失败)、隔离性(事务之间有隔离,保证互不干扰)、持久性(是事务结束的标志)。

2.Spring对事务的支持

Spring对事务的支持是基于AOP实现的。

采用声明式事务处理,基于注解或XML。

PlatformTransactionManager接口:事务管理器核心接口,在Spring6中有两个实现

DataSourceTransactionManager:支持jdbcTemplate,MyBatis,Hibernate等。

JtaTransactionManager:支持分布式事务管理。

注解实现

xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:tx="http://www.springframework.org/schema/tx"
       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
                           http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd">

    <context:component-scan base-package="com.qiye.bank"/>
<!--    配置数据源-->
    <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/spring6"/>
        <property name="username" value="root"/>
        <property name="password" value="1234"/>
    </bean>
<!--    配置jdbcTemplate-->
    <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
        <property name="dataSource" ref="dataSource"/>
    </bean>
<!--    配置事务管理器-->
    <bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource"/>
    </bean>
<!--    开启事务注解驱动器 -->
    <tx:annotation-driven transaction-manager="txManager"/>
</beans>

在要控制事务的类/方法上加@Transactional注解即可

@Transactional
@Service("accountService")
@Transactional
public class AccountServiceImpl implements AccountService {
    @Resource(name = "accountDao")
    private AccountDao accountDao;
    @Override
    public void transfer(String fromActno, String toActno, double money) {
        Account fromAct = accountDao.selectByActno(fromActno);
        if(fromAct.getBalance() < money){
            throw new RuntimeException("余额不足~~~");
        }
        Account toAct = accountDao.selectByActno(toActno);
        fromAct.setBalance(fromAct.getBalance() - money);
        toAct.setBalance(toAct.getBalance() + money);
        //数据库更新
        int count = accountDao.update(fromAct);
        int a = 1/0;
        count += accountDao.update(toAct);
        if(count != 2){
            throw new RuntimeException("转账失败!!");
        }

    }
}

@transactional的属性

传播行为

当有事务的方法A调用了有事务的方法B的时候,事务有不同的传播行为。

一共有七个传播行为,默认为REQUIRED。

REQUIRED:支持当前事务,不存在就新建一个。

propagation(传播,繁衍)

@Transactional(propagation = Propagation.REQUIRED)

SUPPORTS:支持当前事务,不存在就以非事务方式执行。

@Transactional(propagation = Propagation.SUPPORTS)

MANDATORY(强制性的):支持当前事务,不存在就抛出异常。

REQUIRES_NEW:开启一个新的事务,将存在的事务挂起。

NOT_SUPPORTED:不支持事务,如果存在事务则挂起。

NEVER:不支持事务,如果存在事务则抛出异常。

NESTED:有事务的话,在该事务中嵌套一个独立的事务,没有事务就和REQUIRED一样。

事务隔离级别

READ_UNCOMMITTED 读未提交

@Transactional(isolation = Isolation.READ_UNCOMMITTED)

READ_COMMITTED 读提交(Oracle数据库默认)

REPEATABLE_READ 可重复读(MySQL数据库默认)

SERIALIZABLE 序列化

事务超时

设置超时时间为10秒,表示超过10秒如果该事务中所有的DML语句还有没执行完毕的,最后会回滚。时间记录的是最后一条DML语句执行结束的时间。

@Transactional(timeout = 10)
只读事务

设置为只读事务的事务不能执行增删改操作。

作用:启动spring的优化策略,提高select的执行效率

@Transactional(realOnly = true)
设置哪些异常回滚事务

设置为异常是RuntimeException或其子类时,才会回滚

@Transactional(rollbackFor = RuntimeException.class)
设置哪些异常不回滚事务

设置异常为空指针异常时,不回滚。

@Transactional(noRollbackFor = NullPointerException.class)

3.事务的全注解开发

@Configuration //代替spring.xml配置文件
@ComponentScan("com.qiye.bank") //扫描
@EnableTransactionManagement  //开启事务注解驱动器
public class Spring6Config {
    //spring框架看到这个@Bean注解后,会调用这个方法,返回的对象会被纳入IoC容器管理。
    @Bean(name = "dataSource")
    public DruidDataSource getDruidDataSource(){
        DruidDataSource druidDataSource = new DruidDataSource();
        druidDataSource.setDriverClassName("com.mysql.cj.jdbc.Driver");
        druidDataSource.setUrl("jdbc:mysql://localhost:3306/spring6");
        druidDataSource.setUsername("root");
        druidDataSource.setPassword("1234");
        return druidDataSource;
    }
    @Bean(name = "jdbcTemplate")
    public JdbcTemplate getJdbcTemplate(DataSource dataSource){//spring在调用这个方法时,会自动传递过来dataSource对象
        JdbcTemplate jdbcTemplate = new JdbcTemplate();
        jdbcTemplate.setDataSource(dataSource);
        return  jdbcTemplate;
    }
    @Bean(name = "txManager")
    public DataSourceTransactionManager getDataSourceTransactionManager(DataSource dataSource){
        DataSourceTransactionManager dataSourceTransactionManager = new DataSourceTransactionManager();
        dataSourceTransactionManager.setDataSource(dataSource);
        return dataSourceTransactionManager;
    }
}
@Test
    public void testNoXML(){
        ApplicationContext applicationContext = new AnnotationConfigApplicationContext(Spring6Config.class);
        AccountService accountService = applicationContext.getBean("accountService", AccountService.class);
        accountService.transfer("act-001","act-002",10000);
    }

4.Spring对事务支持XML实现方式

需另外添加spring-aspects依赖。
 

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:tx="http://www.springframework.org/schema/tx"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
                           http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
                           http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd
                           http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd">

    <context:component-scan base-package="com.qiye.bank"/>
<!--    配置数据源-->
    <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/spring6"/>
        <property name="username" value="root"/>
        <property name="password" value="20040709qsx"/>
    </bean>
<!--    配置jdbcTemplate-->
    <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
        <property name="dataSource" ref="dataSource"/>
    </bean>
<!--    配置事务管理器-->
    <bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource"/>
    </bean>
<!--配置通知-->
    <tx:advice id="txAdvice"  transaction-manager="txManager">
<!--        配置通知的相关属性-->
        <tx:attributes>
            <tx:method name="transfer" propagation="REQUIRED" rollback-for="java.lang.Throwable"/>
        </tx:attributes>
    </tx:advice>
<!--配置切面-->
    <aop:config>
<!--        切点-->
        <aop:pointcut id="txPointcut" expression="execution(* com.qiye.bank.service..*(..))"/>
<!--        切面 通知+切点-->
        <aop:advisor advice-ref="txAdvice" pointcut-ref="txPointcut"/>
    </aop:config>
</beans>

十六、Spring6整合JUnit5

spring对JUnit4的支持

  <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-test</artifactId>
            <version>6.2.5</version>
        </dependency>
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:spring.xml")
public class TestSpringTX {
    @Autowired
    private AccountService accountService;
    @Test
    public void testSpringTX(){
        try{
            accountService.transfer("act-001","act-002",10000);
            System.out.println("转账成功");
        }catch (Exception e){
            e.printStackTrace();
        }
    }
}

spring对JUnit5的支持

 <dependency>
            <groupId>org.junit.jupiter</groupId>
            <artifactId>junit-jupiter</artifactId>
            <version>5.11.4</version>
            <scope>test</scope>
        </dependency>
@ExtendWith(SpringExtension.class)
@ContextConfiguration("classpath:spring.xml")
public class TestSpringTX {
    @Autowired
    private AccountService accountService;
    @Test
    public void testSpringTX(){
        try{
            accountService.transfer("act-001","act-002",10000);
            System.out.println("转账成功");
        }catch (Exception e){
            e.printStackTrace();
        }
    }
}

十七、Spring6集成MyBatis

需要spring-context spring-jdbc mysql驱动 mybatis mybatis-spring 德鲁伊连接池 junit依赖。

AccountMapper

public interface AccountMapper {//实现类是动态生成的
    int insert(Account account);//增
    int deleteByActno(String actno);//删
    int update(Account account);//改
    Account selectByActno(String actno);//查一个
    List<Account> selectAll();//查所有
}

AccountMapper.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.qiye.bank.mapper.AccountMapper">
    <insert id="insert">
        insert into t_act values(#{actno},#{balance})
    </insert>
    <delete id="deleteByActno">
        delete from t_act where actno = #{actno}
    </delete>
    <update id="update">
        update t_act set balance = #{balance} where actno = #{actno}
    </update>
    <select id="selectByActno" resultType="Account">
        select * from t_act where actno = #{actno}
    </select>
    <select id="selectAll" resultType="Account">
        select * from t_act
    </select>
</mapper>

AccountService

public interface AccountService {
    /**
     * 保存账户
     * @param act
     * @return
     */
    int save(Account act);

    /**
     * 删除账户
     * @param actno
     * @return
     */
    int deleteByActno(String actno);

    /**
     * 修改账户
     * @param account
     * @return
     */
    int modify(Account account);

    /**
     * 查询账户
     * @param actno
     * @return
     */
    Account getByActno(String actno);

    /**
     * 获取所有账户
     * @return
     */
    List<Account> getAll();

    /**
     * 转账
     * @param fromActno
     * @param toActno
     * @param money
     */
    void transfer(String fromActno,String toActno,double money);
}

AccountServiceImpl

@Service("accountService")
@Transactional
public class AccountServiceImpl implements AccountService {
    @Autowired
    private AccountMapper accountMapper;
    @Override
    public int save(Account act) {
        return accountMapper.insert(act);
    }

    @Override
    public int deleteByActno(String actno) {
        return accountMapper.deleteByActno(actno);
    }

    @Override
    public int modify(Account account) {
        return accountMapper.update(account);
    }

    @Override
    public Account getByActno(String actno) {
        return accountMapper.selectByActno(actno);
    }

    @Override
    public List<Account> getAll() {
        return accountMapper.selectAll();
    }

    @Override
    public void transfer(String fromActno, String toActno, double money) {
        Account fromAct = accountMapper.selectByActno(fromActno);
        if(fromAct.getBalance()< money){
            throw new RuntimeException("余额不足");
        }
        Account toAct = accountMapper.selectByActno(toActno);
        fromAct.setBalance(fromAct.getBalance() - money);
        toAct.setBalance(toAct.getBalance()+money);
        int count = accountMapper.update(fromAct);
        count += accountMapper.update(toAct);
        if(count != 2){
            throw new RuntimeException("转账失败");
        }
    }
}

mybatis.config

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
        PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<!--    日志信息    -->
    <settings>
        <setting name="logImpl" value="STDOUT_LOGGING"/>
    </settings>
</configuration>

spring.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context" xmlns:tx="http://www.springframework.org/schema/tx"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd">
<!--    组件扫描-->
    <context:component-scan base-package="com.qiye.bank"/>
<!--    引入外部属性配置文件-->
    <context:property-placeholder location="jdbc.properties"/>
<!--    数据源-->
    <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
        <property name="driverClassName" value="${jdbbc.driver}"/>
        <property name="url" value="${jdbc.url}"/>
        <property name="username" value="${jdbc.username}"/>
        <property name="password" value="${jdbc.password}"/>
    </bean>
<!--    SQLSessionFactoryBean-->
    <bean class="org.mybatis.spring.SqlSessionFactoryBean">
<!--        注入数据源 -->
        <property name="dataSource" ref="dataSource"/>
<!--        指定mybatis核心配置文件-->
        <property name="configLocation" value="mybatis-config.xml"/>
        <!--指定别名-->
        <property name="typeAliasesPackage" value="com.qiye.bank.pojo"/>
    </bean>
<!--    Mapper扫描配置器,生成代理类-->
    <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
        <property name="basePackage" value="com.qiye.bank.mapper"/>
    </bean>
<!--    事务管理器-->
    <bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource"/>
    </bean>
<!--    启动事务注解-->
    <tx:annotation-driven transaction-manager="txManager"/>
</beans>

十八、Spring中的八大模式

简单工厂模式

工厂方法模式

单例模式:双重锁解决循环依赖

代理模式

装饰器模式

观察者模式

策略模式

模板方法模式

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值