Spring5学习笔记

Spring学习笔记

1、IoC

Spring的IoC(控制反转) 、DI(依赖注入)。

Ioc—Inversion of Control,即“控制反转”,不是什么技术,而是一种设计思想。DI是实现IoC的一种方法。控制反转是一种设计思想。

传统Java SE程序设计,我们直接在对象内部通过new进行创建对象,是程序主动去创建依赖对象;而IoC是有专门一个容器来创建这些对象,即由Ioc容器来控制对 象的创建。

写得很好的一篇博客:https://www.cnblogs.com/xdp-gacl/p/4249939.html

spring官网:https://docs.spring.io/spring/docs/5.2.7.RELEASE/spring-framework-reference/core.html#beans-annotation-config

  • 其实IoC对编程带来的最大改变不是从代码上,而是从思想上,发生了“主从换位”的变化。应用程序原本是老大,要获取什么资源都是主动出击,但是在IoC/DI思想中,应用程序就变成被动的了,被动的等待IoC容器来创建并注入它所需要的资源了。IoC很好的体现了面向对象设计法则之一—— 好莱坞法则:“别找我们,我们找你”;即由IoC容器帮对象找相应的依赖对象并注入,而不是由对象主动去找。

image-20200718211620579

  • 从上面的图中我们也看到了中间件的影子。

1.1、如何实现IoC:

IoC是Spring框架的核心内容,使用多种方式完美的实现了IoC,可以使用XML配置,也可以使用注解,新版本的Spring也可以零配置实现IoC。

Spring容器在初始化时先读取配置文件,根据配置文件或元数据创建与组织对象存入容器中,程序使用时再从Ioc容器中取出需要的对象。

image-20200718211940110

  • 采用XML方式配置Bean的时候,Bean的定义信息是和实现分离的,而采用注解的方式可以把两者合为一体,Bean的定义信息直接以注解的形式定义在实现类中,从而达到了零配置的目的。控制反转是一种通过描述(XML或注解)并通过第三方去生产或获取特定对象的方式。在Spring中实现控制反转的是IoC容器,其实现方法是依赖注入(Dependency Injection,DI)。

1.2、HelloSpring:

  1. 创建一个无模板的maven项目。

  2. 在该项目中在创建一个maven模块。可以添加spring或者springMVC的支持。或者直接创建一个SpringMVC的模块。

  3. ApplicationContext.xml文件的大致样子:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">

    <bean id="helloSpring" class="com.aaron.pojo.Person">
    <!--在这里改变赋值就可以改变测试的输出-->
    <property name="name" value="spring"></property>
    </bean>

    </beans>
  4. 测试使用:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    import com.aaron.pojo.Person;
    import org.springframework.context.ApplicationContext;
    import org.springframework.context.support.ClassPathXmlApplicationContext;
    import org.springframework.test.context.TestConstructor;

    public class Test {
    public static void main(String[] args) {
    ApplicationContext context = new ClassPathXmlApplicationContext("ApplicationContext.xml");
    Person helloSpring = (Person) context.getBean("helloSpring");
    helloSpring.toString();
    }

    }

2、IoC创建对象的方式

  1. 默认使用无参构造创建对象。

  2. 假设我们需要使用有参构造来创建对象。可以使用注入构造器:

    参考文档:https://docs.spring.io/spring/docs/5.2.7.RELEASE/spring-framework-reference/core.html#beans-dependencies

    1. 第一种,参数下标赋值:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    例如你的构造函数是:
    public class Person {
    public Person(String name) {
    this.name = name;
    }
    }

    那么index就是你有参方法里面的参数的下标,从0开始:
    <bean id="exampleBean" class="examples.ExampleBean">
    <constructor-arg index="0" value="aaron荣燊"/>
    </bean>
    1. 第二种,参数类型赋值:
    1
    2
    3
    <bean id="exampleBean" class="examples.ExampleBean">
    <constructor-arg type="java.lang.String" value="aaron荣燊"/>
    </bean>
    1. 第三种,参数名来赋值:
    1
    2
    3
    <bean id="exampleBean" class="examples.ExampleBean">
    <constructor-arg name="name" value="aaron荣燊"/>
    </bean>

总结:在配置文件加载的时候,容器中管理的对象就已经初始化了!而且是单例的。。。就是相同的对象只创建一个。

3、Spring配置

3.1、别名:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<bean id="helloSpring" class="com.aaron.pojo.Person">
<!--在这里改变赋值就可以改变测试的输出-->
<property name="name" value="hello spring"></property>
</bean>

<alias name="helloSpring" alias="jj"></alias>

//使用
public class Test {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("ApplicationContext.xml");
Person helloSpring = (Person) context.getBean("jj");
helloSpring.show();
}

3.2、Bean的配置:

1
2
3
4
5
6
7
8
9
10
id:bean的唯一标识符,也就是相当于我们学的对象名
classbean对象所对应的权限定名,包名+类型
name:通过name也可以取别名,而且那么可以取多个别名。
scope:是否是单例模式

<bean id="helloSpring" class="com.aaron.pojo.Person" name="jj jj1,jj2;jj3" scope="prototype">
<!--在这里改变赋值就可以改变测试的输出-->
<property name="name" value="hello spring"></property>
</bean>

3.3、import:

  • 一般用于团队开发使用,他可以将多个xml配置文件导入合并为一个。

  • applicationContext.xml:

1
2
3
4
5
6
7
8
9
10
11
12
13
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">

<bean id="helloSpring" class="com.aaron.pojo.Person">
<!--在这里改变赋值就可以改变测试的输出-->
<property name="name" value="hello spring"></property>
</bean>

<alias name="helloSpring" alias="jj"></alias>

</beans>
  • bean.xml:
1
2
3
<import resource="applicationContext.xml">
<import resource="applicationContext1.xml">
<import resource="applicationContext2.xml">

后期直接使用总的bean.xml配置文件就可以了。

4、DI依赖注入

4.1、构造器注入:

前面已经讲过了。

4.2、Set方式注入【重点】:

  • 依赖:bean对象的创建依赖于容器
  • 注入:bean对象中的所有属性,由容器来注入。
  • 例:
  1. 项目构造:

    image-20200719131600188

  2. Address.java:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    package com.aaron.pojo;

    public class Address {
    private String address;

    public String getAddress() {
    return address;
    }

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

    @Override
    public String toString() {
    return "Address{" +
    "address='" + address + '\'' +
    '}';
    }
    }
  3. Student.java:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    package com.aaron.pojo;

    import java.util.*;

    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;

    public String getName() {
    return name;
    }

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

    public Address getAddress() {
    return address;
    }

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

    public String[] getBooks() {
    return books;
    }

    public void setBooks(String[] books) {
    this.books = books;
    }

    public List<String> getHobbys() {
    return hobbys;
    }

    public void setHobbys(List<String> hobbys) {
    this.hobbys = hobbys;
    }

    public Map<String, String> getCard() {
    return card;
    }

    public void setCard(Map<String, String> card) {
    this.card = card;
    }

    public Set<String> getGames() {
    return games;
    }

    public void setGames(Set<String> games) {
    this.games = games;
    }

    public String getWife() {
    return wife;
    }

    public void setWife(String wife) {
    this.wife = wife;
    }

    public Properties getInfo() {
    return info;
    }

    public void setInfo(Properties info) {
    this.info = info;
    }

    @Override
    public String toString() {
    return "Student{" +
    "name='" + name + '\'' +
    ", address=" + address.toString() +
    ", books=" + Arrays.toString(books) +
    ", hobbys=" + hobbys +
    ", card=" + card +
    ", games=" + games +
    ", wife='" + wife + '\'' +
    ", info=" + info +
    '}';
    }
    }
  4. bean.xml:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">

    <bean id="address1" class="com.aaron.pojo.Address">
    <property name="address" value="江西九江"></property>
    </bean>

    <bean name="student" class="com.aaron.pojo.Student">
    <!-- 第一种,普通值注入,使用value -->
    <property name="name" value="荣燊"></property>

    <!-- 第二种,bean注入,使用ref -->
    <property name="address" ref="address1"></property>

    <!-- String数组注入-->
    <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="123123123123123123"/>
    <entry key="银行卡" value="1231231231231231234567"/>
    </map>
    </property>

    <!-- Set注入-->
    <property name="games">
    <set>
    <value>LOL</value>
    <value>COC</value>
    </set>
    </property>

    <!-- NULL空字符串注入-->
    <property name="wife">
    <null/>
    </property>

    <!-- Properties注入-->
    <property name="info">
    <props>
    <prop key="学号">1467002030</prop>
    <prop key="性别">男</prop>
    <prop key="姓名">小明</prop>
    <prop key="userName">root</prop>
    <prop key="passWord">root</prop>
    </props>
    </property>

    </bean>
    </beans>
  5. Mytest.java:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    import com.aaron.pojo.Student;
    import org.springframework.context.ApplicationContext;
    import org.springframework.context.support.ClassPathXmlApplicationContext;

    public class MyTest {
    public static void main(String[] args) {
    ApplicationContext context = new ClassPathXmlApplicationContext("bean.xml");
    Student student = (Student) context.getBean("student");
    System.out.println(student.toString());
    }
    }
  6. 输出:

    image-20200719131957489

4.3、拓展方式注入:

说明:p命名空间像之前的直接注入,c命名空间注入像通过一个有参构造器注入。p命名空间与c命名空间都不能直接使用,需要导入xml约束。

  • p命名空间导入:

  1. 命名头:
1
xmlns:p="http://www.springframework.org/schema/p"
  1. 实体类User.java:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
package com.aaron.pojo;

public class User {
private String userName;
private int age;

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

public String getUserName() {
return userName;
}

public void setUserName(String userName) {
this.userName = userName;
}

public int getAge() {
return age;
}

public void setAge(int age) {
this.age = age;
}
}
  1. xml文件中就可以这样写:
1
2
<!-- p命名空间可以直接注入同样的值,property -->
<bean id="user" class="com.aaron.pojo.User" p:userName="夏天" p:age="18"/>
  • c命名空间导入:

注意,该命名空间是要求 实体类里面 写了有参构造器的。

  1. 命名头:
1
xmlns:c="http://www.springframework.org/schema/c"
  1. 实体类中需要写 有参构造器 :
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
package com.aaron.pojo;

public class User {
private String userName;
private int age;

//可以在这里写一个无参构造器,那么p命名空间也可以用了

public User(String userName, int age) {
this.userName = userName;
this.age = age;
}

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

public String getUserName() {
return userName;
}

public void setUserName(String userName) {
this.userName = userName;
}

public int getAge() {
return age;
}

public void setAge(int age) {
this.age = age;
}
}
  1. xml文件中就可以这样写:
1
<bean id="user" class="com.aaron.pojo.User" c:userName="谢磊" c:age="25" />

5、bean的作用域scope

image-20200719161622482

5.1、singleton单例:

默认实现用这种。

5.2、prototyp原型:

也就是多例。每次从容器中get的时候,都会产生一个新对象。

  1. 配置:

    1
    <bean id="user" class="com.aaron.pojo.User" c:userName="谢磊" c:age="25" scope="prototype"/>
  2. 使用:
    image-20200719162229863

5.3、其余:

其余的像request、session、application这些只能在web开发中使用到。

6、Bean的自动装配

  • 自动装配是spring满足bean依赖的一种方式。
  • spring会在上下文context中自动寻找,并自动给bean装配属性。

在spring中有三种装配的方式

  1. 在xml中显示的配置
  2. 在java中显示的配置
  3. 隐式的自动装配bean【重要】

6.1、@Autowired:

6.1.1、byName自动装配:

  • byName会自动在容器上下文中查找,和自己对象set方法后面的值对饮的bean的id。id是要小写的。

    1
    2
    3
    4
    5
    6
    7
    8
    <bean id="cat" class="com.aaron.pojo.Cat"/>
    <bean id="dog" class="com.aaron.pojo.Dog"/> //id必须小写,与people里的对应
    <bean id="dog1" class="com.aaron.pojo.Dog"/> //错误
    <bean id="Dog" class="com.aaron.pojo.Dog"/> //错误

    <bean id="people" class="com.aaron.pojo.People" autowire="byName">
    <property name="name" value="谢蕾蕾"/>
    </bean>

6.1.2、byType自动装配:

  • byName会自动在容器上下文中查找,和自己对象属性类型相同的bean。类型必须唯一。

    1
    2
    3
    4
    5
    6
    7
    8
    <bean id="cat" class="com.aaron.pojo.Cat"/>
    <bean id="dog111" class="com.aaron.pojo.Dog"/>
    <bean class="com.aaron.pojo.Dog"/> //不用id也可以
    <bean id="dog" class="com.aaron.pojo.Dog"/> //错误,和上面的定义只能有一个在

    <bean id="people" class="com.aaron.pojo.People" autowire="byType">
    <property name="name" value="谢蕾蕾"/>
    </bean>

6.1.3、小结:

  • byName的时候,需要保证所有的bean的id唯一,并且这个bean需要和自动注入的属性的set方法的值一致。
  • byType的时候,需要保证所有的bean的class唯一,并且这个bean需要和自动注入的属性的类型一致。

6.2、注解实现自动装配:

jdk1.5支持注解,spring2.5就开始支持注解。

要使用注解:

  1. 导入约束,context约束。

  2. 配置注解的支持

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    <?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>
  3. 直接在属性上使用即可,也可以在set方法上使用。使用Autowired我们可以不用写set方法了,前提是你这个自动装配的属性在IOC容器中存在。:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    @Autowired
    private Cat cat;
    @Autowired
    private Dog dog;


    <?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/>

    <bean id="cat" class="com.aaron.pojo.Cat"/>
    <bean id="dog" class="com.aaron.pojo.Dog"/>
    <bean id="people" class="com.aaron.pojo.People" autowire="byName"/>

    </beans>
  4. 科普:

    1
    2
    3
    4
    5
    6
    @Nullable 字段标记了这个注解,说明这个字段可以为null

    @Autowired(required = false) //里面的值可以为空,null
    @Qualifier(value="dog") //多个装配相同的对象,可以和autowired搭配使用

    @Resource //java的原生注解
  • 小结:

    @Resource和@Autowired的区别:

    1. 都是用来自动装配的,都可以放在属性字段上
     2. @Autowired通过byType的方式实现,而且必须要求这个对象存在。
     3. @Resource默认通过byName的方式实现,如果找不到名字,则通过byType实现。
    

7、使用注解开发

在spring4之后要使用注解开发,必须导入AOP的包了。使用注解需要导入context的约束,增加注解的支持。

1
2
//指定要扫描的包,这个包下面的注解就会生效
<context:component-scan base-package="com.aaron.pojo"/>

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

7.1、bean:

1
2
3
4
5
6
7
//该注解等价于<bean id="user" class="com.aaron.pojo.User"/>
//注册组件
@Component
public class User {
@Value("夏天")
public String name;
}

7.2、属性如何注入:

1
2
3
4
5
6
7
8
9
//相当于<property name="name" value="夏天"/>,也可以注解在set方法上
@Value("夏天")
public String name;

//或者
@Value("夏天")
public void setName(String name) {
this.name = name;
}

7.3、衍生的注解:

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

  • dao层:@Repository
  • service层:@Service
  • controller{servlet}层:@Controller
  • 这四个注解都是代表将某个类注册到Spring中,装配bean。

7.4、自动装配:

1
2
3
4
5
6
@Nullable 字段标记了这个注解,说明这个字段可以为null

@Autowired(required = false) //里面的值可以为空,null
@Qualifier(value="dog") //多个装配相同的对象,可以和autowired搭配使用

@Resource //java的原生注解

7.5、作用域:

@Scope

1
2
3
4
5
@Component
@Scope("prototype")
public class User {
public String name;
}

7.6、小结:

比较xml配置文件与注解:

  • xml更加万能,适用于任何场合!维护更加方便。
  • 注解,不是自己的类使用不了,维护相对复杂。

xml与注解的最佳实践:

  • xml用来管理bean。
  • 注解只负责完成属性的注入。

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

1
2
<context:annotation-config/>
<context:component-scan base-package="com.aaron.pojo"/>

8、使用java的方式配置Spring

我们现在不使用Spring中xml的配置方式了,交给java来做。

JavaConfig是Spring的一个子项目,在spring4之后,他称为了一个新功能。

image-20200719210501430

image-20200719210538628

这种纯java的注解在spingboot中随处可见。所以springboot很重要。

9、代理模式proxy

9.1、静态代理:

角色分析:

  • 抽象角色:一般会使用接口或者抽象类来解决
  • 真实角色:被代理的角色
  • 代理角色:代理真实角色,代理真实角色之后,我们一般会做一些附属操作
  • 客户:访问代理对象的人

代理模式的好处:

  • 可以使真实角色的操作更加纯粹,不用去关注一些公共的业务(例如日志功能 )。
  • 公共业务也就交给代理角色,实现了业务的分工。
  • 公共业务发生扩展的时候,方便集中管理。

缺点:

  • 一个真实角色就会产生一个代理角色,代码量就会翻倍,开发效率会更低

9.2、动态代理:

  • 动态代理和静态代理角色一样
  • 动态代理的代理类是动态生成的,不是我们直接写好的。
  • 动态代理分为两大类:1.基于接口的动态代理,2.基于类的动态代理。
    • 基于接口——JDK代理
    • 基于类的——cglib
    • java字节码实现——javasist

需要了解两个类:Proxy代理,InvocationHandler调用处理程序。

动态代理的好处:

  • 可以使真实角色的操作可以更加纯粹!不用去关注一些公共的业务。
  • 公共业务就交给代理角色!实现了业务的分工。
  • 公共业务发生扩展的时候,方便集中管理。
  • 一个动态代理类代理的就是一个接口,一般就是对应的一类业务。
  • 一个动态代理类可以代理多个类,只要是实现了同一个接口即可。

实例:

  1. UserService接口:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    package com.aaron.proxyDemo;

    public interface UserService {
    //接口里面的类
    public void add();
    public void delete();
    public void update();
    public void query();
    }
  2. UserService接口实现类UserServiceImpl:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    package com.aaron.proxyDemo;

    public class UserServiceImpl implements UserService {

    public void add() {
    System.out.println("新增一个用户~");
    }

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

    public void update() {
    System.out.println("修改一个用户~");
    }

    public void query() {
    System.out.println("查询一个用户~");
    }

    }
  3. ProxyInvocationHandler自动生成代理类:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    package com.aaron.proxyDemo;

    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+"方法");
    }
    }
  4. 测试方法:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    package com.aaron.proxyDemo;

    public class Client {
    public static void main(String[] args) {
    //真实角色
    UserServiceImpl userService = new UserServiceImpl();
    //代理角色
    ProxyInvocationHandler pih = new ProxyInvocationHandler();
    //设置要代理的对象
    pih.setTarget(userService);
    //动态生成代理类
    UserService proxy = (UserService) pih.getProxy();
    proxy.add();
    proxy.delete();
    proxy.update();
    proxy.query();
    }
    }

10、AOP

10.1、什么是AOP:

在软件业,AOP为Aspect Oriented Programming的缩写,意为:面向切面编程,通过预编译方式和运行期间动态代理实现程序功能的统一维护的一种技术。

AOP的几个专业术语:

  • 横切关注点:

  • 通知方法:(就像日志工具类LogUtils里面的LogStart、LogReturn等等方法)

  • 切面类:(就像日志工具类LogUtils)

  • 连接点:每一个方法的每一个位置就是一个连接点。

  • 切入点:红色部分,我们真正需要执行日志记录的地方。

  • 图见:

    img

image-20200721082031772

10.2、AOP在Spring中的作用:

image-20200721082511662

10.3、使用Spring实现AOP:

10.3.1、使用Spring的API接口实现:

  1. 使用到的jar包(有些包应该也不是必须的):

    image-20200721100200905

  2. 项目结构:

    image-20200721100302953

  3. 各类、接口、方法与xml配置:

    • UserService接口与UserServiceImpl实现方法:

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      //UserService.java
      package com.aaron.service;

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

      //UserServiceImpl.java
      package com.aaron.service;

      public class UserServiceImpl implements UserService {
      public void add() {
      System.out.println("增加了一个用户~");
      }

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

      public void update() {
      System.out.println("修改了一个用户~");
      }

      public void query() {
      System.out.println("查询了一个用户~");
      }
      }
    • 接口方法实现前执行的Log:

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      package com.aaron.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()+"方法被执行了");
      }

      }
    • 接口方法实现后执行的LogAfter:

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      package com.aaron.log;

      import org.springframework.aop.AfterReturningAdvice;

      import java.lang.reflect.Method;

      public class LogAfter implements AfterReturningAdvice {

      //o:返回结果
      //objects:参数
      //o1:目标对象
      public void afterReturning(Object o, Method method, Object[] objects, Object o1) throws Throwable {
      System.out.println("执行了"+method.getName()+"方法,返回结果为:"+o);
      }

      }
    • beans.xml:

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      <?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/beans
      http://www.springframework.org/schema/beans/spring-beans.xsd
      http://www.springframework.org/schema/aop
      http://www.springframework.org/schema/aop/spring-aop.xsd">

      <!--注册bean-->
      <bean id="userService" class="com.aaron.service.UserServiceImpl"/>
      <bean id="log" class="com.aaron.log.Log"/>
      <bean id="logAfter" class="com.aaron.log.LogAfter"/>

      <!--方式一:使用原生的Spring API接口-->
      <!--配置AOP,需要导入aop的约束-->
      <aop:config>
      <!--切入点,expression表达式,要执行的位置execution(修饰词 返回值 类名 方法名 参数)-->
      <aop:pointcut id="pointcut" expression="execution(* com.aaron.service.UserServiceImpl.*(..))"/>

      <!--执行环绕增加,将log这个类加到id为pointcut的切入点上-->
      <aop:advisor advice-ref="log" pointcut-ref="pointcut"></aop:advisor>
      <aop:advisor advice-ref="logAfter" pointcut-ref="pointcut"/>
      </aop:config>

      </beans>
  4. 测试类:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    import com.aaron.service.UserService;
    import com.aaron.service.UserServiceImpl;
    import org.springframework.beans.BeansException;
    import org.springframework.context.ApplicationContext;
    import org.springframework.context.support.ClassPathXmlApplicationContext;

    public class MyTest {
    public static void main(String[] args) {
    ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
    //动态代理代理的是个接口,注意点
    UserService userService = (UserService) context.getBean("userService");
    userService.add();
    }
    }
  5. 输出:

    image-20200721100448732

10.3.2、自定义类来实现:

基于以上的测试类不变,新增一个diy的类,xml文件里的写法也要改变:

  • diy的类:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    package com.aaron.diy;

    public class DiyPointcut {
    public void before(){
    System.out.println("方法执行前————————");
    }

    public void after(){
    System.out.println("方法执行后————————");
    }
    }
  • beans.xml:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    <?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/beans
    http://www.springframework.org/schema/beans/spring-beans.xsd
    http://www.springframework.org/schema/aop
    http://www.springframework.org/schema/aop/spring-aop.xsd">

    <!--注册bean-->
    <bean id="userService" class="com.aaron.service.UserServiceImpl"/>
    <bean id="diy" class="com.aaron.diy.DiyPointcut"/>

    <!--方法二,自定义类-->
    <aop:config>
    <!--自定义切面,ref要使用的类-->
    <aop:aspect ref="diy">
    <!--切入点-->
    <aop:pointcut id="point" expression="execution(* com.aaron.service.UserServiceImpl.*(..))"/>
    <!--通知-->
    <aop:before method="before" pointcut-ref="point"/>
    <aop:after method="after" pointcut-ref="point"/>
    </aop:aspect>
    </aop:config>

    </beans>
  • 测试类不变,结果为:

    image-20200721105741754

10.3.3、使用注解实现:

  • 先标记切入类和切入方法:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    package com.aaron.annotation;

    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 AnnotationPointCut {

    @Before("execution(* com.aaron.service.UserServiceImpl.*(..))")
    public void before(){
    System.out.println("方法执行前——————");
    }

    @After("execution(* com.aaron.service.UserServiceImpl.*(..))")
    public void after(){
    System.out.println("方法执行后——————");
    }

    //在环绕增强的时候,我们可以给定一个参数,代表我们要获取处理切入的点
    @Around("execution(* com.aaron.service.UserServiceImpl.*(..))")
    public void around(ProceedingJoinPoint pjp) throws Throwable {
    System.out.println("环绕前");
    pjp.proceed();
    System.out.println("环绕后");
    }
    }
  • beans.xml中注册:

    1
    2
    <bean id="userService" class="com.aaron.service.UserServiceImpl"/>
    <bean id="annotionPointCut" class="com.aaron.annotation.AnnotationPointCut"/>
  • 测试类不变,测试结果:

    image-20200721134056609

  • 版权声明: 本博客所有文章除特别声明外,著作权归作者所有。转载请注明出处!

请我喝杯咖啡吧~

支付宝
微信