Spring学习笔记 之 Spring<全>

开始学习Spring全家桶

文章目录


1. IoC

定义

控制反转(Inversion of Control,缩写为IoC),是面向对象编程中的一种设计原则,可以用来减低计算机代码之间的耦合度。其中最常见的方式叫做依赖注入(Dependency Injection,简称DI),还有一种方式叫“依赖查找”(Dependency Lookup)。IoC 的思想就是将原本在程序中手动创建对象的控制权,交由 Spring 框架来管理


为什么叫控制反转?

  • 控制 :指的是对象创建(实例化、管理)的权力
  • 反转 :控制权交给外部环境(Spring 框架、IoC 容器)

在 Spring 中, IoC 容器是 Spring 用来实现 IoC 的载体, IoC 容器实际上就是个 Map(key,value),Map 中存放的是各种对象。

Spring 时代我们一般通过 XML 文件来配置 Bean,后来开发人员觉得 XML 文件来配置不太好,于是 SpringBoot 注解配置就慢慢开始流行起来。


实现

1. 创建maven工程,在pom.xml中加Spring依赖(为了方便可以加上Lombok)

<dependencies>

        <!--Spring框架依赖-->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>5.2.3.RELEASE</version>
        </dependency>

        <!--Lombok依赖(可有可无)-->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.18.10</version>
        </dependency>

    </dependencies>

注:加入Lombok后,可以直接在类上用@Data/@Getter/@Setter.....等注解隐式封装类,如有个Student类用了Lombok的@Data:

@Data
public class Student {
    private Integer id;
    private String name;
    private Integer age;
}

效果:隐式封装,减少代码量

注意:第一次使用Lombok需要在Settings的Plugins里面安装插件

2.  resources 路径下创建 spring.xml,并添加Bean

  • 简单来说,Bean 代指的就是那些被 IoC 容器所管理的对象
  • 我们需要告诉 IoC 容器帮助我们管理哪些对象,这个是通过配置元数据来定义的。配置元数据可以是 XML 文件、注解或者 Java 配置类。
<?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:p="http://www.springframework.org/schema/p"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.2.xsd
 http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-4.3.xsd
">

    <!--自定义的bean信息,class指定的是 java文件夹路径下的 类的路径-->
    <bean id="student" class="Entity.Student"></bean>
</beans>

3.IoC 容器通过读取 spring.xml 配置⽂件,加载 bean 标签来创建对象

4.调⽤ API 获取 IoC 容器中已经创建的对象

public class Test01 {
    public static void main(String[] args) {
        //传统创建对象方法
//        Student student = new Student();

        //通过IoC创建对象
        //创建IoC容器读取spirng.xml配置文件
        ClassPathXmlApplicationContext applicationContext = 
new ClassPathXmlApplicationContext("spring.xml");

        //IoC容器会根据参数id,读取相应的bean,返回一个Object 类型的对象
        Student student = (Student) applicationContext.getBean("student");

        /*也可以直接传输一个class对象,不过如果xml文件中同一个类配置有两个或以上id,那就会报错
        *这种方式不用强制类型转换
        Student student = applicationContext.getBean(Student.class);
        */

        System.out.println(student);
    }
}

IoC 容器创建 bean 的两种⽅式 

  • 无参构造,类中必须包含有无参构造,默认
<!--原理是先调用无参构造,再使用set方法赋初值,而不是有参构造。初值可有可无-->
<bean id="student" class="Entity.Student">
 <property name="id" value="1"></property>
 <property name="name" value="张三"></property>
 <property name="age" value="22"></property>
</bean>

  • 有参构造,类中必须包含对应参数的有参构造 -- constructor-arg

方式一:按照index赋值,index代表构造器里面参数顺序,0代表第一个参数

<bean id="student3" class="Entity.Student">
 <constructor-arg index="1" value="王五"></constructor-arg>
 <constructor-arg index="0" value="3"></constructor-arg>
 <constructor-arg index="2" value="18"></constructor-arg>
</bean>

方式二:根据name赋值,name代表对应的属性

<bean id="student3" class="Entity.Student">
 <constructor-arg name="name" value="王五"></constructor-arg>
 <constructor-arg name="id" value="3"></constructor-arg>
 <constructor-arg name="age" value="18"></constructor-arg>
</bean>

 IoC DI 

 依赖注入(Dependency Injection,简称DI): Bean 之间的依赖注⼊,设置对象之间的级联关系

场景:有一个Classes班级类,和一个学生类Student,他们之间有级联关系,班级包含很多学生

1. 创建Classes

@Data
public class Classes {
    private Integer id;
    private String name;
    private List<Student> studentList;
}

2. 创建Student

@Data
public class Student {
    private Integer id;
    private String name;
    private Integer age;
    //若Classes中已经有关联信息了,则这里不需要创建关联属性了
    //private Classes classes;
}

3. 新建resources下的新文件spring-di.xml

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

<!--此处省略复制粘贴的必备部分-->

    <!-- Classes -->
    <bean id="classes" class="Entity.Classes">
        <property name="id" value="1"></property>
        <property name="name" value="⼀班"></property>
        <!--关联两个student-->
        <property name="studentList">
            <list>
                <ref bean="student1"></ref>
                <ref bean="student2"></ref>
            </list>
        </property>

    </bean>


    <!-- Student -->
    <bean id="student1" class="Entity.Student">
        <property name="id" value="100"></property>
        <property name="name" value="张三"></property>
        <property name="age" value="22"></property>
        <!--在关联级联关系的时候,不能用value,只能用ref-->
        <!--<property name="classes" ref="classes"></property>-->
    </bean>

    <bean id="student2" class="Entity.Student">
        <property name="id" value="200"></property>
        <property name="name" value="李四"></property>
        <property name="age" value="20"></property>
        <!--如果Classes里面已经有ref,在Student里面就不用加级联信息了,会造成死循环,栈溢出错误-->
        <!--<property name="classes" ref="classes"></property>-->
    </bean>
</beans>

注:bean 之间的级联需要使⽤ ref 属性完成映射,⽽不能直接使⽤ value ,否则会抛出类型转换异常

4. 测试

public class Test01 {
    public static void main(String[] args) {
        //创建IoC容器
        ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring-di.xml");

        //IoC容器创建对象
        Classes classes = (Classes) applicationContext.getBean("classes");
        Student student1 = (Student) applicationContext.getBean("student1");
        Student student2 = (Student) applicationContext.getBean("student2");
    
        //输出
        System.out.println(classes);
        System.out.println(student1);
        System.out.println(student2);
    }
}

5. 结果 


特殊字符的处理

例如  “一班” 要改成 “<一班>”,错误的写法是:

<property name="name" value="⼀班"></property>

org.xml.sax.SAXParseException 错误! 

正确的写法是:

<property name = "name" >
<value> <![CDATA[< ⼀班 >]]> </value>
</property>

Spring 中的bean创建类型 -- scope

bean 是根据 scope 来⽣成,表示 bean 的作⽤域, scope 4 种类型:
  • singleton,单例,表示通过 Spring 容器获取的对象是唯⼀的默认值
  • prototype,原型,表示通过 Spring 容器获取的对象是不同的
  • request,请求,表示在⼀次 HTTP 请求内有效
  • session,会话,表示在⼀个⽤户会话内有效

注:requset,session 适⽤于 Web 项⽬

singleton 模式,只要加载 IoC 容器,⽆论  IoC 是否 取出 bean ,配置⽂件中的 bean 都会被创
建,即都会调用构造函数
ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.xml");
        Student stu1 = applicationContext.getBean(Student.class);
        Student stu2 = applicationContext.getBean(Student.class);

        //true
        System.out.println(stu1 == stu2);
prototype 模式,如果不从 IoC 中取 bean ,则不创建对象,取⼀次 bean ,就会创建⼀个对象
<bean id = "student" class = "Entity.Student scope = "prototype" ></property>

Spring 的继承 -- parent

  • Spring 继承不同于 Java 中的继承,区别:Java 中的继承是针对于类的,Spring 的继承是针对于对象
  • Spring 的继承中,⼦ bean 可以继承⽗ bean 中的所有成员变量的值,不需要类间继承,只要成员变量一致即可

1. 新建一个User类:比Student多了一个sex属性(只能相同或者多

@Data
public class User {
    private Integer id;
    private String name;
    private Integer age;
    private Integer sex;
}

2. 配置Sping.xml文件,对User的Bean标签加上 parent

<bean id="student" class="Entity.Student">
        <property name="age" value = "12"></property>
        <property name="id" value = "01"></property>
        <property name="name" value = "张三"></property>
    </bean>

    <!--user 继承 student-->
    <bean id="user" class="Entity.User" parent="student">
        <property name="name" value = "李四"></property>
        <property name="sex" value="0"></property>
    </bean>

3. IoC容器创建这两个对象,并且打印信息,得到结果

标签配置的数值  覆盖  继承的数值

Spring 的依赖 -- depends-on

  • ⽤来设置两个 bean 的创建顺序
  • IoC 容器默认情况下是通过 spring.xml 中 bean 的配置顺序来决定创建顺序的,配置在前⾯的 bean 会 先创建
  • 在不更改 spring.xml 配置顺序的前提下,通过设置 bean 之间的依赖关系来调整 bean 的创建顺序
<bean id="student" class="Entity.Student" depends-on="user"></bean>

<bean id="user" class="Entity.User"></bean>
上述代码的结果是 先创建 User,再创建 Account

Spring 读取外部资源 -- context:property-placeholder

场景:实际开发中,数据库的配置⼀般会单独保存到后缀为 properties 的⽂件中,⽅便维护和修改,如果使⽤ Spring 来加载数据源,就需要在 spring.xml 中读取 properties 中的数据,这就是读取外部资源

<beans>头标签需要加上:

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

1. resources文件夹下新建jdbc.properties
user = root
password = 000000
url = jdbc:mysql://localhost:3306/test
driverName = com.mysql.cj.jdbc.Driver

 2. 创建DataSource类

@Data
public class DataSource {
    private String user;
    private String password;
    private String url;
    private String driverName;
}

3. 配置spring-properties.xml文件

<?xml version="1.0" encoding="UTF-8"?>
<!-- 此处省略复制粘贴部分-- >


    <!--导入文件-->
    <context:property-placeholder location="jdbc.properties"></context:property-placeholder>

    <!--spring EL表达式 :${变量名}-->
    <bean id ="dataSource" class="Entity.DataSource">
        <property name="user" value="${user}"></property>
        <property name="password" value="${password}"></property>
        <property name="url" value="${url}"></property>
        <property name="driverName" value="${driverName}"></property>
    </bean>

</beans>

4. IoC创建对象及其输出结果:


Spring p 命名空间

p可以简化bean对象属性的赋值,包括级联关系的映射

使用p命名需要在<beans>头标签加上:

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

以Student和 Class举例,配置spring-p.xml:

<bean id = "student" class = "Entity.Student" p:id = "1" p:name = " 张三 "
                p:age = "22" p:classes-ref = "classes" > </bean>
<bean id = "classes" class = "Entity.Classes" p:id = "1" p:name = " ⼀班 " > </bean>

Spring工厂方法

  • 静态工厂方法

1. 创建Car类

@Data
@AllArgsConstructor
public class Car {
    private Integer num;
    private String brand;
}

2. 创建StaticCarFactory类

public class StaticCarFactory {
    //value一定为Car , key可以根据序号或者品牌名字都行
    private static Map<Integer,Car> carMap;
    
    //静态块,类加载的时候就运行
    static {
        carMap = new HashMap<Integer, Car>();
        carMap.put(1, new Car(1,"奥迪"));
        carMap.put(2, new Car(2,"奥拓"));
    }

    //静态方法
    public static Car getCar(Integer num){
        return carMap.get(num);
    }
}

3.配置spring-factory.xml 

     <!--    此为1号汽车的bean对象-->
    <bean id ="car1" class="Entity.StaticCarFactory" factory-method="getCar">
        <constructor-arg value="1"/>
    </bean>

factory-method要指定一个静态方法,constructor-arg 指定一个参数 

  • 实例工厂方法

1. 创建InstanceFactory类

@Data
@AllArgsConstructor
public class InstanceFactory {
    private Map<Integer,Car> carMap;
    
    public InstanceFactory(){
        carMap = new HashMap<Integer, Car>();
        carMap.put(1,new Car(1,"奥迪"));
        carMap.put(2,new Car(2,"奥拓"));
    }
    
    public Car getCar(Integer num){
        return carMap.get(num);
    }
}

2.配置spring-factory.xml

<!--需要实例化工厂-->
    <bean id="instanceFactory" class="Entity.InstanceFactory"></bean>
    
    <!-- 通过工厂方法创建对象-->
    <bean id="car2" factory-bean="instanceFactory" factory-method="getCar">
        <constructor-arg type="java.lang.Integer" value="2"/>
    </bean>

3.测试

Car car2 = (Car) applicationContext.getBean("car2");


静态工厂和实例工厂区别

  • 静态工厂不需要实例化工厂对象,spring.xml 中 是class + factory-method 的形式
  • 实例工厂需要实例化工厂对象,spring.xml是factory-bean + factory-method 的形式

SpringIoC自动装载autowire

不需要配置property,更加简便方式完成DI,IoC容器自动选择bean注入

自动装载两种方式:

  • byName,通过属性名完成自动装载
  • byType,通过属性对应的数据类型完成自动装载

byName:属性名 和 bean id 要一致

1. 创建Person类  (注意:Car 类型的属性名为car1)

@Data
public class Person {
    private Integer Id;
    private String name;

    //这里属性名为car1
    private Car car1;
}

2. 创建Spirng-autowire,配置Car 和 Person的 bean,自动DI

 <!--    bean标签的id也要为car1-->
    <bean id ="car1" class="Entity.Car" >
        <property value="1" name="num"/>
        <property value="奥迪" name="brand"/>
    </bean>
    
    <!--    配置autowire为byName-->
    <bean id="person" class="Entity.Person" autowire="byName">
       <property name="id" value="1"></property>
       <property name="name" value="张三"></property>
    </bean>

byType:要求Car bean只有一个,才能自动装载

<bean id="person" class="Entity.Person" autowire="byType">
 <property name="id" value="1"></property>
 <property name="name" value="张三"></property>
</bean>

<bean id="car2" class="Entity.Car">
 <constructor-arg name="num" value="1"></constructor-arg>
 <constructor-arg name="brand" value="奥迪"></constructor-arg>
</bean>

Spring IoC 基于注解的开发

Spring IoC 的作⽤是帮助开发者创建项⽬中所需要的 bean,同时完成 bean 之间的依赖注⼊关系

实现该功能有两种⽅式:

  • 基于 XML 配置
  • 基于注解

基于注解有两步操作,缺⼀不可:

1、xml配置⾃动扫包

<context:component-scan base-package="Entity"> </context:component-scan>

2、在对应的类添加@Component注解

@Data
@Component(value = "student")  //这个value就是bean id ,默认的也是首字母小写
public class Student {
    private Integer age;
    private String name;
}

以上步骤相当于在xml中配置如下,(使用的时候直接getBean就行)

<bean id="student" class="Entity.Student"> </bean>

DI 注解

1.给DateSource成员赋值    用@Value

@Data
@Component(value = "ds")
public class DataSource {
     @Value("root")
     private String user;

     @Value("root")
     private String password;

     @Value("jdbc:mysql://localhost:3308/library")
     private String url;

     @Value("com.mysql.cj.Driver")
     private String driverName;
}

2.给Repostory中的DataSource类型的成员加上@Autowired

@Data
@Component(value = "myrepo")
public class Repository {
    
     @Autowired          //表示有级联关系
     private DataSource dataSource;
}

注:@Autowired 默认是通过 byType 进⾏注⼊的,如果要改为 byName,需要配置 @Qualifier 注解来完成

@Autowired
@Qualifier(value = "ds")
private DataSource dataSource;

以上就完成了DI的注解操作!


实际开发的使⽤

实际开发中我们会将程序分为三层:

  • Controller
  • Service
  • Repository(DAO)

关系: Controller 调用Service;Service 调用Repository;Repository访问数据库

层层调用关系
  • 实际开发中可以根据业务需求分别使用@Controller、@Service、@Repository 注解来代替@Component标注控制层类、业务层类、持久层类

举一个层层调用的例子:

  • Controller
@Setter
@Controller            //代替@Component
public class MyController {

    @Autowired        //自动DI
    private MyService myService;

    //模拟客户端请求 -> 调用sevice层接口
    public String service(Double score){
        return myService.doService(score);
    }
}
  • Service
------------------------函数式接口-------------------------
//不能加@Component,因为不能实例化接口
public interface MyService {
    public String doService(Double score);
}


---------------------实现类------------------------------
@Setter
@Service
public class MyServiceImpl implements MyService{

    @Autowired     //自动DI
    private MyRepository myRepository;

    public String doService(Double score) {
        return myRepository.doRepository(score);
    }
}
  • Repository
--------------------------接口------------------------
//不能加@Component,因为不能实例化接口
public interface MyRepository {
    public String doRepository(Double score);
}

-------------------------实现类-----------------------
@Repository
public class MyRepositoryImpl implements MyRepository{
    
    @Override
    public String doRepository(Double score) {
        String res = "";
        if(score < 60) res="不及格";
        else res ="及格";
        return res;
    }
}
  • spring.xml
<context:component-scan base-package="controller"></context:component-scan>
<context:component-scan base-package="service"></context:component-scan>
<context:component-scan base-package="repository"></context:component-scan>
  • Test
    ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.xml");

        MyController myController = applicationContext.getBean(MyController.class);
        System.out.println(myController.service((double) 80));

Spring IoC 底层实现

核⼼技术点: XML 解析 + 反射
具体的思路:
  • 根据需求编写 XML ⽂件,配置需要创建的 bean
<!--student类-->  
<bean id="student" class="Entity.Student" >
        <property name="id" value="01"></property>
        <property name="age" value="18"></property>
        <property name="name" value="张三"></property>
</bean>
  • 编写程序读取 XML ⽂件,获取 bean 相关信息,类、属性、id

注意:读取xml需要在pom.xml中加入dom4j依赖

<!--dom4j-->
<dependency>
      <groupId>dom4j</groupId>
      <artifactId>dom4j</artifactId>
      <version>1.1</version>
</dependency>
  • 根据第 2 步获取到的信息,结合反射机制动态创建对象,同时完成属性的赋值
  • 将创建好的 bean 存⼊ Map 集合,设置 key - value 映射,key 就是 bean id 值,value 就是 bean 对象
  • 提供⽅法从 Map 中通过 id 获取到对应的 value

自定义一个类实现ApplicationContext接口

/**
 * @Author 苦恼的java小宝
 * @Date 2022/5/10 16:28
 * @ClassName: ioc.MyClassPathXmlApplicationContext
 * @Version 1.0
 */
public class MyClassPathXmlApplicationContext implements ApplicationContext {
    private Map<String,Object> iocMap;

    //有参构造
    public MyClassPathXmlApplicationContext(String path) {
        iocMap = new HashMap<>();

        //解释XML
        parseXml(path);
    }

    public void parseXml(String path) {
        SAXReader saxReader = new SAXReader();
        try {
            //import org.dom4j.io.SAXReader;
            Document document = saxReader.read(path);
            //拿到beans大标签
            Element root = document.getRootElement();
            //子标签迭代器->拿到每个bean
            Iterator<Element> roorIter = root.elementIterator();

            //寻找每个bean的子标签->拿到每个property
            while(roorIter.hasNext()){
                Element bean = roorIter.next();
                String id = bean.attributeValue("id");
                String className = bean.attributeValue("class");
                //通过反射创建对象
                Class clazz = Class.forName(className);
                Constructor constructor = clazz.getConstructor();//无参
                Object obj = constructor.newInstance();
                //给对象属性赋值(通过获取setter方法)
                Iterator<Element> beanIter = bean.elementIterator();
                //寻找每个属性
                while(beanIter.hasNext()){
                    Element property = beanIter.next();
                    //获取到<property>标签里面的name值 与 value值 ,取出来为String类型
                    String propertyName = property.attributeValue("name");
                    String propertyValue = property.attributeValue("value");
                    //获取setter方法的名字  方法名:setName()  setId()
                    String methodName = "set" + propertyName.substring(0,1).toUpperCase() +propertyName.substring(1);
                    //获取属性
                    Field field = clazz.getDeclaredField(propertyName);
                    System.out.println(field.getType());
                    //获取方法(通过 方法名和参数列表 才能确定唯一方法)
                    Method method = clazz.getMethod(methodName, field.getType());
                    Object value = propertyValue;

                    //类型转换
                    switch (field.getType().getName()){
                        case "java.lang.Integer":
                            value = Integer.parseInt(propertyValue);
                            break;
                        case "java.lang.Double":
                            value = Double.parseDouble(propertyValue);
                            break;
                    }
                    //调用setter方法
                    method.invoke(obj,value);
                }
                //存入map
                iocMap.put(id,obj);
            }
        } catch (DocumentException | ClassNotFoundException | NoSuchMethodException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        } catch (NoSuchFieldException e) {
            e.printStackTrace();
        }
    }


   
    @Override
    public Object getBean(String s) throws BeansException {
        return iocMap.get(s);
    }

}


Spring AOP

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

EX:若要在每个方法 的前后 打印日志,那么传统方法如下图:

 显而易见,上述形式的代码维护性差,没有代码复⽤性,使⽤ AOP 进⾏优化,如下图所示:

 上述图片用一个切片对象进行代理,所有方法都可以通过代理对象来运行,日志需要改变的时候,只需要改变一个切面对象的代码即可,不需要逐个方法体的代码都改。 实现非业务代码和业务代码的分离


AOP底层

新建一个AOP pakage

1、创建⼀个计算器接⼝ Cal
public interface Cal {
    public int add(int num1,int num2);
    public int sub(int num1,int num2);
    public int mul(int num1,int num2);
    public int div(int num1,int num2);
}
2、创建接⼝的实现类 CalImpl
public class CalImpl implements Cal {
    @Override
    public int add(int num1, int num2) {
        int result = num1 + num2;
        return result;
    }
    @Override
    public int sub(int num1, int num2) {
        int result = num1 - num2;
        return result;
    }
    @Override
    public int mul(int num1, int num2) {
        int result = num1 * num2;
        return result;
    }
    @Override
    public int div(int num1, int num2) {
        int result = num1 / num2;
        return result;
    }
}
对于计算器来讲,加减乘除就是业务代码,⽇志打印就是⾮业务代码
AOP 如何实现业务代码和非业务代码的分离?
  • 使⽤动态代理的⽅式来实现
  • 创建 MyInvocationHandler 类,实现 InvocationHandler 接⼝,⽣成动态代理类
  • 动态代理类,需要动态⽣成,需要获取到委托类的接⼝信息,根据这些接⼝信息动态⽣成⼀个代理类
  • 然后再由 ClassLoader 将动态⽣成的代理类加载到 JVM

3、创建 MyInvocationHandler 类,实现 InvocationHandler(调用处理程序) 接⼝

public class MyInvocationHandler implements InvocationHandler {
    //委托对象
    private Object object = null;

    //由委托对象生成代理对象
    public Object bind(Object object){
        this.object = object;
        /*
        newProxyInstance,方法有三个参数:
        loader: 用哪个类加载器去加载代理对象
        interfaces:动态代理类需要实现的接口
        h:动态代理方法在执行时,会调用h里面的invoke方法去执行
         */
        return Proxy.newProxyInstance(
                object.getClass().getClassLoader(),
                object.getClass().getInterfaces(),
                this
        );
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        //实现业务代码和⾮业务代码的解耦合
        System.out.println(method.getName()+"⽅法的参数是"+
                Arrays.toString(args));
        
        //执行业务代码
        Object result = method.invoke(this.object,args);
        
        System.out.println(method.getName()+"⽅法的结果是"+ result);
        return result;
    }
}

4.测试类

public class Test02 {
    public static void main(String[] args) {
        //创建委托对象
        Cal cal = new CalImpl();

        //返回代理对象
        MyInvocationHandler myInvocationHandler = new MyInvocationHandler();
        Cal proxy = (Cal) myInvocationHandler.bind(cal);
        
        proxy.add(1,2);
        proxy.sub(1,2);
        proxy.mul(1,2);
        proxy.div(1,2);
    }
}

AOP实际注解开发

1、添加pom.xml信息
        <!--Spring AOP-->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-aspects</artifactId>
            <version>5.2.3.RELEASE</version>
        </dependency>

        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-aop</artifactId>
            <version>5.2.3.RELEASE</version>
        </dependency>
2.创建切片类
@Component
@Aspect
public class LoggerAspect {

    //表示在AOP包下的CalImpl实体类中的任何返回值为int的方法 之前调用
    @Before("execution(public int aop.CalImpl.*(..))")
    public void before(JoinPoint joinPoint){
        //获得方法名
        String name = joinPoint.getSignature().getName();
        //获得方法参数
        String args = Arrays.toString(joinPoint.getArgs());
        System.out.println(name+"方法的参数是"+args);
    }

    @After("execution(public int aop.CalImpl.*(..))")
    public void after(JoinPoint joinPoint){
        String name = joinPoint.getSignature().getName();
        System.out.println(name+"⽅法执⾏完毕");
    }

    @AfterReturning(value = "execution(public int " +
            "aop.CalImpl.*(..))",returning = "result")
    public void afterReturn(JoinPoint joinPoint,Object result){
        String name = joinPoint.getSignature().getName();
        System.out.println(name+"⽅法的结果是"+result);
    }

    @AfterThrowing(value = "execution(public int " +
            "aop.CalImpl.*(..))",throwing = "ex")
    public void afterThrowing(JoinPoint joinPoint,Exception ex){
        String name = joinPoint.getSignature().getName();
        System.out.println(name+"⽅法抛出异常"+ex);
    }
}
  • @Component,将切⾯类加载到 IoC 容器中
  • @Aspect,表示该类是⼀个切⾯类
  • @Before,表示⽅法的执⾏时机是在业务⽅法之前,execution 表达式表示切入点是委托类的哪个方法
  • @After,表示⽅法的执⾏时机是在业务⽅法结束之后
  • @AfterReturning,表示⽅法的执⾏时机是在业务⽅法返回结果之后,returning 是将业务⽅法的返回值与切⾯类⽅法的形参进⾏绑定
  • @AfterThrowing,表示⽅法的执⾏时机是在业务⽅法抛出异常之后,throwing 是将业务⽅法的异常与切⾯类⽅法的形参进⾏绑定

3.配置Sping-aop.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:aop="http://www.springframework.org/schema/aop"
 xmlns:p="http://www.springframework.org/schema/p"
 xsi:schemaLocation="http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-4.3.xsd
 http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-4.3.xsd
 http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-4.3.xsd
">

 <!-- ⾃动扫包 -->
 <context:component-scan base-package="aop"></context:component-scan>

 <!-- 为委托对象⾃动⽣成代理对象 -->
 <aop:aspectj-autoproxy></aop:aspectj-autoproxy>

</beans>
aop:aspectj-autoproxy: Spring IoC 容器会结合切⾯对象和委托对象⾃动⽣成动态代理对象,
AOP 底层就是通过动态代理机制来实现的
4.测试类
public class Test01 {
    public static void main(String[] args) {
        ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("Spring-aop.xml");

        //内部已经自动配置代理对象,以下方法都由代理对象执行
        Cal calImpl = (Cal) applicationContext.getBean("calImpl");
        calImpl.div(10,3);
        calImpl.add(10,3);
        calImpl.sub(10,3);
        calImpl.mul(10,3);
    }}
AOP 的概念:
切⾯对象:根据切⾯抽象出来的对象, CalImpl 所有⽅法中需要加⼊⽇志的部分,抽象成⼀个切⾯
LoggerAspect
通知:切⾯对象具体执⾏的代码,即⾮业务代码, LoggerAspect 对象打印⽇志的代码
⽬标:被横切的对象,即 CalImpl ,将通知加⼊其中
代理:切⾯对象、通知、⽬标混合之后的结果,即我们使⽤ JDK 动态代理机制创建的对象
连接点:需要被横切的位置,即通知要插⼊业务代码的具体位置
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值