Spring框架笔记

Spring

1.Spring简介

程序原则:尽量不要改变原有的代码

Spring:春天--> 给软件行业带来了春天

2002,首次推出了Spring框架的雏形:interface21框架!

Rod Johnson

spring理念:使现有的技术更加容易使用,整合了现有的技术框架!,本身是一个大杂烩--> 支持的东西比较多!!

官网:Core Technologies (spring.io)

官方下载地址:

<!-- https://mvnrepository.com/artifact/org.springframework/spring-webmvc -->
<dependency>
   <groupId>org.springframework</groupId>
   <artifactId>spring-webmvc</artifactId>
   <version>5.2.20.RELEASE</version>
</dependency>

<!-- https://mvnrepository.com/artifact/org.springframework/spring-webmvc -->
<dependency>
   <groupId>org.springframework</groupId>
   <artifactId>spring-jdbc</artifactId>
   <version>5.2.20.RELEASE</version>
</dependency>

 

2 优点

spring是一个开源的免费的框架(容器)!

Spring是一个轻量级的、非入侵式的框架!

两个特性:控制反转(ioc) 和面向切面编程(AOP)

支持事物处理,对框架整合的支持!

总结:Spring就是一个轻量级的控制反转(IOC)和面向切面编程(AOP)的框架!

3. Spring的组成

 

 

4.拓展

在spring的官网有这个介绍:现代化的Java开发! 说白就是基于Spring的开发!

Spring Boot:

一个快速开发的脚手架

基于SpringBoot可以快速的开发单个微服务

约定大于配置!

Spring Cloud:(微服务的整合)基于SpringBoot实现的

因为现在大多数公司都在使用Spring Boot进行快速开发,学习SpringBoot 的前提,需要完全掌握Spring及SpringMVC! Spring是一个承上启下的作用!!

弊端:Spring发展的太久之后,违背了原来的理念! 配置十分繁琐,人称:“配置地狱!”

5. IOC理论推导

  1. UserDao接口

  2. UserDaoImpl实现类

  3. UserService业务接口

  4. UserServiceimpl业务实现类

因为用户的每一次需求,要去更改原有的代码,这不是一个好的程序,程序要适应用户的变更

在我们之前的业务中,用户的需求可能会影响我们原来的代码,我们需要根据用户的需求去修改原代码!如果程序代码量十分大,修改一次的成本十分昂贵!

我们使用一个Set接口实现,

private UserDao userDao;
   //利用set进行动态实现值的注入!
   public void setUserDao(UserDao userDao) {
       this.userDao = userDao;
  }

 

之前,程序是主动创建对象,控制权在程序员手上!

使用了set注入后,程序不再具有主动性,而是变成了被动的接收对象

这种思想,从本质上解决了问题,我们程序员不用再去管理对象的创建了,系统的耦合性大大降低,可以更加专注的在业务的实现上了!这是ioc的原型!

IOC本质

控制反转是一种设计思想,DI是实现IOC的一种方法,也有人认为DI只是IOC的另一种说法。没有IOC的程序中,我们使用面向对象编程,对象的创建是由程序自己控制,控制反转后将对象转移给第三方,个人认为所谓的控制反转就是:获得依赖对象的方式反转了。

采用XML方式配置Bean的时候,Bean的定义信息是和实现分离的,而采用注解的方式可以把两者合为一体按,Bean的定义信息是直接以注解的形式定义在实现类中,从而达到了零配置的目的。

控制反转是一种通过描述(XML或注释)并通过第三方去生产或者获取特定对象的方式,在Spring中实现控制反转的是IOC容器,其实现的方法是依赖注入(Dependency Injection,DI)

6. HelloSpring

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

配置spring 来管理

思考问题

hello对象是有谁创建的?

hello对象是由Spring创建的

<!--使用Spring来创建对象,在Spring中这些都称为Bean
  之前是 类型 变量名 =new 类型();
  Hello hello=new Hello();
  id=变量名
  class=new 的对象;
  property 相当于给对象中的属性设置一个值!

    bean==对象  new Hello();
-->
   <bean id="hello" class="com.kuang.pojo.Hello">
      <property name="str" value="Spring"/>

   </bean>

原先是主动去new一个对象,现在是利用容器(beans)来做,我们来给他配置一下就可以,容器随意性比较大,用的时候去容器里面拿就可以

Hello对象的属性是怎么设置的?

Hello对象的属性是由Spring容器设置的。

这个过程就叫控制反转:

控制:谁来控制对象的创建,传统应用程序是由对象本身控制创建的,使用Spring后,对象是由Spring来创建的

反转:程序本身不创建对象,而变成被动的接收对象

依赖注入:就是利用set方法来进行注入的

public void setStr(String str) {
       this.str = str;
  }

必须要使用set方法

IOC是一种编程思想,是由主动的编程变成被动的接收

可以通过newClassPathXmlApplicationContext去浏览一下底层源码

到了现在,我们彻底不用在程序中去改动了,要实现不同的操作,只需要在xml配置文件中进行修改,所谓的ioc,一句话搞定,对象由Spring来创建,管理,装配!

7. IOC 创建对象的方式

  1. 使用无参构造创建对象,默认实现方式!

    <bean id="user" class="com.kuang.pojo.User">
         <property name="name" value="msh"/>
     </bean>
    在xml文档中写属性  

     

    public class User {
       private String name;
       public User(){
           System.out.println("User的无参构造!");
    //       只要new了他,他就会存在!
      }
       
       public String getName() {
           return name;
      }

       public void setName(String name) {
           this.name = name;
      }
       public void show(){
           System.out.println("name="+name);
      }

     

  2. 假设我们要使用有参构造来实现,

    1. 下标赋值

      在User类写入有参构造

      public User(String name){
             this.name=name;
        }

      在XML中写入

      <!--第一种方式:下标赋值-->
       <bean id="user" class="com.kuang.pojo.User">
           <constructor-arg index="0" value="狂神说Java">

           </constructor-arg>

       

    2. 通过参数的类型去匹配

      <!--第二种方式:通过参数类型去创建-->
         <!--不建议使用-->
         <bean id="user" class="com.kuang.pojo.User">
             <constructor-arg type="java.lang.String" value="msh"/>
         </bean>

       

    3. 直接通过参数名来设置(比较常用)

      <bean id="user" class="com.kuang.pojo.User">
           <constructor-arg name="name" value="msh"/>
         </bean>

      总结:在配置文件加载的时候,容器中管理的对象就已经初始化了

8. Spring配置

8.1.别名

<!--别名:如果添加了别名,我们也可以使用别名或缺到这个对象-->
   <alias name="userT" alias="t"/>

 

8.2.Bean配置

<!--
   id:bean 的唯一标识符,也就是我们相当于学过的对象名
   class:Bean对象所对应的全限定名:包名+类型
   name:也是别名,而且name可以同时取多个别名
   -->
   <bean id="userT" class="com.kuang.pojo.UserT" name="user u2,u3;u4">
       <constructor-arg name="name" value="秦疆"/>
   </bean>

 

8.3.import

这个import,一般用于团队开发使用,它可以将多个配置文件,导入合并为一个

假设,现在项目中有多个人开发,这三个人负责不同的类开发,不同的类需要注册在不同的bean中,我们可以利用import将所有人的beans.xml合并为一个总的!

张三

李四

王五

applicationContext.xml

<import resource="beans.xml"></import>
   <import resource="beans2.xml"></import>
<import resource="beans3.xml"></import>

最后使用的时候,直接使用总的配置就可以了!

9. DI 依赖注入

9.1 构造器注入

9.2 set方式注入【重点】

依赖注入:set注入!

依赖:bean对象的创建依赖于容器

注入:bean 对象中的所有属性,由容器来注入!

【环境搭建】

  1. 复杂类型

    public class Address {
       private String address;

       public String getAddress() {
           return address;
      }

       public void setAddress(String address) {
           this.address = address;
      }
    }

     

  2. 真实测试对象

    public class Student {
       private String name;
       private Address address;
       private String[] books;
       private List<String> hobbys;
       private Map<String,String> card;
       private Set<String> games;
       private String wife;
       private Properties info;

    后面需要加get set方法,toString方法

    1. 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
             https://www.springframework.org/schema/beans/spring-beans.xsd">
         <bean id="student" class="com.kuang.pojo.Student">
             <!--第一种,普通值注入,value-->
             <property name="name" value="秦疆"/>
         </bean>
      </beans>

       

    2. 测试类

      import com.kuang.pojo.Student;
      import com.sun.javafx.fxml.BeanAdapter;
      import org.springframework.context.support.ClassPathXmlApplicationContext;

      public class MyTest {
         public static void main(String[] args) {
             ClassPathXmlApplicationContext context=new ClassPathXmlApplicationContext("beans.xml");

             Student student=(Student)  context.getBean("student");
             System.out.println(student.getName());
        }
      }

       

      完善注入信息

      <bean id="student" class="com.kuang.pojo.Student">
             <!--第一种,普通值注入,value-->
             <property name="name" value="秦疆"/>
      <!--       第二种方法:Bean注入,ref-->
             <property name="address" ref="address"/>
      <!--     数组注入: ref -->
             <property name="books">
                 <array>
                     <value>红楼梦</value>
                     <value>水浒传</value>
                     <value>西游记</value>
                     <value>三国演义</value>
                 </array>
             </property>
      <!--       list注入-->
             <property name="hobbys">
                 <list>
                     <value>听歌</value>
                     <value>敲代码</value>
                     <value>看电影</value>
                     <value>打游戏</value>
                 </list>
             </property>
      <!--       Map注入-->
             <property name="card">
                 <map>
                     <entry key="身份证" value="12345612345567880X"></entry>
                     <entry key="银行卡" value="1218372398783778"></entry>
                 </map>
             </property>
      <!--       set注入-->
             <property name="games">
                 <set>
                     <value>LOL</value>
                     <value>COC</value>
                     <value>BOB</value>
                 </set>
             </property>

      <!--       wife注入 空值住入-->
             <property name="wife">
                 <null/>
             </property>

      <!--       property-->
             <property name="info">
                 <props>
                     <prop key="学号">20190525</prop>
                     <prop key="性别">女</prop>
                     <prop key="username">小明</prop>
                     <prop key="password">123456</prop>
                 </props>
             </property>
         </bean>

       

9.3 拓展方式注入

我们可以使用C命名空间和p命名空间的注入

官方解释:

使用:

<?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"
      xmlns:c="http://www.springframework.org/schema/c"
      xsi:schemaLocation="http://www.springframework.org/schema/beans
       https://www.springframework.org/schema/beans/spring-beans.xsd">

   <!--p命名空间注入/set注入-->
   <bean id="use" class="com.kuang.pojo.User" p:name="dong" p:age="10">
   </bean>

   <!--c命名空间/构造器-->
   <bean id="use2" class="com.kuang.pojo.User" c:name="kun" c:age="19"></bean>
</beans>

 

 

测试:

@Test
   public void test2(){
       ApplicationContext context=new ClassPathXmlApplicationContext("Userbeans.xml");
      User user= (User) context.getBean("user",User.class);
       System.out.println(user);
  }

注意点:

p命名和C命名不能直接使用,需要导入xml约束!

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

9.4 Bean的作用域

image-20221015155714189

bean的6种作用域

重点掌握 前两种

  1. 单例模式(默认):signleton是Spring容器默认的作用域,当Bean的作用域为singleton时,Spring容器只为Bean创建一个实例,该实例可以重复使用

<bean id="use2" class="com.pojo.User" c:name="kun" c:age="19" scope="singleton"></bean>
  1. 原型模式(prototype):每次从容器中get的时候,都产生一个新对象!

当bean 的作用域为prototype时,每次对bean请求时都会创建一个新的实例,Spring容器只负责创建bean实例而不再管理其他生命周期

<bean id="use2" class="com.pojo.User" c:name="kun" c:age="19" scope="prototype"></bean>
  1. 其余的request、session、application 这些个只能在web 开发中使用到

10. bean的自动装配

自动装配是Spring满足bean 依赖的一种方式

spring会在上下文总自动寻找,并自动给bean装配属性!

在spring中有三种自动装配的方式

  1. 在xml中显示的配置

     

  2. 在java中显示的配置

  3. 隐式的自动装配bean[重点]

    10.1 测试

    环境搭建:一个人有两个宠物

     

    10.2 ByName自动装配

    <!-- byName:会自动在容器的上下文中查找,和自己对象set方法后面的值对象的beanid!-->
       <bean id="people" class="com.kuang.pojo.People" autowire="byName">
           <property name="name" value="小狂神啊" />
       </bean>

    10.3 ByType自动装配

     <bean class="com.kuang.pojo.Cat"></bean>
       <bean class="com.kuang.pojo.Dog"></bean>
       <!--
       byName:会自动在容器的上下文中查找,和自己对象set方法后面的值对象的beanid!
       byName:会自动在容器的上下文中查找,和自己对象属性类型相同的bean!
       -->
       <bean id="people" class="com.kuang.pojo.People" autowire="byName">
           <property name="name" value="小狂神啊" />
       </bean>

     

小结:

  1. byname的时候,需要保证所有bean的id唯一,并且这个bean需要和自动注入的属性的set方法的值一致!

  2. bytype的时候,需要保证所有bean的class唯一,并且这个bean需要和自动注入的属性的类型一致!

    10.4 使用注解实现自动装配

    jdk1.5支持的注解,spring2.5支持的注解

    The introduction of annotation-based configuration raised the question of whether this approach is “better” than XML.

    要使用注解须知:

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

         <context:annotation-config/>

      </beans>

      @Autowired

      直接在属性上使用即可! 也可以在set方式上使用

      使用Autowired 我们可以不用编写set 方法了,前提是你这个自动装配的属性在ioc(Spring) 容器中存在,且符合名字byname!

      科普:

      @Nullable 字段标记了这个注解,说明这个字段可以为null;

      小结:

      @Resource和@Autowired的区别:

      都是用来自动装配的,都可以放在属性字段上

      @Autowired通过byType的方式实现,而且必须要求这个对象存在!【常用】

      @Resource默认通过byname的方式实现,如果找不到名字,则通过byType 实现!如果两个都找不到的情况下,就报错【常用】

      执行顺序不同:@Autowired通过Type的方式实现,@Resource默认通过byname的方式实现。

      11.Spring的注解开发

      在Spring4之后,要使用注解开发,必须要保证aop 的包导入了

      在使用注解需要导入Context约束,增加注解的支持!

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

         <context:annotation-config/>

      </beans>

       

       

      1. bean

      2. 属性如何注入

      @Component
      public class User {
         //相当于<property name="name" value="kaungshen"/>
         @Value("kuangshen")
         public String name ;

         public void setName(String name) {
             this.name = name;
        }
         
      //简单的可以使用注解,一些复杂的还是建议走配置文件
         //类似于DI注入的东西比较多,配置文件更快,更清晰
         //如果提供了set方法
      }

       

      3. 衍生的注解

      @Component有几个衍生注解,我们在web开发中,会按照mvc三层架构分层!!

      dao---【@Repository】

      service---【@Service】

      controller---【@Controller】

      这四个注解的功能都是一样的,都是代表将某个类注册到Spring容器中,装配Bean!

      4. 自动装配置

      注解说明:

      @Autowired:自动装配通过类型,名字

      如果Autowired 不能唯一自动装配上属性,则需要通过@Qualifier(value=“XXX")

      @Nullable:字段标记了这个注解,说明这个字段可以为null;

      @Resource:自动装配通过名字,类型

      @Component:组件,放在类上,说明这个类被spring 管理了,就是bean!

      5. 作用域

      @Component
      @Scope("singleton")
      //相当于<bean scope="singleton">
      public class User {
         //相当于<property name="name" value="kaungshen"/>
         @Value("kuangshen")
         public String name ;


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

      注解就是将配置文件中的一些配置变得简单

      6. 小结

      xml和注解的区别:

      xml与注解:

      xml更加万能,适用于任何场合!维护简单方便

      注解不是自己的类使用不了,维护相对复杂!

xml与注解的最佳实践:

xml用来管理bean

注解只负责完成属性的注入;

我们在使用的过程中,就需要注意一个问题,必须让注解生效,就需要开启注解的支持!

<!--指定要扫描的包,这个包下的注解就会生效-->
   <context:component-scan base-package="com.kuang"/>
   <context:annotation-config/>

经典面试题1:在Spring中使用xml配置与注解配置的区别:

  1. 适用场景:

    xml配置场景:

    ✔bean实现类来源于第三方类库,如DataSource、JdbcTemplate等,因为无法在类中标注注解,所以通过XML配置方式较好;

    ✔命名空间的配置,如aop、context等,只能采用基于XML的配置

    基于注解配置适用情况:

    Bean的实现类是当前项目开发的,可以直接在Java类中使用基于注解的配置。

     

  2. 两者优缺点:

    XML的优点:

    1. XML配置方式进一步降低了耦合,使得应用更加容易扩展,即使对配置文件进一步修改也不需要工程进行修改和重新编译。

    2. 在处理大的业务量的时候,用XML配置应该更加好一些。因为XML更加清晰的表明了各个对象之间的关系,各个业务类之间的调用。同时spring的相关配置也能一目了然。

      3、利用 xml 配置能使软件更具扩展性。例如 Spring 将 class 间的依赖配置在 xml 中,最大限度地提升应用的可扩展性。

      4.具有成熟的验证机制确保程序正确性。利用 Schema 或 DTD 可以对 xml 的正确性进行验证,避免了非法的配置导致应用程序出错。

    缺点:

    需要解析工具或类库的支持。

    解析 xml 势必会影响应用程序性能,占用系统资源。

    配置文件过多导致管理变得困难。

    编译期无法对其配置项的正确性进行验证,或要查错只能在运行期。

    查错变得困难。往往配置的一个手误导致莫名其妙的错误。

    开发人员不得不同时维护代码和配置文件,开发效率变得低下。

    Annotation的优点:

    1. 在class文件中,可以降低维护成本,annotation的配置非常简单。

    2. 不需要第三方的解析工具,利用java反射技术就可以轻松获取been。

    3. 编辑器会实时校验错误,如果注解错误会有提醒。

    4. 不用维护xml配置文件,提高了开发效率。

    缺点:

    如果需要对于annotation进行修改,那么要重新编译整个工程。

    在程序中Annotation比较多,直接影响代码质量,对于代码的简洁度有一定的影响。

 

11.使用Java的方式配置Spring

我们现在要完全不使用Spring 的xml配置了,全权交给Java来做!

JavaConfig是Spring的一个子项目,在Spring4 之后,他成为了一个核心功能

备注:报错1
No bean named 'getuser' available

找不到getuser这个变量,多半因该是<bean>里的id的名字和getBean("id名字")不一致。

实体类:

//这个注解的意思,就是说明这个类被Spring接管了,注册到了容器中
@Component
public class User {
   private  String name;

   public String getName() {
       return name;
  }
   @Value("QINJIANG")//属性注入值
   public void setName(String name) {
       this.name = name;
  }

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

配置文件:

package com.kuang.config;

import com.kuang.pojo.User;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;

//这个也会被Spring容器托管,注册到容器中,因为他本来就是一个@Component,
// @Configuration代表这是一个配置类,就和我们之前看到的beans.xml一样
@Configuration
@ComponentScan("com.kuang")//扫描包
@Import(KuangConfig2.class) //导入KuangConfig2
public class KuangCongfig {

   //注册一个bean,就相当于我们之前写的一个bean标签,
   // 这个方法的名字就相当于bean标签中的id属性,
   // 这个方法的返回值,就相当于bean标签中的class属性
   @Bean
   public User getUser(){
       return new User();//就是返回要住的到bean的对象!
  }

}

测试类!

import com.kuang.config.KuangCongfig;
import com.kuang.pojo.User;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

public class MyTest {
   public static void main(String[] args) {
       //如果完全使用了配置类方式去做,我们就只能通过AnnotationConfig上下文来获取容器,通过配置类的class对象加载!
       ApplicationContext context=new AnnotationConfigApplicationContext(KuangCongfig.class);
       User getUser=(User) context.getBean("getUser");
       //name里面的名字要和方法名一值;方法名就是bean的名字
       System.out.println(getUser.getName());
  }
}

这种纯Java的配置方式,在SpringBoot中随处可见!

12. 代理模式

为什么要学习代理模式?(就相当于中介)因为这就是SpringAOP的底层!【SpringAOP和SpringMVC】面试必考

代理模式的分类:

静态代理

动态代理

image-20221016102507136

1. 静态代理
1. 角色分析

抽象角色: 一般会使用接口或者抽象类来解决

真实角色:被代理的角色

代理角色:代理真实角色,代理真实角色后,我们一般会做一些附属操作

客户:访问代理对象的人!

代码步骤
  1. 接口

    package com.kuang.demo01;

    //租房
    public interface Rent {
       public void rent();
    }

     

  2. 真实角色

    package com.kuang.demo01;

    //房东
    public class Hont implements Rent{
      public void rent(){
          System.out.println("房东要出租房子!");
      }
    }

     

  3. 代理角色

    package com.kuang.demo01;

    //代理角色
    //代理角色:第一件事:帮房东租房子
    //原则:多用组合,少用继承
    public class Proxy implements Rent{
       private Hont hont;

       public Proxy(){

      }

       public Proxy(Hont hont) {
           this.hont = hont;
      }
       public void rent(){
           setHouse();
           fare();
           hetorg();
           hont.rent();
      }
       //看房
       public  void setHouse(){
           System.out.println("中介带你看房!");
      }
       //收中介费
       public  void fare(){
           System.out.println("收中介费!");
      }
       //签租赁合同
       public  void hetorg(){
           System.out.println("签租赁合同!");
      }

     

  4. 客户端访问代理角色

    public class Client {
       public static void main(String[] args) {
           //房东要租房子
           Hont hont=new Hont();
           //代理,中介帮房东租房子,但是呢?代理一般会有一些附属操作!
           Proxy proxy=new Proxy(hont);

           //你不用面对房东,直接找中介租房即可!
           proxy.rent();
      }
    }

     

静态代理模式的好处!

耦合性降低了

可以使真实角色的操作更加纯粹! 不用关注一些公共的业务

公共也就交给代理角色!实现业务的分工!

公共业务发生扩展的时候,方便集中管理!

静态代理模式的缺点!

一个真实角色就会产生一个代理角色:代码量会翻倍~ 开发效率会变低

加深理解

在不改变原有模式的情况下, 想要扩展一个东西,代理模式就很好用!

聊聊AOP

image-20221016150655979

在原有类不动的情况下,想要增加一个类,为什么不能在原有的类上加?

改动原有的业务代码,在公司中是大忌!

  1. 接口

package com.kuang.demo02;

public interface UserService {
   public void add();
   public void delete();
   public void update();
   public void query();
}

 

  1. 代理角色

    package com.kuang.demo02;
    //代理角色
    public class UserServiceProxy implements UserService{
    //将真实角色丢进来
       private UserServiceImpl userService;

       public void setUserService(UserServiceImpl userService) {
           this.userService = userService;
      }

       public UserServiceProxy(){

      }
       @Override
       public void add() {
           log("add");
          userService.add();
      }

       @Override
       public void delete() {
           log("delete");
        userService.delete();
      }

       @Override
       public void update() {
           log("update");
        userService.update();
      }

       @Override
       public void query() {
           log("query");
        userService.query();
      }
       //日志方法
       public void log(String msg){
           System.out.println("[Deubg]使用了"+msg+"方法");
      }
    }

     

  2. 真实角色

    package com.kuang.demo02;

    public class UserServiceImpl implements UserService{

       public void add() {
           System.out.println("增加了一个用户");
      }
       public void delete(){
           System.out.println("删除了一个用户");
      }
       public void query(){
           System.out.println("修改了一个用户");
      }
       public void update(){
           System.out.println("查询了一个用户");
      }

    }
    1. 客户端调用代理角色

    package com.kuang.demo02;

    public class Client {
       public static void main(String[] args) {
           UserServiceImpl userService=new UserServiceImpl();
           //调用代理
           UserServiceProxy proxy=new UserServiceProxy();
           proxy.setUserService(userService);

           proxy.query();
      }
    }

     

面向对象七大原则[谈资]

  • 开闭原则(OCP) Open-Closed Principle

    • 总纲,告诉我们要对扩展开放,对修改关闭
    • 即软件实体应该尽量在不修改原有代码上进行扩展
    • 实现:通过"抽象约束、封装变化"来实现开闭原则,即通过接口或者抽象类为软件实体定义个相对稳定的抽象层,而将相同的可变因素在相同的具体实现类中
  • 里氏替换原则(LSP)Liskov Subsitution Prinicple

    • 告诉我们不要破坏继承关系
    • 所有引用(基类)父类的地方能够透明的引用其子类
    • 任何基类可以出现的地方,子类一定可以出现,里氏替换原则是继承复用的基石。即把基类换成子类,程序也不会出错
    • 实现:子类继承父类时,添加新的反复完成新增功能外,尽量不要重写父类的方法
  • 依赖倒置原则(DIP)Dependence Inversion Principle

    • 抽象不应该依赖于细节,细节应该依赖于对象
    • 针对接口编程,而不是针对实现编程
    • 告诉我们要面向接口编程
  • 单一职责原则(SRP)Single Responsibility Principe

    • 一个类只负责一个功能领域中的相应职责,或者定义为就一个类而言,应该只有一个引起它变化的原因
    • 单一职责原则就是实现高内聚,低耦合的指导方针,它是最简单但又最难以运用的原则
    • 告诉我们实现来要职责单一;
    • 实现:一个类只负责一项职责,一个方法只负责处理一项事情。
  • 接口隔离原则(ISP) Interface Segregation Principle

    • 告诉我们在设计接口的时候要精简单一;

    • 实现:客户端不应该被迫依赖于它不使用的方法。

    • 和单一职责原则的区别: 它和单一职责原则差不多,一个接口只服务于一个子模块或业务逻辑。只是单一职责是侧重于约束类和方法。而借口隔离侧重约束接口。

  • 合成复用原则(CRP) Composite Reuse Principle[组合/聚合复用原则]

    • 优先使用组合或者聚合关系复用,少用继承关系复用
    • 尽量使用对象组合,而不是继承来达到复用的目的
    • 相对于继承复用而言,其耦合性相对较低,成员对象对新对象影响不大。可动态运行。
    • 服用时尽量减少组合/聚合关系,少用继承。
    • 实现:合成复用原则是通过将已有的帝乡纳入新对象中,作为新对象的成员对象来实现的,先对象可以调用已有对象的功能,从而达到复用
  • 迪米特法则(LoD) Law of Demeter[又称为最少知识法则]

    告诉我们要降低耦合度;

    一个软件实体应当可能少地与其他实体发生相互作用;

    实现:如果两个软件实体无需直接通信,那么久不应当发生直接的相互调用,可以通过第三方转发该调用

 

2. 动态代理

动态代理和静态代理角色一样

动态代理的代理类是动态生成的,不是我们直接写好的!

动态代理分为两大类:基于接口的动态代理,基于类的动态代理

基于接口---(最经典的)JDK的动态代理

基于类的:cglib

java字节码实现:javassist-->能够快速简单的改变类的结构,或者生成动态类

需要了解两个类:proxy,invocationHandler:调用处理程序

InvocationHandler是由代理实例的调用处理程序实现的接口

每一个代理实例都有一个关联的调用处理程序,当在代理实例上调用方法时,方法调用将被编码分派到其调用处理程序的invoke 方法

proxy:提供了创建动态代理类和实例的静态方法,它是由这些方法创建的所有动态代理类的超类。

动态代理的好处

可以使真实角色的操作更加纯粹! 不用关注一些公共的业务

公共也就交给代理角色!实现业务的分工!

公共业务发生扩展的时候,方便集中管理!

一个动态代理类代理的是一个接口,一般就是对应的一类业务!

一个动态代理类可以代理多个类,只要是实现了同一个接口即可!

通用的代理类

通过反射可以做很多的事情,反射属于底层

package com.kuang.demo04;
import com.kuang.demo03.Rent;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

//我们会用这个类,自动生成代理类!
public class ProxyInvocationHandler implements InvocationHandler {
   //被代理的接口
   private Object target;

   public void setTarget(Object target){
       this.target=target;
  }
   
   //生成得到代理类
   public Object getProxy() {
       return Proxy.newProxyInstance(this.getClass().getClassLoader(),
               target.getClass().getInterfaces(), this);

  }
 
   //处理代理实例,并返回结果(核心)
   public Object invoke(Object proxy, Method method,Object[] args)throws Throwable{
       //上句话生成了三个方法
      //动态代理的本质,就是使用反射机制实现!
      log(method.getName());//动态的进行增删改查
       Object result=method.invoke(target,args);

       return result;
  }
   //增加一个方法,增加一个日志的功能
   public void log(String msg){
       System.out.println("执行了"+msg+"方法");
  }
   
}

13.AOP

1. 什么是AOP:

AOP(Aspect Oriented Programming) 面向切面编程,通过预编译模式和运行期动态dialing实现程序功能的统一维护的一种技术。AOP是OOP的延续,是软件开发中的一个热点,也是Spring框架中的一个重要内容,是函数式编程的一种衍生范型。利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可用性,同时提高了开发的效率

image-20221016162609624

2. AOP在Spring中的作用

提供声明式事务;允许用户自定义切面

横切关注点:跨越应用程序多个模块的方法或者功能。即是,与我们业务无关的,但是我们需要关注的部分,就是横切关注点。如日志、安全、缓存、事务等...

切面(ASPECT):横切关注点被模块化的特殊对象,即,他是一个类

通知(Advice):切面必须要完成的工作,即,它是类中的一个方法

目标(Target): 被通知对象

代理(Proxy):向对象应用通知之后创建的对象[即生成的代理类]

切入点(PointCut):切面通知执行的“地点”的定义

连接点(JoinPoint):与切入点匹配的执行点

image-20221016163420480

3.使用Spring实现AOP

【重点】使用AOP织入,需要导入一个依赖包!

<dependencies>
   <dependency>
       <groupId>org.aspectj</groupId>
       <artifactId>aspectjweaver</artifactId>
       <version>1.9.4</version>
   </dependency>
</dependencies>

方式一:使用Spring的API接口[主要是springAPI接口实现]

applicaitonContext.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:aop="http://www.springframework.org/schema/aop"
      xsi:schemaLocation="http://www.springframework.org/schema/beanss
       https://www.springframework.org/schema/beans/spring-beans.xsd
       http://www.springframework.org/schema/aop
       https://www.springframework.org/schema/aop/spring-aop.xsd">

   <!--注册bean-->
   <bean id="userservice" class="com.service.UserServiceImp"></bean>
   <bean id="log" class="com.log.Log"/>
   <bean id="afterlog" class="com.log.AfterLog"/>

   <!--配置aop-->
   <aop:config>
       <!--切入点:expression:表达式,execution(要执行的位置)-->
       <aop:pointcut id="point" expression="execution(* com.service.UserServiceImp.*(..))"/>
       <!--执行环绕-->
       <aop:advisor advice-ref="log" pointcut-ref="point"/>
       <aop:advisor advice-ref="afterlog" pointcut-ref="point"/>
   </aop:config>

</beans>

UserServiceImpl类

package com.kuang.service;

public class UserServiceImpl implements UserService {

   @Override
   public void add() {
       System.out.println("增加了一个用户");
  }

   @Override
   public void delete() {
       System.out.println("删除了一个用户");
  }

   @Override
   public void update() {
       System.out.println("更新了一个用户");
  }

   @Override
   public void select() {
       System.out.println("查询了一个用户");
  }
}

log类

import org.springframework.aop.MethodBeforeAdvice;

import java.lang.reflect.Method;

public class Log implements MethodBeforeAdvice {
   //method:要执行的目标对象的方法
   //args:参数
   //target:目标对象
   public void before(Method method, Object[] args, Object target) throws Throwable {
       System.out.println(target.getClass().getName()+method.getName());
  }
}

AfterLog类

package com.kuang.log;

import org.springframework.aop.AfterAdvice;

import java.lang.reflect.Method;

public class AfterLog implements AfterAdvice {

   //returnValue:返回值
   public void afterReturning(Object returnValue,
                              Method method,Object[] args,
                              Object target)throws Throwable{
       System.out.println("执行了"+
               method.getName()+
               "方法,返回结果为"+returnValue);
  }

}

测试类

import com.kuang.service.UserService;
import com.kuang.service.UserServiceImpl;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class MyTest {
   public static void main(String[] args) {
       ApplicationContext context=new ClassPathXmlApplicationContext("applicationContext.xml");
       //动态代理代理的是接口!!!
         UserService userService=context.getBean("userService", UserServiceImpl.class);

       userService.add();
  }
}

方式二:使用自定义来实现AOP[主要是切面定义]

<!--方式二:自定义类-->
   <bean id="diy" class="com.kuang.diy.DiyPointCut">
    <aop:config>
        <!--自定义切面,ref要引用的类-->
        <aop:aspect ref="diy">
       <!--切入点-->
            <aop:pointcut id="point" expression="exection(com.kuang.service.UserServiceImpl.*(..))"/>
        <!--通知-->
            <aop:before method="before" pointcut-ref="point"/>
            <aop:after method="after" pointcut-ref="point"/>
        </aop:aspect>
    </aop:config>
   </bean>

方式三:使用注解实现!

xml

<!--方式二:自定义类-->
   <bean id="diy" class="com.kuang.diy.DiyPointCut">
    <aop:config>
        <!--自定义切面,ref要引用的类-->
        <aop:aspect ref="diy">
       <!--切入点-->
            <aop:pointcut id="point" expression="exection(com.kuang.service.UserServiceImpl.*(..))"/>
        <!--通知-->
            <aop:before method="before" pointcut-ref="point"/>
            <aop:after method="after" pointcut-ref="point"/>
        </aop:aspect>
    </aop:config>
   </bean>

 

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;

@Aspect  //标注这个类是一个切面
public class Annotation {

   @Before("execution(* com.service.UserServiceImp.*(..))")
   public void before(){
       System.out.println("before");
  }

   @After("execution(* com.service.UserServiceImp.*(..))")
   public void after(){
       System.out.println("after");
  }

   //在环绕增强中,我们可以给地暖管一个参数,代表我们要获取切入的点
   @Around("execution(* com.service.UserServiceImp.*(..))")
   public void around(ProceedingJoinPoint joinPoint) throws Throwable {
       System.out.println("around");

       Object proceed = joinPoint.proceed();

       System.out.println("after around");
  }
}
 
posted @   小门的代码  阅读(165)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 25岁的心里话
· 闲置电脑爆改个人服务器(超详细) #公网映射 #Vmware虚拟网络编辑器
· 零经验选手,Compose 一天开发一款小游戏!
· 因为Apifox不支持离线,我果断选择了Apipost!
· 通过 API 将Deepseek响应流式内容输出到前端
点击右上角即可分享
微信分享提示