您当前的位置:首页 >  商业资讯  > 正文
【Java学习】 Spring的基础理解 IOC、AOP以及事务
来源:博客园     时间:2023-06-18 14:35:41


(资料图)

一、简介 官网:https://spring.io/projects/spring-framework#overview 官方下载工具:https://repo.spring.io/release/org/springframework/spring/ github下载:https://github.com/spring-projects/spring-framework maven依赖:
    org.springframework    spring-webmvc    5.2.0.RELEASE    org.springframework    spring-jdbc    5.2.0.RELEASE
1.spring全家桶的结构构图: 最下边的是测试单元 其中spring封装了自己的测试单元Core Container 上边一层(Core Container)是核心容器,也是spring框架的基础 也是核心core:提供了框架的基本组成部分 包括ioc和依赖注入功能beans:提供了BeanFactory 实现了工厂模式。方便解耦context:上下文内容expression:提供了强大的表达式语言,用于在运行时查询和操作对象。 3.Data Access/Integration 数据访问/集成该模块包含JDBC、ORM、OXM、JMS和事务处理模块JDBC:提供了JDBC的抽象层,可以更方便的处理数据库ORM:模块提供了流行的对象关系型映射的API的集成OXM:模块提供了对OXM实现的支持(啥是OXM)JMS:包含了生产和消费消息的功能事务:毋庸置疑,可以实现特殊接口类以及所有的pojo支持编程式和声明式事务管理。 4.Web Web层由Web、Servlet、Web-Sockethe和Web-Portlet组成Web模块:提供面向web的基本功能和面向web的应用上下文Servlet模块:为web应用提供了模型视图看着(MVC)和RestWeb服务的实现。Spring的MVC框架可以将代码与web表单进行分离。Web-SocketWeb-Portlet 5.AOP Aop模块提供了面向切面编程的实现,允许自定义放啊拦截器和切入点,对代码继续宁解耦,可以减少模块间的耦合度,方便扩展和提高可维护性 6.Instrumentation 7.Messaging 8.Aspects 也是面向切面编程 9.Test二、IOC: 2.1理论:IOC也就是控制反转 其基本理解就是Spring将创建对象的过程转交给了IOC容器。,在其它类中我们只需要调用即可,不需要重新创建对象。 在我们传统的三层架构模型中,目录结构分别为pojo、dao、和service 当我们在service中调用dao时,常会使用到 private UserDao userDao=new UserDao(); 这就是我们通常的思维,需要创建对象才可以使用,但是引入spring后,便可以使用标签就可以创建对象。 2.2 Spring 中IOC的实现提供了两种方式: 2.2.1BeanFactory:这是IOC的基本实现,是Spring的内部接口,不会提供给开发人员使用。 加载配置文件时,不会创建对象,只有在使用的时候才会创建对象(一会可以代码解释 注意的是getBean的动作) 2.2.2ApplicationContext 它是BeanFactory接口的子接口,提供了更加强大的功能开发人员进行加载配置文件的时候就进行了创建。
//1.引入jar包//2.编辑配置文件配置对象  id为唯一限定名 一般为类名的小写   class是类所在的位置    //3.测试@Testpublic void testTeacherCreate(){    //加载配置文件    ApplicationContext ac=new ClassPathXmlApplicationContext("applicationConfig.xml");    Teacher teacher = ac.getBean("teacher", Teacher.class);    System.out.println(teacher);}//在以上代码中  如果使用的是ApplicationContext 则在进行加载配置文件时 就已经在IOC容器中创建好了teacher对象//如果是使用的是 BeanFactory ac 创建对象,则在getbean时才会创建对象(不可操作,因为该接口不对开发人员透明)

2.2.3 ApplicationContext接口的实现类的继承关系

由上图可以看出,我们能使用的只有ClassPathXMlApplicationContext和FileSystemXmlApplicationContext来解析我们的xml配置文件。其中Class开头的可以去classpath路径下去寻找File开头的可以加载磁盘路径下的配置文件之后我们还可以使用AnnotationconfigApplicationContext可以使用注解方式来创建容器。2.3 IOC对Bean的管理 2.3.1 创建bean bean就是我们所说的Java对象,在之前所学的创建Java对象,一般是使用new关键字 调用类中的构造器来创建对象。 因此创建bean的方法就可以有三种方式:使用默认的无参构造、使用简单工厂方式创建、使用静态工厂方式创建 A:使用默认的无参构造
 

B :使用普通工厂类创建bean实例

1.建立普通类teacher,包含个别属性,并添加get、set方法2.创建工厂类public class SchoolFactory {    Teacher teacher=new Teacher();    public  Teacher getInstance(){        return teacher;    }}在工厂类中实例化对象,并添加一个普通方法可以获取到对象3.配置文件        第一个bean是一个实例化的对象也就是对象工厂第二个bean 是利用对象工厂来创建的对象实例化            factory标签指的是  是哪个工厂对象,对应上边的id            factory-method指的是调用可以获取对象实例的方法(普通方法)4.测试:/*** 测试普通工厂类创建bean实例*/@Testpublic void testTeacherFactory1(){    //加载配置文件    ApplicationContext ac=new ClassPathXmlApplicationContext("applicationConfig.xml");    Teacher teacher1 = ac.getBean("teacher1", Teacher.class);    System.out.println(teacher1);}

如果在程序中可能需要频繁的创建某个类的实例对象,采用工厂模式会更好

2.3.2 依赖注入 也就是注入属性 依赖注入也就是对属性的注入,分为三种:构造器注入、set注入以及p标签注入 其中的构造注入,需要走的是带参构造 set注入,走的是set方法和无参构造 p标签注入和set注入基本一样,只不过头文件中需要引入 对象工厂接口中最基本的是BeanFactory,它可以支持懒加载,也就是在加载配置文件时,不会创建实例对象,而是在需要调用时才会创建。 如今使用的是applicationContext来解析配置文件,他的底层接口也是BeanFactory,但是它可以在加载配置文件的时候,就直接创建了bean实例。 以Student类作为演示
public class Student {    private String sname;    private int age;    private String sex;    public Student(String sname, int age, String sex) {        this.sname = sname;        this.age = age;        this.sex = sex;        System.out.println("这是三个参数的无参构造");    }    public Student(String sname, int age) {        this.sname = sname;        this.age = age;        System.out.println("这是第一个属性为name的两个参数的构造");    }    public Student( int age,String sname) {        this.sname = sname;        this.age = age;        System.out.println("这是第一个属性为age的两个参数的构造");    }    public Student() {        System.out.println("这是无参构造");    }    public String getSname() {        return sname;    }    public void setSname(String sname) {        this.sname = sname;    }    public int getAge() {        return age;    }    public void setAge(int age) {        this.age = age;    }    public String getSex() {        return sex;    }    public void setSex(String sex) {        this.sex = sex;    }    @Override    public String toString() {        return "Student{" +                "sname="" + sname + "\"" +                ", age=" + age +                ", sex="" + sex + "\"" +                "}";    }}
A 构造器注入
        看我们的实体类中就可以发现,我们的含有两个参数的构造器,有两个,在这个时候我们使用的构造器的构造函数就不知道使用的是哪个带参的构造器因此我们可以使用 index来标识下标 就可以指定先执行那个代餐的构造    其中constructor-arg中也可以使用type来标识参数类型来确定属性        如果是八大基本数据类型,则可以直接写关键字,如果是其他类型,则需要添加类的全限定路径    如果其中还含有其他对象类型的参数,    如此时的student类中包含属性  private Grade grade;        构造器:                                            

B set方法注入 此时需要在类中对属性添加set方法 以及无参构造

                    

C :p标签注入 此时就要添加对应的set方法以及无参构造 以及添加头文件

D:也可以set注入和构造器注入 混合使用 但是需要有对应的构造方法

2.3.3 不同属性类型对应的写法: 由于我们的属性的在不同的使用场景下可能有不同的属性类型,如集合、数组等情况,因此可能会需要使用到不同的标签,下面只演示使用set方法注入的情况 那就意味着,我们需要添加构造方法和无参构造。
实体类public class Order {    private String [] cources;    private List lists;    private Map maps;    private Set sets;
配置文件                                        美羊羊                兰羊羊                                                        舒克                贝塔                                                        mysql                javase                javaweb                                                                                            
数组类型的使用 list类表类型的使用: set集合类型的使用 map集合类型的使用2.4 Bean的作用域: scope属性singleton:默认值 单例模式 每次获取的bean都是同一个对象prototype:每次获取bean都会被重新实例化request:每次请求都会重新实例化对象,但是在同一请求下获取的情况下的bean是单例的session 每次会话内的bean是单例的application:整个应用程序对象内的bean实例都是单例模式的websocket:同一个websocket对象内的对象是单例的。
Singleton    测试类:@Testpublic void testStudent3(){    //加载配置文件    BeanFactory ac=new ClassPathXmlApplicationContext("applicationConfig2.xml");    Student stu1 = ac.getBean("stu3", Student.class);    System.out.println(stu1);    Student stu2 = ac.getBean("stu3", Student.class);    System.out.println(stu2);    System.out.println(stu1==stu2);}结果:com.qiang.pojo.Student@10a035a0com.qiang.pojo.Student@10a035a0true
prototype测试类@Testpublic void testStudent4(){    //加载配置文件    BeanFactory ac=new ClassPathXmlApplicationContext("applicationConfig2.xml");    Student stu1 = ac.getBean("stu4", Student.class);    System.out.println(stu1);    Student stu2 = ac.getBean("stu4", Student.class);    System.out.println(stu2);    System.out.println(stu1==stu2);}结果:com.qiang.pojo.Student@10a035a0com.qiang.pojo.Student@67b467e9false
2.5 bean的生命周期 2.5.1 一般理解下的bean的生命周期:通过构造器创建bean实【此时执行的是无参构造】为bean属性设置值以及对其他的bean的引用【set注入】调用bean的初始化方法,在配置文件中配置bean对象可以使用了 【已经获取到了对象】当容器关闭时,调用bean的销毁方法【在配置文件中配置】
实体类对象public class People implements Serializable {    private String oid;    public People() {        System.out.println("第一步:执行无参构造");    }    public String getOid() {        return oid;    }    public void setOid(String oid) {        this.oid = oid;        System.out.println("第二步: 调用set方法给属性设置值......");    }    public void initMethod(){        System.out.println("第三步:执行初始化方法............");    }    public void destroyMethod(){        System.out.println("第五步:执行销毁方法............");    }    @Override    public String toString() {        return "People{" +                "oid="" + oid + "\"" +                "}";    }}
配置文件  添加初始化方法 和销毁方法其中的标签init-method、destroy-method 中的方法是在实体类中自定义的 测试类/*** 测试bean的生命周期*/@Testpublic void testBeanLive(){    //加载配置文件    ClassPathXmlApplicationContext ac=new ClassPathXmlApplicationContext("applicationConfig2.xml");    //获取bean实例    People people = ac.getBean("people", People.class);    System.out.println("第四步:获取bean实例对象 。。。");    System.out.println(people);    //手动销毁    ac.close();}结果:第一步:执行无参构造第三步:执行初始化方法............第四步:获取bean实例对象 。。。People{oid="null"}第五步:执行销毁方法............
2.5.2 添加后置处理器的生命周期的方法第一步:执行无参数的构造方法 。。。第二步: 调用set方法给属性设置值......在初始化之前执行的方法第三步:执行初始化方法............在初始化之后执行的方法第四步:获取bean实例对象 。。。第五步:执行销毁方法............
在实体类中 实现了BeanPostProcessor接口    并重写了@Overridepublic Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {    System.out.println("在初始化之前执行的方法");    return bean;}@Overridepublic Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {    System.out.println("在初始化之后执行的方法");    return bean;}
2.6 自动装配: 装配的意思是 就是怎么去创建对象spring的装配方式有三种: 在xml中显示的装配 也就是使用标签 在Java中的显示配置 new关键字 自动装配机制 Spring的自动装配你有两个角度的实现 分别是 组件扫描和 自动装配 组件扫描:spring 回自动发现应用上下文中所创建的bean 自动装配:spring自动满足bean之间的依赖,也就是使用IOC和DI 自动装配的实现方式有两种,一种是通过xml的bena标签 也可以使用注解方式 2.6.1 通过XMl中的标签 自动装配 A:byName 按名称自动装配 通俗理解:在xml配置文件中 使用bean标签 通过bynae自动诸如和,每次遇到名称为 byname属性值时就自动创建对象
实体类Dogprivate String color;private int age;配置文件    测试类:@Testpublic void testDog1(){    ApplicationContext ac=new ClassPathXmlApplicationContext("applicationConfig.xml");    Dog d1 = ac.getBean("d1", Dog.class);    System.out.println(d1.getAge());}结果:15

B.byType 按类型自动装配

配置文件    测试类:@Testpublic void testDog1(){    ApplicationContext ac=new ClassPathXmlApplicationContext("applicationConfig.xml");    Dog d1 = ac.getBean(Dog.class);    System.out.println(d1.getAge());}结果:21
2.6.2根据注解开发 spring中的注解注入的有:@Autowired、@Resources @Qualifier @Service @Commonent @Controller @Repository 在使用注解开发时,需要现在配置文件中开启组件扫描功能开启组件扫描 便可以自动查找其中的自动注入 不同的注解的区分:Autowired是自动注入,自动从spring的上下文找到合适的bean来注入。Resource用来指定名称注入。Qualifier和Autowired配合使用,指定bean的名称。Service,Controller,Repository分别标记类是Service层类,Controller层类,Dao层的类,spring扫描注解配置时,会标记这些类要生成bean。Component是一种泛指,标记类是组件,spring扫描注解配置时,会标记这些类要生成bean。@Autowired注解 默认按类型装配springbean 默认情况下,必须要求依赖对象必须存在。如果容器中有多个相同类型的bean,则框架会抛出异常。 @Qualifier 此注解用来消除依赖注入冲突的。我们可以消除需要注入那个bean的问题 通过该注解,我们可以使用特定的Spring Bean一起装配,Spring框架可以从多个相同类型并满足装配要求的bean中找到我们想要的。 @Resource是按名称装配:
分析:  由于注解@Autowired是默认按类型装配的,一个类型的可能会有多个实现方法        因此在演示的时候 就可以选择一个接口,有多个实现类来作为演示1.构建一个接口public interface TeacherService {    public void sayName();    public void saysex();}2.创建多个实现类(以三个举例)@Componentpublic class TeacherServiceImpl1 implements TeacherService {    @Override    public void sayName() {        System.out.println("A");    }}@Componentpublic class TeacherServiceImpl1 implements TeacherService {    @Override    public void sayName() {        System.out.println("B");    }}@Componentpublic class TeacherServiceImpl1 implements TeacherService {    @Override    public void sayName() {        System.out.println("C");    }}3.创建两外一个类,可以调用该类的实现类public class TeacherController {    @Autowired    //创建对象    private TeacherService teacherService;    public void soutResult(){        teacherService.sayName();    }}4.修改配置文件 开启扫描    5.测试:@Testpublic void testAopAno(){    ApplicationContext ac=new ClassPathXmlApplicationContext("applicationConfig.xml");    TeacherController contro = ac.getBean("teacherController", TeacherController.class);    contro.soutResult();}6.观察结果:org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name "teacherController": Unsatisfied dependency expressed through field "teacherService"; nested exception is org.springframework.beans.factory.NoUniqueBeanDefinitionException: No qualifying bean of type "com.qiang.service.TeacherService" available: expected single matching bean but found 3: teacherServiceImpl1,teacherServiceImpl2,teacherServiceImpl3分析:由于在service接口有多个实现类,使用autowired是按类型注入,可能会找不到使用哪个  因此可以搭配使用@Qualifier注解修改第三步:public class TeacherController {    @Autowired    @Qualifier("teacherServiceImpl1")    //创建对象    private TeacherService teacherService;    public void soutResult(){        teacherService.sayName();    }}继续进行测试  结果为:A结果显示正常继续修改第三步:public class TeacherController {   @Resource(name = "teacherServiceImpl1")    //创建对象    private TeacherService teacherService;    public void soutResult(){        teacherService.sayName();    }}继续进行测试  结果为:A结果显示正常
通过上述例子可以看出,如果一个类需要由多个实例变量时,可以搭配使用@Autowired @Qualifier("teacherServiceImpl1"),也可以单独使用@Resource(name = "teacherServiceImpl1"三、AOP 3.1AOP简介: AOP通俗理解就是面向切面编程,是对面向对象的一种补充、将那些与业务无关的,但却对多个对象产生影响的公共行为和逻辑,抽取并封装为一个可重用的模板,这个模板就被称为”切面“,使用AOP减少了系统中的重复代码、降低了模块间的耦合度,同时提高了系统的可维护性。 AOP的底层是动态代理,分别为JDK动态代理和CGLIB动态代理 3.2术语: 连接点:可以被增强的方法 切入点:实际真正被增强的方法,称为切入点 通知(增强):又被叫做增强,实际增强的逻辑部分称为通知[增强] 切面(是个动作):把通知应用到切入点的过程。其中:通知有五种: 前置通知(before) 后置(返回)通知(after returning) 、环绕通知(Around)、异常通知(after-throwing)通知通俗理解就是AOP暴漏给我们的方法,我们在这个方法中直接定义需要扩展的代码即可,但是其执行顺序,交给了spring来处理。 3.3 AOP的准备工作:首先介绍一下Aspect J  这个是一个独立的AOP模型,但不是Spring框架的内容。 因此在演示的时候需要导入相关的jar包,或者使用maven文件 切入点表达式:知道对那个类的方法进行增强 execution( [权限修饰符] [返回值类型] [类的全路径] [方法名][参数列表]) 3.4 AOP通知 3.4.1基于XML的配置通知 A 第一种方式:
1.添加jar包2.建造实体类和代理对象类    普通类应包含一个普通方法,该普通方法也就是要增强的那个方法public class Student implements Serializable {    public void add(){        System.out.println("这个只是一个普通的方法");    }}public class StudentProxy {    //配置前置通知    public void before(){        System.out.println("前置通知。。。。。");    }    //配置后置返回通知    public void afterreturning(){        System.out.println("后置返回通知....");    }    //配置最终通知    public void after(){        System.out.println("最终通知....");    }    //配置环绕通知    public void around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {        System.out.println("环绕通知前");        proceedingJoinPoint.proceed();        System.out.println("环绕通知后....");    }    //配置异常通知    public void afterThrow(){        System.out.println("异常通知....");    }}3.修改配置文件在修改配置文件时应注意,头文件也需要进行修改xmlns:aop="http://www.springframework.org/schema/aop"xsi:http://www.springframework.org/schema/aop    https://www.springframework.org/schema/aop/spring-aop.xsd"//注入                                                                                           以上没有演示异常通知,异常通知在程序发生异常时才会发生。测试:@Testpublic void testAopXmlDemo1(){    ApplicationContext ac=new ClassPathXmlApplicationContext("applicationConfig.xml");    Student student = ac.getBean("student", Student.class);    student.add();}结果:前置通知。。。。。环绕通知前这个只是一个普通的方法环绕通知后....最终通知....后置返回通知....
分析: 前置通知:在连接点之前执行的通知 后置返回通知:一般在方法的结尾,必然会多一个返回值 环绕通知:包含了前置通知和后置通知 异常通知:处理异常数据,事务回滚 3.4.2基于注解的配置通知 B:第二种方式
1.引入jar包 2.创建实体类@Componentpublic class User {    public void add(){        System.out.println("这是一个普通方法");    }}3.创建代理类@Component@Aspect //生成代理对象public class UserProxy {    //前置通知    @Before(value = "execution(* org.qiang.aop.anno.pojo.User.add(..))")    public void before(){        System.out.println("前置通知。。。");    }    //后置返回通知    @AfterReturning(value = "execution(* org.qiang.aop.anno.pojo.User.add(..))")    public void afterReturning(){        System.out.println("后置返回通知afterReturning....");    }    //环绕通知    @Around(value = "execution(* org.qiang.aop.anno.pojo.User.add(..))")    public void around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable{        System.out.println("环绕之前....");        // 被增强的方法执行了        proceedingJoinPoint.proceed();        System.out.println("环绕之后....");    }//    //异常通知 只有手动创造了异常才可以触发这个通知//    @AfterThrowing(value = "execution(* org.qiang.aop.anno.pojo.User.add(..))")//    public void afterThrowing(){//        System.out.println("异常通知 afterThrowing......");//    }//最终通知    @After(value = "execution(* org.qiang.aop.anno.pojo.User.add(..))")    public void after(){        System.out.println("最终通知....");    }}//修改配置文件    //测试:@Testpublic void testAopAnno(){    ApplicationContext ac=new ClassPathXmlApplicationContext("applicationConfig2.xml");    User user = ac.getBean("user", User.class);    user.add();}结果:环绕之前....前置通知。。。这是一个普通方法环绕之后....最终通知....后置返回通知afterReturning....

C:第三种方式抽取重复代码

只需要修改代理类对象就可以了@Component@Aspect //生成代理对象public class UserProxy {//    抽取切入点    @Pointcut(value = "execution(* org.qiang.aop.anno.pojo.User.add(..))")    public void pointcutDemo(){    }    //前置通知    @Before(value = "pointcutDemo()")    public void before(){        System.out.println("前置通知。。。");    }          //后置返回通知    @AfterReturning(value = "pointcutDemo()")    public void afterReturning(){        System.out.println("后置返回通知afterReturning....");    }          //环绕通知    @Around(value = "pointcutDemo()")    public void around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable{        System.out.println("环绕之前....");        // 被增强的方法执行了        proceedingJoinPoint.proceed();        System.out.println("环绕之后....");    }        //    //异常通知 只有手动创造了异常才可以触发这个通知//    @AfterThrowing(value = "pointcutDemo()")//    public void afterThrowing(){//        System.out.println("异常通知 afterThrowing......");//    }            //最终通知    @After(value = "pointcutDemo()")    public void after(){        System.out.println("最终通知....");    }    }
D : AOP 底层是动态代理默认的是JDK动态代理的方法,也可以通过 标签来修改,其默认值是false是JDK动态代理的格式,改为true就可以使用CGLIB代理格式。3.5 Spring的事务管理 Spring框架将固定的冗余部分的套路代码进行了封装,对程序员仅提供简单的XML配置就可以完成事务的管理,不需要在编写事务管理代码。这也就是Spring的非常重要功能--声明式事务 声明式事务是基于AOP实现的(动态代理)。程序员只需要调用持久层代码和业务逻辑代码,将开启事务的代码反正该了前置通知中,将事务回滚和事务提交的代码放在了后置通知中。 使用事物可以保证操作前后数据的完整性,事务的四个特性:ACID 原子性、一致性、隔离性和持久性 编程式事务:整个事务的操作都是由程序员进行手动管理,手动提交,手动回滚 声明式事务:整个事务由其他框架进行管理,我们使用事务的时候只需要进行简单的声明或 者配置即可。 Spring中的Tx模块就包含了对声明式事务的封装,以下是我们的日常的手动提交事务的写法:
public void testdemo1() throws SQLException {    Connection conn= DriverManager.getConnection("","","");    //关闭自动提交    conn.setAutoCommit(false);    try {        PreparedStatement ptst=conn.prepareStatement("insert into bank  values=(?,?)");                ptst.executeUpdate();            }catch (Exception e){    //再发生异常时,就会进行事务的回滚        conn.rollback();    }    }
回顾Aop切面编程,可以发现共通点,在关闭自动提交的部分,可以用前置通知来代替,try代码块的部分就可以理解为是切入点,事务提交的部分可以使用后置通知来实现,而对于出现异常,事务回滚的操作就可以使用异常通知来处理。也就是:
在org.springframework.jdbc.datasource.DataSourceTransactionManager 中的方法:protected void doBegin(){    conn.setAutoCommit(false)}protected void doBegin() {}protected void doRollback() {}

添加事务通知:

                                               
声明式事务的四个基础属性介绍: 标签下有属性配置,也可以用在注解上:

标签:

相关新闻

X 关闭

X 关闭

精彩推荐