核心内容

概念

Spring几个重要概念

1.Spring可以整合其他的框架(解读: Spring是管理框架的框架)

2.Spring有两个核心的概念: IOC 和 AOP

3.IOC [Inversion Of Control反转控制]

传统的开发模式[JdbcUtils/反射]程序------>环境//程序读取环境配置,然后自己创建对象.

解读

配置Been.xml文件

public class SpringBeenstest {

    @Test
    public void getMonster() {
        // 创建容器
        ApplicationContext ioc =
                new ClassPathXmlApplicationContext("beans.xml");

        Object monster01 = ioc.getBean("monster01");
        System.out.println(monster01);

    }

}

<?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="com.zfc.been.Monster" id="monster01">
          <property name="Monsterid" value="100"/>
           <property name="name" value="黑悟空"/>
           <property name="skill" value="喷火"/>
       </bean>
</beans>

手动实现 Spring底层

package com.zfc.spring.hspapplicationcontext;

import com.hspedu.spring.bean.Monster;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;

import java.io.File;
import java.lang.reflect.Method;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;

/**
 * @author zfc
 * @version 1.0
 * 解读
 * 1. 这个程序用于实现Spring的一个简单容器机制
 * 2. 后面我们还会详细的实现
 * 3. 这里我们实现如何将beans.xml文件进行解析,并生成对象,放入容器中
 * 4. 提供一个方法 getBean(id) 返回对应的对象
 * 5. 这里就是一个开胃小点心, 理解Spring容器的机制
 */
public class ZfcApplicationContext {

    private ConcurrentHashMap<String, Object> singletonObjects =
            new ConcurrentHashMap<>();


    //构造器
    //接收一个容器的配置文件 比如 beans.xml, 该文件默认在src
    public ZfcApplicationContext(String iocBeanXmlFile) throws Exception {

        //1. 得到类加载路径
        String path = this.getClass().getResource("/").getPath();

        //2. 创建 Saxreader
        SAXReader saxReader = new SAXReader();

        //3. 得到Document对象
        Document document =
                saxReader.read(new File(path + iocBeanXmlFile));

        //4. 得到rootDocument
        Element rootElement = document.getRootElement();

        //5. 得到第一个bean-monster01
        Element bean = (Element) rootElement.elements("bean").get(0);

        //6. 获取到第一个bean-monster01的相关属性
        String id = bean.attributeValue("id");
        String classFullPath = bean.attributeValue("class");
        List<Element> property = bean.elements("property");
        //遍历->老师简化直接获取
        Integer monsterId =
                Integer.parseInt(property.get(0).attributeValue("value"));

        String name = property.get(1).attributeValue("value");
        String skill = property.get(2).attributeValue("value");

        //7. 使用反射创建对象.=> 回顾反射机制
        Class<?> aClass = Class.forName(classFullPath);
        //这里o对象就是Monster对象
        Monster o = (Monster) aClass.newInstance();
        //给o对象赋值
        //反射来赋值=> 这里老师就简化,直接赋值->目的就是先理解流程
        //这里的方法就是setter方法
        //Method[] declaredMethods = aClass.getDeclaredMethods();
        //for (Method declaredMethod : declaredMethods) {
        //    declaredMethod.invoke();
        //}
        //赋值
        o.setMonsterId(monsterId);
        o.setName(name);
        o.setSkill(skill);

        //8. 将创建好的对象放入到singletonObjects
        singletonObjects.put(id, o);

    }

    public Object getBean(String id) {
        //这里小伙伴可以在处理
        return singletonObjects.get(id);
    }
}

package com.zfc.spring.hspapplicationcontext;

import com.Zfc.spring.bean.Monster;

/**
 * @author zfc
 * @version 1.0
 */
public class ZfcApplicationContextTest {
    public static void main(String[] args) throws Exception {

        ZfcApplicationContext ioc =
                new ZfcApplicationContext("beans.xml");

        Monster monster01 = (Monster)ioc.getBean("monster01");

        System.out.println("monter01=" + monster01);
        System.out.println("monster01.name=" + monster01.getName());
        System.out.println("ok");
    }
}

Spring 配置/管理bean

按照类型来获取 直接传入bean.class对象

通过构造器配置Bean

可以通过类型来配置

通过P名称空间来分配Bean对象

通过ref来实现

直接配置内部Bean对象

通过list配置

通过Map对象赋值

用Set属性赋值

通过Array赋值

通过Properties进行配置

通过utils名称空间来创建List集合

属性级联赋值

通过静态工厂获取

通过实例工厂获取

通过FactoryBean获取Bean

Bean配置信息重用

Bean创建顺序

Bean单例和多实例

细节

Bean的生命周期

Bean的后置处理器(重要!!!)

package com.zfc.spring.bean;

import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;

/**
 * @author zfc
 * @version 1.0
 * 这是一个后置处理器, 需要实现 BeanPostProcessor接口
 */
public class MyBeanPostProcessor implements BeanPostProcessor {

    /**
     * 什么时候被调用: 在Bean的init方法前被调用
     * @param bean 传入的在IOC容器中创建/配置Bean
     * @param beanName 传入的在IOC容器中创建/配置Bean的id
     * @return Object 程序员对传入的bean 进行修改/处理【如果有需要的话】 ,返回
     * @throws BeansException
     */

    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        System.out.println("postProcessBeforeInitialization().. bean="
                + bean + " beanName=" + beanName);

        //初步体验案例: 如果类型是House的统一改成 上海豪宅
        //对多个对象进行处理/编程==>切面编程
        if(bean instanceof House) {
            ((House)bean).setName("上海豪宅~");
        }
        return null;
    }

    /**
     * 什么时候被调用: 在Bean的init方法后被调用
     * @param bean  传入的在IOC容器中创建/配置Bean
     * @param beanName 传入的在IOC容器中创建/配置Bean的id
     * @return 程序员对传入的bean 进行修改/处理【如果有需要的话】 ,返回
     * @throws BeansException
     */

    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        System.out.println("postProcessAfterInitialization().. bean="
                + bean + " beanName=" + beanName);
        return bean;
    }
}

AOP(Aspect-Oriented Programming,面向切面编程)和OOP(Object-Oriented Programming,面向对象编程)是两种不同的编程范式,它们在设计和实现软件系统时具有不同的侧重点和应用场景。

OOP(面向对象编程)

面向对象编程是一种编程范式,通过将数据和行为封装在对象中来组织代码。OOP的核心概念包括:

1.对象:对象是类的实例,包含数据(属性)和方法(行为)。

2.类:类是对象的蓝图,定义了对象的属性和方法。

3.继承:允许一个类继承另一个类的属性和方法,促进代码重用。

4.多态:同一方法可以在不同对象上表现出不同的行为,增强代码的灵活性。

5.封装:将对象的内部状态与外部世界隔离,只允许通过公共方法访问和修改对象的状态。

AOP(面向切面编程)

面向切面编程是一种编程范式,旨在通过将横切关注点(cross-cutting concerns)从业务逻辑中分离出来来改善代码的模块化。AOP的核心概念包括:

6.切面(Aspect):定义横切关注点的模块,可以在不同的地方应用,例如日志、安全、事务管理等。

7.连接点(Join Point):程序执行的特定点,比如方法调用、对象实例化等,切面可以在这些点上进行操作。

8.通知(Advice):在连接点上执行的代码,可以在连接点之前、之后或异常时执行。

9.切入点(Pointcut):定义哪些连接点会被切面应用,可以通过表达式指定。

10.织入(Weaving):将切面与主程序代码结合的过程,可以在编译时、类加载时或运行时进行。

AOP 和 OOP 的区别

| 特性 | OOP | AOP |

|-----------------------|-----------------------------------------|---------------------------------------- - |

| 关注点 | 主要关注对象及其行为 | 主要关注横切关注点(如日志、安全等) |

| 模块化方式 | 通过类和对象封装 | 通过切面分离横切关注点 |

| 代码重用 | 通过继承和组合 | 通过切面复用逻辑 |

| 设计目的 | 封装数据和行为,促进重用和维护 | 解耦关注点,增强模块化和可维护性 |

| 处理方式 | 在类内部定义行为 | 在运行时或编译时动态插入行为 |

何时使用 AOP?

11.跨切关注点:需要对日志、安全、事务、缓存等逻辑进行统一管理。

12.代码复用:希望在多个模块中复用相同的逻辑,而不需要在每个模块中重复代码。

13.降低耦合度:希望将横切关注点与核心业务逻辑分离,从而提高系统的可维护性。

结合使用

AOP 和 OOP 是互补的,通常在实际项目中可以结合使用。OOP 适合实现核心业务逻辑,而 AOP 可以帮助处理与业务逻辑无关但又重要的关注点。通过这样的结合,开发者能够编写更加清晰、可维护和可扩展的代码

通过属性文件配置Bean

自动装配

基于注解配置Bean(重点!!!!!!)

注意事项和细节

在Spring框架中,@Controller、@Service和@Component都是用来标注类的注解,它们都属于Spring的依赖注入(DI)和控制反转(IoC)机制的一部分。虽然它们都可以用来将类注册为Spring容器中的Bean,但它们有不同的语义和用途。以下是它们的主要区别:

1. @Controller

1.用途:

2.@Controller用于标注控制层的组件,通常用于处理HTTP请求和响应。

3.它是Spring MVC框架的一部分,主要用于Web应用程序。

4.特点:

5.用于定义控制器类,这些类处理用户的请求并返回视图(例如,HTML页面)。

6.结合@RequestMapping等注解来处理特定的URL请求。

7.示例:

  @Controller

  public class MyController {

      @RequestMapping("/hello")

      public String sayHello() {

          return "hello"; // 返回视图名称

      }

  }

2. @Service

8.用途:

9.@Service用于标注服务层的组件,通常用于实现业务逻辑。

10.特点:

11.它标识的是服务类,这些类包含核心业务逻辑,通常与数据访问层(Repository)交互。

12.Spring会处理这些类的事务管理(Transaction Management)等。

13.示例:

  @Service

  public class UserService {

      public User getUserById(Long id) {

          // 业务逻辑

      }

  }

3. @Component

14.用途:

15.@Component是一个通用的组件注解,用于标识一个类是一个Spring管理的Bean。

16.特点:

17.可以用于任何类,它是最基本的构造,除了特殊的功能注解(如@Controller和@Service)之外的所有组件都可以使用它。

18.适用于不符合其他类型(如Controller或Service)的任何类。

19.示例:

  @Component

  public class UtilityComponent {

      public void doSomething() {

          // 工具类功能

      }

  }

总结

20.职责:

21.@Controller: 处理HTTP请求,返回视图。

22.@Service: 实现业务逻辑,处理与数据层的交互。

23.@Component: 用于任何普通的Spring Bean。

24.使用场景:

25.使用@Controller来定义Web控制器,使用@Service来定义业务逻辑,使用@Component来标识任何普通组件。

选择建议

通常情况下,建议在合适的层使用相应的注解,以提高代码的可读性和可维护性。即使@Service和@Component的功能相似,使用特定的注解可以帮助其他开发者快速理解类的意图和用途。

手动实现IOC容器

package com.zfcedu.spring.annotation;

import com.zfcedu.spring.zfcapplicationcontext.zfcApplicationContext;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Controller;
import org.springframework.stereotype.Repository;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;

import java.io.File;
import java.lang.annotation.Annotation;
import java.net.URL;
import java.util.concurrent.ConcurrentHashMap;

/**
 * @author zfc
 * @version 1.0
 * zfcSpringApplicationContext 类的作用类似Spring原生ioc容器
 */
public class zfcSpringApplicationContext {
    private Class configClass;
    //ioc我存放的就是通过反射创建的对象(基于注解方式)
    private final ConcurrentHashMap<String, Object> ioc =
            new ConcurrentHashMap<>();

    //构造器
    public zfcSpringApplicationContext(Class configClass) {

        this.configClass = configClass;
        //System.out.println("this.configClass=" + this.configClass);
        //获取要扫描的包
        //1. 先得到zfcSpringConfig配置的的@ComponentScan(value = "com.zfcedu.spring.component")
        ComponentScan componentScan =
                (ComponentScan) this.configClass.getDeclaredAnnotation(ComponentScan.class);
        //2. 通过componentScan的value=> 即要扫描的包
        String path = componentScan.value();
        System.out.println("要扫描的包= " + path);

        //得到要扫描的包下的所有资源(类 .class)
        //1.得到类的加载器
        ClassLoader classLoader =
                zfcApplicationContext.class.getClassLoader();
        //2. 通过类的加载器获取到要扫描的包的资源 url=》类似一个路径
        path = path.replace(".", "/");//一定要把. 替换成 /
        URL resource =
                classLoader.getResource(path);
        System.out.println("resource=" + resource);
        //3. 将要加载的资源(.class) 路径下的文件进行遍历=>io
        File file = new File(resource.getFile());
        if (file.isDirectory()) {
            File[] files = file.listFiles();
            for (File f : files) {
                System.out.println("=====================");
                System.out.println("=" + f.getAbsolutePath());
                //D:\zfcedu_spring\spring\out\production\spring\com\zfcedu\spring\component\UserService.class
                //获取到 com.zfcedu.spring.component.UserService
                String fileAbsolutePath = f.getAbsolutePath();

                //这里我们只处理.class文件
                if (fileAbsolutePath.endsWith(".class")) {

                    //1. 获取到类名
                    String className =
                            fileAbsolutePath.substring(fileAbsolutePath.lastIndexOf("\\") + 1, fileAbsolutePath.indexOf(".class"));
                    //System.out.println("className=" + className);
                    //2. 获取类的完整的路径(全类名)
                    //老师解读 path.replace("/",".") => com.zfcedu.spring.component.
                    String classFullName = path.replace("/", ".") + "." + className;
                    //System.out.println("classFullName=" + classFullName);

                    //3. 判断该类是不是需要注入容器, 就看该类是不是有注解 @Component @Service..
                    try {
                        //这时,我们就得到该类的Class对象
                        //Class clazz = Class.forName(classFullName)
                        //说一下
                        //1. Class clazz = Class.forName(classFullName) 可以反射加载类
                        //2. classLoader.loadClass(classFullName); 可以反射类的Class
                        //3. 区别是 : 上面方式后调用来类的静态方法, 下面方法不会
                        //4. aClass.isAnnotationPresent(Component.class) 判断该类是否有 @Component
                        Class<?> aClass = classLoader.loadClass(classFullName);
                        if (aClass.isAnnotationPresent(Component.class) ||
                                aClass.isAnnotationPresent(Controller.class) ||
                                aClass.isAnnotationPresent(Service.class) ||
                                aClass.isAnnotationPresent(Repository.class)) {

                            //这里老师演示一个Component注解指定value,分配id
                            //老师就是演示了一下机制.
                            if(aClass.isAnnotationPresent(Component.class)) {
                                //获取到该注解
                                Component component = aClass.getDeclaredAnnotation(Component.class);
                                String id = component.value();
                                if(!"".endsWith(id)) {
                                    className = id;//替换
                                }
                            }

                            //这时就可以反射对象,并放入到容器中
                            Class<?> clazz = Class.forName(classFullName);
                            Object instance = clazz.newInstance();
                            //放入到容器中, 将类名的首字母小写作为id
                            //StringUtils

                            ioc.put(StringUtils.uncapitalize(className) , instance);
                        }
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                }
            }
        }

    }

    //编写方法返回对容器中对象
    public Object getBean(String name) {
        return ioc.get(name);
    }
}

package com.zfc.Annotation.componet;



import org.springframework.context.annotation.ComponentScan;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Controller;
import org.springframework.stereotype.Repository;
import org.springframework.stereotype.Service;

import java.io.File;
import java.net.URL;
import java.util.Arrays;
import java.util.concurrent.ConcurrentHashMap;

/**
 * @author zfc
 * @version 1.0
 */

@SuppressWarnings({"all"})
public class ZfcSpringApplicationContext {
    // 定义一个类属性
    private Class configClass;
    // 创建一个concurrentHashMap来存放注解信息
    private static ConcurrentHashMap<String, Object> ioc = new ConcurrentHashMap<>();


    // 构造器
    public ZfcSpringApplicationContext(Class configClass) {

        this.configClass = configClass;

        // 获取要扫描的包

        ComponentScan componentScan =
                (ComponentScan) this.configClass.getDeclaredAnnotation(ComponentScan.class);
        System.out.println("componentScan = " + componentScan);
        String[] path = componentScan.value();
        System.out.println("要扫描的包=" + Arrays.toString(path));
//        要扫描的包=[com.zfc.Annotation.componet]


        // 先得到类加载器
        ClassLoader classLoader = ZfcSpringApplicationContext.class.getClassLoader();
        // 扫描包下面的类
        for (String p : path) {
            String packagePath = p.replace(".", "/");
            System.out.println("packagePath:" + packagePath);
            // packagePath:com/zfc/Annotation/componet
            // 扫描包下面的类
            URL resource = classLoader.getResource(packagePath);
            System.out.println("resource=" + resource);
            // resource=file:/D:/javacode/mavenproject/maven_java/spring/target/classes/com/zfc/Annotation/componet
            File file = new File(resource.getFile());
            if(file.isDirectory()) {
                File[] files = file.listFiles();
                for (File f : files) {
                    System.out.println("=====================");

                    String fileAbsolutePath = f.getAbsolutePath();
                    if (fileAbsolutePath.endsWith(".class")) {
                        // 获取到类名
                        String ClassName = fileAbsolutePath.substring(fileAbsolutePath.lastIndexOf("\\") + 1,
                                fileAbsolutePath.indexOf(".class"));
                        System.out.println("ClassName=" + ClassName);

                        String classFullName = p.replace("/", ".") + "." + ClassName;

                        System.out.println("classFullName=" + classFullName);

                        // System.out.println("=" + f.getAbsolutePath());
//                    =====================
//                    ClassName=User
//                    classFullName=com.zfc.Annotation.componet.User
//                            =====================
//                    ClassName=UserAction
//                    classFullName=com.zfc.Annotation.componet.UserAction
//                            =====================
//                    ClassName=UserDAO
//                    classFullName=com.zfc.Annotation.componet.UserDAO
//                            =====================
//                    ClassName=UserServet
//                    classFullName=com.zfc.Annotation.componet.UserServet
//                            =====================
//                    ClassName=ZfcSpringApplicationContext
//                    classFullName=com.zfc.Annotation.componet.ZfcSpringApplicationContext
//                            =====================
//                    ClassName=ZfcSpringConfig
//                    classFullName=com.zfc.Annotation.componet.ZfcSpringConfig
                        // 反射获取类对象
                        try {
                            Class<?> aClass = classLoader.loadClass(classFullName);

                            if(aClass.isAnnotationPresent(Component.class) ||
                              aClass.isAnnotationPresent(Controller.class) ||
                              aClass.isAnnotationPresent(Service.class) ||
                              aClass.isAnnotationPresent(Repository.class)
                            ){
                                // 保存到IOC容器中
                                ioc.put(ClassName, aClass.newInstance());
                            };

                        } catch (Exception e) {
                            throw new RuntimeException(e);
                        }
                    }
                }
            }
        }
    }
    public Object getBean(String name) {
        return ioc.get(name);
    }
}

package com.zfc.Annotation;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * @author zfc
 * @version 1.0
 * 这是一个扫描类注解
 *  * 1. @Target(ElementType.TYPE)指定我们的ComponentScan注解可以修饰 Type程序元素
 *  * 2. @Retention(RetentionPolicy.RUNTIME) 指定ComponentScan注解 保留范围
 *  * 3. String value() default ""; 表示ComponentScan 可以传入 value
 */
@SuppressWarnings({"all"})
// 添加自定义注解的可作用的范围
@Target(value = {ElementType.TYPE})
@Retention(value = RetentionPolicy.RUNTIME)
public @interface ComponetScan {
    String value() default "";
}

package com.zfc.Annotation.componet;

import com.zfc.Annotation.ComponetScan;
import org.springframework.context.annotation.ComponentScan;

/**
 * @author zfc
 * @version 1.0
 */


@ComponentScan(value = "com.zfc.Annotation.componet")
public class ZfcSpringConfig {
    // 该类用于识别一个注解
}

基于注解自动装配bean

泛型依赖注入

基本说明

1.为了更好的管理有继承和相互依赖的bean的自动装配,spring还提供基于泛型依赖的注入机制

2.在继承关系复杂情况下,泛型依赖注入就会有很大的优越性

AOP

横切关注点

注意事项

动态代理

手动实现AOP

package com.zfc.spring.proxy2;



import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

/**
 * @author zfc
 * @version 1.0
 * VehicleProxyProvider 该类可以返回一个代理对象.
 */
public class VehicleProxyProvider {

    //定义一个属性
    //target_vehicle 表示真正要执行的对象
    //该对象实现了Vehicle接口
    private Vehicle target_vehicle;

    //构造器
    public VehicleProxyProvider(Vehicle target_vehicle) {
        this.target_vehicle = target_vehicle;
    }

    //编写一个方法,可以返回一个代理对象, 该代理对象可以通过反射机制调用到被代理对象的方法
    //解读
    //1. 这个方法非常重要, 理解有一定难度
    public Vehicle getProxy() {

        //得到类加载器
        ClassLoader classLoader =
                target_vehicle.getClass().getClassLoader();

        //得到要代理的对象/被执行对象 的接口信息,底层是通过接口来完成调用
        Class<?>[] interfaces = target_vehicle.getClass().getInterfaces();


        //创建InvocationHandler 对象
        //因为 InvocationHandler 是接口,所以我们可以通过匿名对象的方式来创建该对象
        /**
         *
         * public interface InvocationHandler {
         *  public Object invoke(Object proxy, Method method, Object[] args)
         *         throws Throwable;
         * }
         * invoke 方法是将来执行我们的target_vehicle的方法时,会调用到
         *
         */

        InvocationHandler invocationHandler = new InvocationHandler() {
            /**
             * invoke 方法是将来执行我们的target_vehicle的方法时,会调用到
             * @param o 表示代理对象
             * @param method 就是通过代理对象调用方法时,的哪个方法 代理对象.run()
             * @param args : 表示调用 代理对象.run(xx) 传入的参数
             * @return 表示 代理对象.run(xx) 执行后的结果.
             * @throws Throwable
             */
            @Override
            public Object invoke(Object o, Method method, Object[] args)
                    throws Throwable {

                System.out.println("交通工具开始运行了....");
                //这里是我们的反射基础 => OOP
                //method 是?: public abstract void com.zfc.spring.proxy2.Vehicle.run()
                //target_vehicle 是? Ship对象
                //args 是null
                //这里通过反射+动态绑定机制,就会执行到被代理对象的方法
                //执行完毕就返回
                Object result = method.invoke(target_vehicle, args);
                System.out.println("交通工具停止运行了....");
                return result;
            }
        };

        /*

          public static Object newProxyInstance(ClassLoader loader,
                                          Class<?>[] interfaces,
                                          InvocationHandler h)

          解读
          1. Proxy.newProxyInstance() 可以返回一个代理对象
          2. ClassLoader loader: 类的加载器.
          3. Class<?>[] interfaces 就是将来要代理的对象的接口信息
          4. InvocationHandler h 调用处理器/对象 有一个非常重要的方法invoke
         */
        Vehicle proxy =
                (Vehicle)Proxy.newProxyInstance(classLoader, interfaces, invocationHandler);

        return proxy;
    }
}

Test

package com.zfc.spring.proxy2;

import org.junit.jupiter.api.Test;

/**
 * @author zfc
 * @version 1.0
 */
public class TestVehicle {

    @Test
    public void run() {
        //OOP基础=>java基础
        Vehicle vehicle = new Ship();
        //动态绑定
        vehicle.run();
    }

    @Test
    public void proxyRun() {
        //创建Ship对象
        Vehicle vehicle = new Car();

        //创建VehicleProxyProvider对象, 并且我们传入的要代理的对象
        VehicleProxyProvider vehicleProxyProvider =
                new VehicleProxyProvider(vehicle);

        //获取代理对象, 该对象可以代理执行方法
        //解读
        //1. porxy 编译类型 Vehicle
        //2. 运行类型 是代理类型 class com.sun.proxy.$Proxy9

        Vehicle proxy = vehicleProxyProvider.getProxy();

        System.out.println("proxy的编译类型是 Vehicle");
        System.out.println("proxy的运行类型是 " + proxy.getClass());
        //下面就要给大家解读/debug怎么 执行到 代理对象的 public Object invoke(Object o, Method method, Object[] args)
        //梳理完毕. proxy的编译类型是 Vehicle, 运行类型是 class com.sun.proxy.$Proxy9
        //所以当执行run方法时,会执行到 代理对象的invoke
        //如何体现动态 [1. 被代理的对象 2. 方法]
        //proxy.run();
        String result = proxy.fly(10000);
        System.out.println("result=" + result);
    }
}

切入点表达式的具体使用

注意细节

1.为什么要使用动态代理?

动态代理:在不改变原有代码的情况下上进行对象功能增强 使用代理对象代替原来的对象完成功能 进而达到拓展功能的目

2.JDK Proxy 动态优理面向接口的动态代理 :特点:

1.一定要有接口和实现类的存在 代理对象增强的是实现类 在实现接口的方法重写的方法

2.生成的代理对象只能转换成 接口的不能转换成 被代理类

3.代理对象只能增强接口中定义的方法 实现类中其他和接口无关的方法是无法增强的

4.代理对象只能读取到接口中方法上的注解 不能读取到实现类方法上的注解使用方法:

cglib动态代理模式是面向父类

特点:

1.面向父类的和接口没有直接关系

2.不仅可以增强接口中定义的方法还可以增强其他方法

3.可以读取父类中方法上的所有注解

两个动态代理的区别

1.JDK动态代理是面向接口的,只能增强实现类中接口中存在的方法。CGlib是面向父类的,可以增强父类的所有方法2.JDK得到的对象是JDK代理对象实例,而CGlib得到的对象是被代理对象的子类

JoinPoint

    //后置通知
    @After(value = "execution(public void Car.run())")
    public void ok4(JoinPoint joinPoint) {


        Signature signature = joinPoint.getSignature();
        System.out.println("切面类的ok4()-执行的目标方法-" + signature.getName());
        //演示一下JoinPoint常用的方法.
        joinPoint.getSignature().getName(); // 获取目标方法名
        joinPoint.getSignature().getDeclaringType().getSimpleName(); // 获取目标方法所属类的简单类名
        joinPoint.getSignature().getDeclaringTypeName(); // 获取目标方法所属类的类名
        joinPoint.getSignature().getModifiers(); // 获取目标方法声明类型(public、private、protected)
        Object[] args = joinPoint.getArgs(); // 获取传入目标方法的参数,返回一个数组
        joinPoint.getTarget(); // 获取被代理的对象
        joinPoint.getThis(); // 获取代理对象自己
    }

拿到返回的结果

    @AfterReturning(value = "myPointCut()", returning = "res")
    public void showSuccessEndLog(JoinPoint joinPoint, Object res) {
        Signature signature = joinPoint.getSignature();
        System.out.println("SmartAnimalAspect-切面类showSuccessEndLog()-方法执行正常结束-日志-方法名-" + signature.getName() + " 返回的结果是=" + res);
    }

捕获异常信息

    //异常通知:即把showExceptionLog方法切入到目标对象方法执行发生异常的的catch{}
    //@AfterThrowing(value = "execution(public float com.hspedu.spring.aop.aspectj.SmartDog.getSum(float, float))", throwing = "throwable")
    //直接使用切入点表达式
    @AfterThrowing(value = "myPointCut()", throwing = "throwable")
    public void showExceptionLog(JoinPoint joinPoint, Throwable throwable) {
        Signature signature = joinPoint.getSignature();
        System.out.println("SmartAnimalAspect-切面类showExceptionLog()-方法执行异常-日志-方法名-" + signature.getName() + " 异常信息=" + throwable);

获取最终通知

    //最终通知:即把showFinallyEndLog方法切入到目标方法执行后(不管是否发生异常,都要执行 finally{})
    //@After(value = "execution(public float com.hspedu.spring.aop.aspectj.SmartDog.getSum(float, float))")
    //直接使用切入点
    @After(value = "myPointCut()")
    public void showFinallyEndLog(JoinPoint joinPoint) {
        Signature signature = joinPoint.getSignature();
        System.out.println("SmartAnimalAspect-切面类showFinallyEndLog()-方法最终执行完毕-日志-方法名-" + signature.getName());

环绕通知

package com.zfc.spring.aop.aspectj;


import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;

import java.util.Arrays;
import java.util.List;

/**
 * @author zfc
 * @version 1.0
 * 切面类 , 类似于我们以前自己写的MyProxyProvider,但是功能强大很多
 */
//@Aspect //表示是一个切面类[底层切面编程的支撑(动态代理+反射+动态绑定...)]
//@Component //会注入SmartAnimalAspect2到容器
public class SmartAnimalAspect2 {

    //演示环绕通知的使用-了解
    //老师解读
    //1. @Around: 表示这是一个环绕通知[完成其它四个通知的功能]
    //2. value = "execution(public float com.hspedu.spring.aop.aspectj.SmartDog.getSum(float, float)) 切入点表达式
    //3. doAround 表示要切入的方法 - 调用结构 try-catch-finally
    @Around(value = "execution(public float com.hspedu.spring.aop.aspectj.SmartDog.getSum(float, float))")
    public Object doAround(ProceedingJoinPoint joinPoint) {
        Object result = null;
        String methodName = joinPoint.getSignature().getName();
        try {
            //1.相当于前置通知完成的事情
            Object[] args = joinPoint.getArgs();
            List<Object> argList = Arrays.asList(args);
            System.out.println("AOP环绕通知[-前置通知]" + methodName + "方法开始了--参数有:" + argList);
            //在环绕通知中一定要调用joinPoint.proceed()来执行目标方法
            result = joinPoint.proceed();
            //2.相当于返回通知完成的事情
            System.out.println("AOP环绕通知[-返回通知]" + methodName + "方法结束了--结果是:" + result);
        } catch (Throwable throwable) {
            //3.相当于异常通知完成的事情
            System.out.println("AOP环绕通知[-异常通知]" + methodName + "方法抛异常了--异常对象:" + throwable);
        } finally {
            //4.相当于最终通知完成的事情
            System.out.println("AOP环绕通知[-后置通知]" + methodName + "方法最终结束了...");
        }
        return result;
    }
}

切入点表达式重用

package com.zfc.spring.aop.aspectj;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.*;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;

import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.util.Arrays;

/**
 * @author zfc
 * @version 1.0
 * 切面类 , 类似于我们以前自己写的MyProxyProvider,但是功能强大很多
 *
 */
@Order(value = 2)//表示该切面类执行的顺序, value的值越小, 优先级越高
@Aspect //表示是一个切面类[底层切面编程的支撑(动态代理+反射+动态绑定...)]
@Component //会注入SmartAnimalAspect到容器
public class SmartAnimalAspect {


    //定义一个切入点, 在后面使用时可以直接引用, 提高了复用性
    @Pointcut(value = "execution(public float com.zfc.spring.aop.aspectj.SmartDog.getSum(float, float)))")
    public void myPointCut() {
    }

    //希望将f1方法切入到SmartDog-getSum前执行-前置通知

    /**
     * 解读
     * 1. @Before 表示前置通知:即在我们的目标对象执行方法前执行
     * 2. value = "execution(public float com.hspedu.spring.aop.aspectj.SmartDog.getSum(float, float)
     * 指定切入到哪个类的哪个方法  形式是: 访问修饰符 返回类型 全类名.方法名(形参列表)
     * 3. showBeginLog方法可以理解成就是一个切入方法, 这个方法名是可以程序员指定  比如:showBeginLog
     * 4. JoinPoint joinPoint 在底层执行时,由AspectJ切面框架, 会给该切入方法传入 joinPoint对象
     * , 通过该方法,程序员可以获取到 相关信息
     *
     * @param joinPoint
     */
    //@Before(value = "execution(public float com.hspedu.spring.aop.aspectj.SmartDog.getSum(float, float))")
    //这里我们使用定义好的切入点
    @Before(value = "myPointCut()")
    public void showBeginLog(JoinPoint joinPoint) {
        //通过连接点对象joinPoint 可以获取方法签名
        Signature signature = joinPoint.getSignature();
        System.out.println("SmartAnimalAspect-切面类showBeginLog()[使用的myPointCut()]-方法执行前-日志-方法名-" + signature.getName() + "-参数 "
                + Arrays.asList(joinPoint.getArgs()));
    }

    //返回通知:即把showSuccessEndLog方法切入到目标对象方法正常执行完毕后的地方
    //解读
    //1. 如果我们希望把目标方法执行的结果,返回给切入方法
    //2. 可以再 @AfterReturning 增加属性 , 比如 returning = "res"
    //3. 同时在切入方法增加 Object res
    //4. 注意: returning = "res" 和 Object res 的 res名字一致
    //@AfterReturning(value = "execution(public float com.hspedu.spring.aop.aspectj.SmartDog.getSum(float, float))", returning = "res")
    //使用切入点
    @AfterReturning(value = "myPointCut()", returning = "res")
    public void showSuccessEndLog(JoinPoint joinPoint, Object res) {
        Signature signature = joinPoint.getSignature();
        System.out.println("SmartAnimalAspect-切面类showSuccessEndLog()-方法执行正常结束-日志-方法名-" + signature.getName() + " 返回的结果是=" + res);
    }


    //异常通知:即把showExceptionLog方法切入到目标对象方法执行发生异常的的catch{}
    //@AfterThrowing(value = "execution(public float com.hspedu.spring.aop.aspectj.SmartDog.getSum(float, float))", throwing = "throwable")
    //直接使用切入点表达式
    @AfterThrowing(value = "myPointCut()", throwing = "throwable")
    public void showExceptionLog(JoinPoint joinPoint, Throwable throwable) {
        Signature signature = joinPoint.getSignature();
        System.out.println("SmartAnimalAspect-切面类showExceptionLog()-方法执行异常-日志-方法名-" + signature.getName() + " 异常信息=" + throwable);
    }

    //最终通知:即把showFinallyEndLog方法切入到目标方法执行后(不管是否发生异常,都要执行 finally{})
    //@After(value = "execution(public float com.hspedu.spring.aop.aspectj.SmartDog.getSum(float, float))")
    //直接使用切入点
    @After(value = "myPointCut()")
    public void showFinallyEndLog(JoinPoint joinPoint) {
        Signature signature = joinPoint.getSignature();
        System.out.println("SmartAnimalAspect-切面类showFinallyEndLog()-方法最终执行完毕-日志-方法名-" + signature.getName());
    }

    //新的前置通知
    //@Before(value = "execution(public void com.hspedu.spring.aop.aspectj.Phone.work()) || execution(public void com.hspedu.spring.aop.aspectj.Camera.work())")
    //public void hi(JoinPoint joinPoint) {
    //    Signature signature = joinPoint.getSignature();
    //    System.out.println("切面类的hi()-执行的目标方法-" + signature.getName());
    //}

    //切入表达式也可以指向接口的方法, 这时切入表达式会对实现了接口的类/对象生效
    //比如下面我们是对UsbInterface 切入,那么对实现类Phone 和 Camera对象都作用了
    @Before(value = "execution(public void com.hspedu.spring.aop.aspectj.UsbInterface.work())")
    public void hi(JoinPoint joinPoint) {
        Signature signature = joinPoint.getSignature();
        System.out.println("切面类的hi()-执行的目标方法-" + signature.getName());
    }


    //给Car配置一个前置通知
    @Before(value = "execution(public void Car.run())")
    public void ok1(JoinPoint joinPoint) {

        Signature signature = joinPoint.getSignature();
        System.out.println("切面类的ok1()-执行的目标方法-" + signature.getName());

    }

    //返回通知
    @AfterReturning(value = "execution(public void Car.run())")
    public void ok2(JoinPoint joinPoint) {

        Signature signature = joinPoint.getSignature();
        System.out.println("切面类的ok2()-执行的目标方法-" + signature.getName());

    }

    //异常通知
    @AfterThrowing(value = "execution(public void Car.run())")
    public void ok3(JoinPoint joinPoint) {

        Signature signature = joinPoint.getSignature();
        System.out.println("切面类的ok3()-执行的目标方法-" + signature.getName());

    }

    //后置通知
    @After(value = "execution(public void Car.run())")
    public void ok4(JoinPoint joinPoint) {


        Signature signature = joinPoint.getSignature();
        System.out.println("切面类的ok4()-执行的目标方法-" + signature.getName());
        //演示一下JoinPoint常用的方法.
        joinPoint.getSignature().getName(); // 获取目标方法名
        joinPoint.getSignature().getDeclaringType().getSimpleName(); // 获取目标方法所属类的简单类名
        joinPoint.getSignature().getDeclaringTypeName(); // 获取目标方法所属类的类名
        joinPoint.getSignature().getModifiers(); // 获取目标方法声明类型(public、private、protected)
        Object[] args = joinPoint.getArgs(); // 获取传入目标方法的参数,返回一个数组
        joinPoint.getTarget(); // 获取被代理的对象
        joinPoint.getThis(); // 获取代理对象自己
    }
}

切面类执行的顺序

切面优先级问题:

如果同一个方法,有多个切面在同一个切入点切入,那么执行的优先级如何控制

基本语法:

@order(value=n)来控制 n值越小,优先级越高

1.不能理解成:优先级高的每个消息通知都先执行,这个和方法调用机制(和Filter过滤器链式调用类似)

基于XML配置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/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd">

    <!--使用XML配置,完成AOP编程-->
    <!--配置一个切面类对象-bean-->
    <bean class="com.zfc.spring.aop.xml.SmartAnimalAspect" id="smartAnimalAspect"/>
    <!--配置一个SmartDog对象-bean-->
    <bean class="com.hspedu.spring.aop.xml.SmartDog" id="smartDog"/>
    <!--配置切面类, 细节一定要引入 xmlns:aop-->
    <aop:config>
        <!--配置切入点-->
        <aop:pointcut id="myPointCut" expression="execution(public float com.zfc.spring.aop.xml.SmartDog.getSum(float, float)))"/>
        <!--配置切面的前置,返回, 异常, 最终通知-->
        <aop:aspect ref="smartAnimalAspect" order="10">
            <!--配置前置通知-->
            <aop:before method="showBeginLog" pointcut-ref="myPointCut"/>
            <!--返回通知-->
            <aop:after-returning method="showSuccessEndLog" pointcut-ref="myPointCut" returning="res"/>
            <!--异常通知-->
            <aop:after-throwing method="showExceptionLog" pointcut-ref="myPointCut" throwing="throwable"/>
            <!--最终通知-->
            <aop:after method="showFinallyEndLog" pointcut-ref="myPointCut"/>
            <!--配置环绕通知-->
            <!--<aop:around method=""/>-->
        </aop:aspect>
    </aop:config>
</beans>

AOP与BeanPostProcessor的关系

1)AOP底层是基于BeanPostProcessor 机制的.

2)即在Bean创建好后,根据是否需要AOP处理,决定返代理对象,还是原生Bean

3)在返回代理对象时,就可以根据要代理的类和方法来返回

4)其实这个机制并不难,本质就是在BeanPostProcessor 机制+动态代理技术

拓展类加载器

java的类加载器3种

1.Bootstrap类加载器 对应路径jre/libI

2.APP类加载器 对应路径classpath

3.Ext 类加载器 对应路径jre/lib/ext

注解的简单实现

package com.zfc.spring;

import com.hspedu.spring.annotation.AfterReturning;
import com.hspedu.spring.annotation.Before;
import com.hspedu.spring.component.SmartAnimalAspect;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

/**
 * @author zfc
 * @version 1.0
 */
public class zfcTest {
    public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InstantiationException, InvocationTargetException {

        //1. 获取SmartAnimalAspect的class对象
        Class<SmartAnimalAspect> smartAnimalAspectClass = SmartAnimalAspect.class;
        //2. 遍历该类的所有方法
        for (Method declaredMethod : smartAnimalAspectClass.getDeclaredMethods()) {
            //如果切面类的方法有Before注解
            if (declaredMethod.isAnnotationPresent(Before.class)) {
                //得到切面类的切入方法名
                System.out.println("m:= " + declaredMethod.getName());
                //得到Before(value="xxxx")
                //得到Before注解
                Before annotation = declaredMethod.getAnnotation(Before.class);
                //得到Before注解的value
                System.out.println("value:= " + annotation.value());

                //得到切入要执行的方法.[反射基础]
                Method declaredMethod1 = smartAnimalAspectClass.getDeclaredMethod(declaredMethod.getName());
                //调用切入方法[通过反射调用]
                declaredMethod1.invoke(smartAnimalAspectClass.newInstance(), null);

            } else if (declaredMethod.isAnnotationPresent(AfterReturning.class)) {

                //如果发现切面类有AfterReturning注解,同样可以进行处理..
                System.out.println("m:= " + declaredMethod.getName());
                AfterReturning annotation = declaredMethod.getAnnotation(AfterReturning.class);
                System.out.println("value:= " + annotation.value());

                //得到切入要执行的方法.
                Method declaredMethod1 = smartAnimalAspectClass.getDeclaredMethod(declaredMethod.getName());
                //调用切入方法[反射调用]
                declaredMethod1.invoke(smartAnimalAspectClass.newInstance(), null);
            }
        }
    }
}

Jdbc Tmpelate

实际需求:如果程序员就希望使用 spring 框架来做项目,spring 框架如何处理对数据库的操作呢?

1.方案1.使用前面做项目开发的 JdbcUtils 类

2.方案2,其实spring提供了一个操作数据库(表)功能强大的类JdbcTemplate 。我们可以同ioc容器来配置一个JdbcTemplate对象,使用它来完成对数据库表的各种操作.

<?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 https://www.springframework.org/schema/context/spring-context.xsd">

    <!--配置要扫描包-->
    <context:component-scan
            base-package="com.zfc.spring.jdbctemplate.dao"/>

    <!--引入外部的jdbc.properties文件-->
    <context:property-placeholder location="classpath:jdbc.properties"/>
    <!--配置数据源对象-DataSoruce-->
    <bean class="com.mchange.v2.c3p0.ComboPooledDataSource" id="dataSource">
        <!--给数据源对象配置属性值-->
        <property name="user" value="${jdbc.user}"/>
        <property name="password" value="${jdbc.pwd}"/>
        <property name="driverClass" value="${jdbc.driver}"/>
        <property name="jdbcUrl" value="${jdbc.url}"/>
    </bean>

    <!--配置JdbcTemplate对象-->
    <bean class="org.springframework.jdbc.core.JdbcTemplate" id="jdbcTemplate">
        <!--给JdbcTemplate对象配置dataSource-->
        <property name="dataSource" ref="dataSource"/>
    </bean>

    <!--配置NamedParameterJdbcTemplate对象-->
    <bean class="org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate"
          id="namedParameterJdbcTemplate">
        <!--通过构造器,设置数据源-->
        <constructor-arg name="dataSource" ref="dataSource"/>
    </bean>
</beans>

增删改查

package com.zfc.spring.test;

import com.zfc.spring.bean.Monster;
import com.zfc.spring.jdbctemplate.dao.MonsterDao;
import org.junit.jupiter.api.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.jdbc.core.BeanPropertyRowMapper;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.jdbc.core.namedparam.BeanPropertySqlParameterSource;
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate;
import org.springframework.jdbc.core.namedparam.SqlParameterSource;
import org.springframework.transaction.annotation.Transactional;

import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * @author zfc
 * @version 1.0
 */
public class JdbcTemplateTest {

    @Test
    public void testDatasourceByJdbcTemplate() throws SQLException {
        //获取到容器
        ApplicationContext ioc =
                new ClassPathXmlApplicationContext("JdbcTemplate_ioc.xml");

        DataSource dataSource = ioc.getBean(DataSource.class);
        Connection connection = dataSource.getConnection();
        System.out.println("获取到connection= " + connection);
        connection.close();
        System.out.println("ok");
    }

    //测试通过JdbcTemplate对象完成添加数据
    @Test
    public void addDataByJdbcTemplate() {
        //获取到容器
        ApplicationContext ioc =
                new ClassPathXmlApplicationContext("JdbcTemplate_ioc.xml");

        //获取JdbcTemplate对象
        JdbcTemplate jdbcTemplate = ioc.getBean(JdbcTemplate.class);
        //1. 添加方式1
        //String sql = "INSERT INTO monster VALUES(400, '红孩儿', '枪法')";
        //jdbcTemplate.execute(sql);
        //2. 添加方式2
        String sql = "INSERT INTO monster VALUES(?, ?, ?)";
        //affected表示 执行后表受影响的记录数
        int affected = jdbcTemplate.update(sql, 500, "红孩儿2", "枪法2");
        System.out.println("add ok affected=" + affected);


    }

    //测试通过JdbcTemplate对象完成修改数据
    @Test
    public void updateDataByJdbcTemplate() {

        //获取到容器
        ApplicationContext ioc =
                new ClassPathXmlApplicationContext("JdbcTemplate_ioc.xml");

        //获取JdbcTemplate对象
        JdbcTemplate jdbcTemplate = ioc.getBean(JdbcTemplate.class);

        //组织SQL
        String sql = "UPDATE monster SET skill=? WHERE id=?";
        int affected = jdbcTemplate.update(sql, "美女计", 300);
        System.out.println("update ok affected= " + affected);

    }

    //批量添加二个monster 白蛇精和青蛇精
    //这里有一个使用API的技巧
    /**
     * 说明
     * 1. 对于某个类, 有很多API, 使用的步骤
     * 2. 老韩的使用技巧(1) 先确定API名字 (2) 根据API提供相应的参数 [组织参数]
     *    (3) 把自己的调用思路清晰 (4) 根据API, 可以推测类似的用法和功能
     */

    /**
     * batch add data
     * 批量添加二个monster 白蛇精和青蛇精-update(sql,List<Object[]>)
     */
    @Test
    public void addBatchDataByJdbcTemplate() {
        ApplicationContext ioc = new ClassPathXmlApplicationContext("JdbcTemplate_ioc.xml");
        //得到JdbcTemplate bean
        JdbcTemplate jdbcTemplate = ioc.getBean(JdbcTemplate.class);//添加..

        //1. 先确定,猜测API名称 batchUpdate[如果出现问题,才重新玩]
        //public int[] batchUpdate(String sql, List<Object[]> batchArgs){}
        //2. 准备参数
        String sql = "INSERT INTO monster VALUES(?, ?, ?)";
        List<Object[]> batchArgs = new ArrayList<>();
        batchArgs.add(new Object[]{600, "老鼠精", "偷吃粮食"});
        batchArgs.add(new Object[]{700, "老猫精", "抓老鼠"});
        //3. 调用
        //说明:返回结果是一个数组,每个元素对应上面的sql语句对表的影响记录数
        int[] ints = jdbcTemplate.batchUpdate(sql, batchArgs);
        //输出 
        for (int anInt : ints) {
            System.out.println("anInt=" + anInt);
        }
        System.out.println("batch add ok..");
    }

    //查询id=100的monster并封装到Monster实体对象[在实际开发中,非常有用]

    @Test
    public void selectDataByJdbcTemplate() {
        ApplicationContext ioc = new ClassPathXmlApplicationContext("JdbcTemplate_ioc.xml");
        //得到JdbcTemplate bean
        JdbcTemplate jdbcTemplate = ioc.getBean(JdbcTemplate.class);
        //组织SQL
        //通过BeanPropertyRowMapper获取rowmapper 是一个接口,可以将查询的结果,封装到你指定的Monster对象中.

        //1. 确定API : queryForObject()
        //public <T> T queryForObject(String sql, RowMapper<T> rowMapper, @Nullable Object... args)
        //2.准备参数
        String sql = "SELECT id AS monsterId, NAME, skill FROM monster WHERE id = 100";
        //使用RowMapper 接口来对返回的数据,进行一个封装-》底层使用的反射->setter
        //这里有一个细节: 你查询的记录的表的字段需要和 Monster的对象字段名保持一致
        RowMapper<Monster> rowMapper = new BeanPropertyRowMapper<>(Monster.class);
        //jdbcTemplate
        Monster monster = jdbcTemplate.queryForObject(sql, rowMapper);
        System.out.println("monster= " + monster);
        System.out.println("查询ok");

    }
    //查询id>=200的monster并封装到Monster实体对象

    /**
     * 查询多条记录
     */
    @Test
    public void selectMulDataByJdbcTemplate() {
        ApplicationContext ioc =
                new ClassPathXmlApplicationContext("JdbcTemplate_ioc.xml");
        //得到JdbcTemplate bean
        JdbcTemplate jdbcTemplate = ioc.getBean(JdbcTemplate.class);
        //组织SQL
        //通过BeanPropertyRowMapper获取rowmapper 是一个接口,可以将查询的结果,封装到你指定的Monster对象中.

        //1.    确定API
        //public <T> T query(String sql, RowMapper<T> rowMapper, Object... args){}
        //2. 组织参数
        String sql = "SELECT id AS monsterId, NAME, skill FROM monster WHERE id >= ?";
        RowMapper<Monster> rowMapper = new BeanPropertyRowMapper<>(Monster.class);
        //3. 调用
        List<Monster> monsterList = jdbcTemplate.query(sql, rowMapper, 100);
        for (Monster monster : monsterList) {
            System.out.println("monster= " + monster);
        }
    }

    //查询返回结果只有一行一列的值,比如查询id=100的怪物名

    /**
     * 查询返回结果只有一行一列的值
     */
    @Test
    public void selectScalarByJdbcTemplate() {
        ApplicationContext ioc =
                new ClassPathXmlApplicationContext("JdbcTemplate_ioc.xml");
        //得到JdbcTemplate bean
        JdbcTemplate jdbcTemplate = ioc.getBean(JdbcTemplate.class);

        //1. 确定API
        //public <T> T queryForObject(String sql, Class<T> requiredType)
        //2. 提供参数
        String sql = "SELECT NAME FROM monster WHERE id = 100";
        //Class<T> requiredType 表示你返回的单行单列的数据类型

        String name =
                jdbcTemplate.queryForObject(sql, String.class);
        System.out.println("返回name= " + name);

    }

    //使用Map传入具名参数完成操作,比如添加 螃蟹精.:name 就是具名参数形式需要使用NamedParameterJdbcTemplate 类, 语句形式: String sql = "INSERT INTO monster VALUES(:my_id, :name, :skill)";

    /**
     * 使用Map传入具名参数完成操作,比如添加
     */
    @Test
    public void testDataByNamedParameterJdbcTemplate() {
        ApplicationContext ioc =
                new ClassPathXmlApplicationContext("JdbcTemplate_ioc.xml");
        //得到NamedParameterJdbcTemplate bean
        NamedParameterJdbcTemplate namedParameterJdbcTemplate =
                ioc.getBean(NamedParameterJdbcTemplate.class);

        //1. 确定使用API
        //public int update(String sql, Map<String, ?> paramMap)
        //2. 准备参数 [:my_id, :name, :skill] 要求按照规定的名字来设置参数
        String sql = "INSERT INTO monster VALUES(:id, :name, :skill)";
        Map<String, Object> paramMap = new HashMap<>();
        //给paramMap填写数据
        paramMap.put("id", 800);
        paramMap.put("name", "蚂蚁精");
        paramMap.put("skill", "喜欢打洞");
        //3. 调用
        int affected = namedParameterJdbcTemplate.update(sql, paramMap);
        System.out.println("add ok affected=" + affected);

    }

    //使用sqlparametersoruce 来封装具名参数,还是添加一个Monster 狐狸精

    @Test
    public void operDataBySqlparametersoruce() {
        ApplicationContext ioc =
                new ClassPathXmlApplicationContext("JdbcTemplate_ioc.xml");
        //得到NamedParameterJdbcTemplate bean
        NamedParameterJdbcTemplate namedParameterJdbcTemplate =
                ioc.getBean(NamedParameterJdbcTemplate.class);

        //确定API
        //public int update(String sql, SqlParameterSource paramSource)
        //public BeanPropertySqlParameterSource(Object object)
        //准备参数
        String sql = "INSERT INTO monster VALUES(:monsterId, :name, :skill)";
        Monster monster = new Monster(900, "大象精", "搬运木头");
        SqlParameterSource sqlParameterSource =
                new BeanPropertySqlParameterSource(monster);
        //调用
        int affected =
                namedParameterJdbcTemplate.update(sql, sqlParameterSource);

        System.out.println("add ok affected= " + affected);
    }


    //测试MonsterDAO
    @Test
    public void monsterDaoSave() {
        ApplicationContext ioc =
                new ClassPathXmlApplicationContext("JdbcTemplate_ioc.xml");

        MonsterDao monsterDao = ioc.getBean(MonsterDao.class);
        Monster monster = new Monster(1000, "小鸭精", "吃鱼");
        monsterDao.save(monster);
        System.out.println("MonsterDAO保存 ok ..");
    }
}

声明式事务

需求说明-用户购买商品

我们需要去处理用户购买商品的业务逻辑:分析:当一个用户要去购买商品应该包含三个步骤:

1.通过商品id获取价格.

2.购买商品(某人购买商品,修改用户的余额.)

3.修改库存量

4.其实大家可以看到,这时,我们需要涉及到三张表商品表,用户表,商品存量表。 应该使用事务处理

@Transactional

使用该注解,可以进行声明式事务控制

事务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 https://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.zfc.spring.tx.dao"/>
    <context:component-scan base-package="com.zfc.spring.tx.service"/>

    <!--引入外部的jdbc.properties文件-->
    <context:property-placeholder location="classpath:jdbc.properties"/>
    <!--配置数据源对象-DataSoruce-->
    <bean class="com.mchange.v2.c3p0.ComboPooledDataSource" id="dataSource">
        <!--给数据源对象配置属性值-->
        <property name="user" value="${jdbc.user}"/>
        <property name="password" value="${jdbc.pwd}"/>
        <property name="driverClass" value="${jdbc.driver}"/>
        <property name="jdbcUrl" value="${jdbc.url}"/>
    </bean>

    <!--配置JdbcTemplate对象-->
    <bean class="org.springframework.jdbc.core.JdbcTemplate" id="jdbcTemplate">
        <!--给JdbcTemplate对象配置dataSource-->
        <property name="dataSource" ref="dataSource"/>
    </bean>

    <!--配置事务管理器-对象
    1. DataSourceTransactionManager 这个对象是进行事务管理-debug源码
    2. 一定要配置数据源属性,这样指定该事务管理器 是对哪个数据源进行事务控制
    -->
    <bean class="org.springframework.jdbc.datasource.DataSourceTransactionManager" id="transactionManager">
        <property name="dataSource" ref="dataSource"/>
    </bean>

    <!--配置启动基于注解的声明式事务管理功能-->
    <tx:annotation-driven transaction-manager="transactionManager"/>
</beans>

事务的传播机制

事务隔离级别