Spring02--自动装配、注解开发、AOP

本文详细介绍了Spring框架中Bean的自动装配,包括按名字、类型和注解方式的自动装配,强调了@Autowired和@Resource注解的区别与使用场景。接着,文章讨论了注解开发的便利性和配置,以及静态和动态代理模式。最后,概述了Spring AOP的基本概念和实现方式,包括前置、后置和环绕通知。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1Bean的自动装配

自动装配在spring满足bean依赖的一种方式,spring会在上下文自动寻找,并自动给bean装配属性!!!【为某个bean寻找其依赖的bean】

在Spring中有三种装配的方式

  1. 在xml中显式的配置

  2. 在Java中显式配置

  3. 隐式的自动配置bean【重要】

    Spring的自动装配需要从两个角度来实现,或者说是两个操作:

    1. 组件扫描(component scanning):spring会自动发现应用上下文中所创建的bean;
    2. 自动装配(autowiring):spring自动满足bean之间的依赖,也就是我们说的IoC/DI;

    组件扫描和自动装配组合发挥巨大威力,使得显示的配置降低到最少。

    推荐不使用自动装配xml配置 , 而使用注解 .

1.1测试

1搭建环境

实体类三个,猫狗和人,一个人有两个宠物

public class Dog {
    public  void shout(){
        System.out.println("wow");
    }
}
public class Cat {
    public  void shout(){
        System.out.println("miao");
    }
}
public class People {
    private String name;
    private Cat cat;
    private Dog dog;
    还有些方法
    }

2编写spring配置文件

    <bean id="cat" class="com.kuang.pojo.Cat"/>
    <bean id="dog" class="com.kuang.pojo.Dog"/>
<bean id="people" class="com.kuang.pojo.People">
    <property name="name" value="wyq"/>
    <property name="cat" ref="cat"/>
    <property name="dog" ref="dog"/>
</bean>

3测试

public class Mytest {
    public static void main(String[] args) {
      ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
        People people = (People) context.getBean("people");
       people.getCat().shout();
       people.getDog().shout();
    }
}

结果:输出正常,环境ok

1.2按名字自动装配【autowire=“byName”】

由于在手动配置xml过程中,常常发生字母缺漏和大小写等错误,而无法对其进行检查,使得开发效率降低。

采用自动装配将避免这些错误,并且使配置简单化。

测试:

  1. 修改bean配置,增加一个属性 autowire=“byName”

        <bean id="cat" class="com.kuang.pojo.Cat"/>
        <bean id="dog" class="com.kuang.pojo.Dog"/>
    <bean id="people" class="com.kuang.pojo.People" autowire="byName">
        <property name="name" value="wyq"/>
    </bean>
    
  2. 再次测试,结果依旧成功输出

  3. 我们将 cat 的bean id修改为 catXXX,再次测试, 执行时报空指针java.lang.NullPointerException。因为按byName规则找不对应set方法,真正的setCat就没执行,对象就没有初始化,所以调用时就会报空指针错误

小结:

当一个bean节点带有 autowire byName的属性时。

  1. 将查找其类所有的set方法名,例如setCat,获得将set去掉并且首字母小写的字符串,即cat。
  2. 去spring容器中寻找是否有此字符串名称id的对象
  3. 如果有,就取出注入;如果没有,就报空指针异常。

1.3按类型自动装配[autowire=“byType”]

注意:使用autowire byType首先需要保证:同一类型的对象,在spring容器中唯一。如果不唯一,会报不唯一的异常【NoUniqueBeanDefinitionException】。

按类型装配会自动在容器上下文查找,和自己对象属性类型相同的bean

   <bean id="cat123" class="com.kuang.pojo.Cat"/>
    <bean id="dog" class="com.kuang.pojo.Dog"/>
<bean id="people" class="com.kuang.pojo.People" autowire="byType">
    <property name="name" value="wyq"/>
</bean>

测试:

1、将user的bean配置修改一下 : autowire=“byType”

2、测试,正常输出

3、再注册一个cat 的bean对象!

     <bean id="cat123" class="com.kuang.pojo.Cat"/>
    <bean id="catg" class="com.kuang.pojo.Cat"/>
    <bean id="dog" class="com.kuang.pojo.Dog"/>
<bean id="people" class="com.kuang.pojo.People" autowire="byType">
    <property name="name" value="wyq"/>
</bean>

最后一个bean飘红了,测试,报NoUniqueBeanDefinitionException

4、删掉cat2,将cat的bean名称改掉!测试!因为是按类型装配,所以并不会报异常,也不影响最后的结果。甚至将id属性去掉,也不影响结果。

byname是通过上下文,bytype看类型就行。

总结:

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

1.4使用注解自动装配【导context】

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

准备工作:利用注解的方式注入属性。

1导入约束-----context的约束,导入context文件头

xmlns:context=“http://www.springframework.org/schema/context”

2配置注解的支持

http://www.springframework.org/schema/contex

http://www.springframework.org/schema/context/spring-context.xsd

context:annotation-config/

完整的配置

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

</beans>

1.4.1@Autowired【bytype】

  • @Autowired是按类型自动转配的,不支持id匹配。

  • 需要导入 spring-aop的包!

  • 直接在属性上使用也可以在set方法上使用

  • 使用autowired就可以不写set方法了,前提是自动装配的属性在IOC(Spring)容器中存在且符合byName

    测试

    1可以将User类中的set方法去掉【此时set方法有无都可】,在属性上面使用@Autowired注解

public class People {
    private String name;
    @Autowired
    private Cat cat;
    @Autowired
    private Dog dog;
    }

2配置文件

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

    <context:annotation-config/>
    <bean id="cat" class="com.kuang.pojo.Cat"/>
    <bean id="dog" class="com.kuang.pojo.Dog"/>
    <bean id="people" class="com.kuang.pojo.People" />

</beans>

注意:这种情况下,如果猫或者狗的bean的id写的不是cat或者dog就会报错!!!

补充:

@Autowired(required=false) 说明:false,对象可以为null;true,对象必须存对象,不能为null。

//如果允许对象为null,设置required = false,默认为true
@Autowired(required = false)
private Cat cat;

1.4.2@Qualifier

  • @Autowired是根据类型自动装配的,加上@Qualifier则可以根据byName的方式自动装配
  • @Qualifier不能单独使用

测试

1、配置文件修改内容,保证类型存在对象。且名字不为类的默认名字!

<bean id="cat2" class="com.kuang.pojo.Cat"/>
<bean id="dog2" class="com.kuang.pojo.Dog"/>
<bean id="cat1" class="com.kuang.pojo.Cat"/>
<bean id="dog1" class="com.kuang.pojo.Dog"/>

2、没有加Qualifier测试,直接报错

因为有两个猫,两个狗,无论是通过,名字还是通过类型都找不到。需压迫@Qualifier指定某个类实现哪个。如果给猫装一个狗的id直接报错

3、在属性上添加Qualifier注解

public class People {
    private String name;
    @Autowired
    @Qualifier(value = "cat2")
    private Cat cat;
    @Autowired
    @Qualifier(value = "dog2")
    private Dog dog;
    }

测试成功

总结:

  1. 如果@Autowired自动给装配的环境比较复杂,自动装配无法通过一个注解@Autowired完成的时候,可以使用@Qualifier(value = “xxx”)配合@Autowired的使用。指定一个唯一的bean对象注入!!!
  2. @Autowired默认按类型装配(这个注解是属于spring的),默认情况下必须要求依赖对象必须存在,如果要允许null值,可以设置它的required属性为false,如:@Autowired(required=false) ;如果我们想使用名称装配可以结合@Qualifier注解进行使用

1.4.3@Resource

  • @Resource【这个注解属于J2EE的】如有指定的name属性
  • @Resource装配顺序
    1. 如果同时指定了name和type,则从Spring上下文中找到唯一匹配的bean进行装配,找不到则抛出异常
        2. 如果指定了name,则从上下文中查找名称(id)匹配的bean进行装配,找不到则抛出异常
      3. 如果指定了type,则从上下文中找到类型匹配的唯一bean进行装配,找不到或者找到多个,都会抛出异常
        4. 如果既没有指定name,又没有指定type,则自动按照byName方式进行装配;如果没有匹配,则回退为一个原始类型进行匹配,如果匹配则自动装配;

测试1:两个猫两个狗,就需要都指定name,不然找不到。

<bean id="cat2" class="com.kuang.pojo.Cat"/>
<bean id="dog2" class="com.kuang.pojo.Dog"/>
<bean id="cat1" class="com.kuang.pojo.Cat"/>
<bean id="dog1" class="com.kuang.pojo.Dog"/>
@Resource(name = "cat1")
private Cat cat;
@Resource(name = "dog1")
private Dog dog;

测试2:一狗两猫,那么狗就不用指定,猫得指定

<bean id="cat2" class="com.kuang.pojo.Cat"/>
<bean id="dog" class="com.kuang.pojo.Dog"/>
<bean id="cat1" class="com.kuang.pojo.Cat"/>
@Resource(name = "cat1")
private Cat cat;
@Resource
private Dog dog;

测试3:一猫一狗,都不要指定,会先按name再按type

<bean id="dog" class="com.kuang.pojo.Dog"/>
<bean id="cat1" class="com.kuang.pojo.Cat"/>
@Resource
private Cat cat;
@Resource
private Dog dog;

总结

@Autowired与@Resource异同:

相同:1、@Autowired与@Resource都可以用来装配bean。都可以写在字段上,或写在setter方法上。

区别:1、@Autowired默认按类型装配(属于spring规范),默认情况下必须要求依赖对象必须存在,如果要允许null 值,可以设置它的required属性为false,如:@Autowired(required=false) ,如果我们想使用名称装配可以结合@Qualifier注解进行使用

2、@Resource(属于J2EE规范),默认按照名称进行装配,名称可以通过name属性进行指定。如果没有指定name属性,当注解写在字段上时,默认取字段名进行按照名称查找,如果注解写在setter方法上默认取属性名进行装配。当找不到与名称匹配的bean时才按照类型进行装配。但是需要注意的是,如果name属性一旦指定,就只会按照名称进行装配

它们的作用相同都是用注解方式注入对象,但执行顺序不同。@Autowired先byType,@Resource先byName。
推荐使用:@Resource注解在字段上,这样就不用写setter方法了

2使用注解开发

在spring4之后,想要使用注解形式,必须得要引入aop的包,

1确认一下aop的包(导入spring的包其实就有)

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-8arZ3B4O-1621244126351)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20210513222111349.png)]

2在配置文件当中,还得要引入一个context约束,增加注解的支持

xmlns:context=“http://www.springframework.org/schema/context”

context:annotation-config/

创建一个applicationContext.xml【就是原来的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"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans.xsd
       http://www.springframework.org/schema/context
       http://www.springframework.org/schema/context/spring-context.xsd">
    <context:annotation-config/>
</beans>

2.1bean的实现

之前都是使用 bean 的标签进行bean注入,但是实际开发中,我们一般都会使用注解!

步骤1 配置扫描哪些包下的注解【在applicationContext.xml下写】指定注解扫描包

<context:component-scan base-package="com.kuang.pojo"/>

步骤2 在指定包下编写类,增加注解【因为以前是写一个类在配置文件写一个bean,现在就要是写一个类增加一个注解,就可以省去在配置文件写的bean的配置了】

@Component(“user”)// 相当于配置文件中 <bean id=“user” class=“当前注解的类”/*

@Component("user")
public class User {
    private String name;
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
}

2.2属性如何注入

使用注解注入属性@Value【v大写】

方法1 可以不用提供set方法,直接在直接名上添加@value(“值”)

@Component("user")
// 相当于配置文件中 <bean id="user" class="当前注解的类"/>
public class User {
    @Value("kuangshen")
    // 相当于配置文件中 <property name="name" value="秦疆"/
    private String name;
    public String getName() {
        return name;
    }
}

方法2 如果提供了set方法,在set方法上添加@value(“值”);

@Component("user")
public class User {
    private String name;
    public String getName() {
        return name;
    }
    @Value("kuangshen")
    public void setName(String name) {
        this.name = name;
    }
}

2.3衍生的注解

这些注解,就是替代了在配置文件当中配置步骤而已!

@Component三个衍生注解

为了更好的进行分层,Spring可以使用其它三个注解,功能一样,目前使用哪一个功能都一样。

  • @Controller:web层【UserController用@Controller】
  • @Service:service层【Userservice用@Service】
  • @Repository:dao层【Userdao用@Repository】
  • @Component:pojo【pojo里面的实体类用@Component】

写上这些注解,就相当于将这个类交给Spring管理装配了!

这四个是一样的,只不过为区分是哪个层,人为起了名字

2.4自动装配注解

1.4节

2.5作用域

@scope

  • singleton:默认的,Spring会采用单例模式创建这个对象。关闭工厂 ,所有的对象都会销毁。
  • prototype:多例模式。关闭工厂 ,所有的对象不会销毁。内部的垃圾回收机制会回收
@Component("user")
@Scope("singleton")
public class User {}

2.6小结

XML与注解比较

  • XML可以适用任何场景 ,结构清晰,维护方便
  • 注解不是自己提供的类使用不了,开发简单方便

xml与注解整合开发 :推荐最佳实践

  • xml管理Bean

  • 注解完成属性注入【就是类里面的各种属性,简单的复杂的】

  • 使用过程中, 可以不用扫描,扫描是为了类上的注解

  • 注意:

    • 进行注解驱动注册,从而使注解生效

      <context:component-scan base-package="com.kuang.pojo"/>   //扫描包
      <context:annotation-config/>   //注解支持
      
    • 用于激活那些已经在spring容器里注册过的bean上面的注解,也就是显式的向Spring注册

    • 如果不扫描包,就需要手动配置bean【】

    • 如果不加注解驱动,则注入的值为null!

3基于Java类进行配置spring

JavaConfig 原来是 Spring 的一个子项目,它通过 Java 类的方式提供 Bean 的定义信息,在 Spring4 的版本, JavaConfig 已正式成为 Spring4 的核心功能 。【Java配置类去替换spring的配置文件,完全等价于

3.1基本操作

步骤1:创建实体类【com.kuang.pojo下创建一个User,类上加注解,如果注入属性,属性或者属性的set方法加注解】

@Component //将这个类标注为Spring的一个组件,放到容器中!
public class User {
    private String name;
    public String getName() {
        return name;
    }
@Value("wyq")
    public void setName(String name) {
        this.name = name;
    }
}

步骤2:创建config类,例如在com.kuang.config包下创建KuangConfig类,在类上加上注解@Configuration【代表这是一个配置类】

点开这个注解,可以看到**@Configuration本身就是一个@Component,**所以就会被spring容器托管,注册到容器中。

@Configuration代表这是一个配置类,就和之前看到的beans.xml是一样的。所以在配置文件可以进行的操作在这里也可以利用注解完成。例如扫描包,就在类上加注解@ComponentScan(“com.kuang.pojo”)

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface Configuration {
    @AliasFor(
        annotation = Component.class
    )
    String value() default "";

    boolean proxyBeanMethods() default true;
}
@Configuration
public class KuangConfig {
    @Bean
public User getUser(){
    return new User();
}
}

@Bean *//通过方法注册一个bean,@Bean就是原来的bean标签,这里的返回值就Bean标签的class属性方法名就是原来写spring配置文件注册bean的id!*在测试方法中getbean参数输入id,原来是user,现在是这个方法名getuser,如果方法名写成user,那getbean的参数还是user;方法里面返回的对象就是要注入bean的对象

步骤3:测试【如果完全用注解,通过AnnotationConfigApplicationContext参数是配置类获取上下文context对象】

public class Mytest {
    public static void main(String[] args) {
    ApplicationContext context = new AnnotationConfigApplicationContext(KuangConfig.class);
       User getUser = (User)context.getBean("getUser");
        System.out.println(getUser.getName());
    }
}

3.2补充:导入其他配置【多个配置文件,这里就是多个配置类】

步骤1 我们再编写一个配置类!

@Configuration
public class KuangConfig2 {
}

步骤2 在之前的配置类中我们来选择导入这个配置类【导入合并其他配置类,类似于配置文件中的 inculde 标签

@Configuration
@ComponentScan("com.kuang.pojo")
@Import(KuangConfig2.class)
public class KuangConfig {
    @Bean
public User User(){
    return new User();
}
}

4静态/动态代理模式

学习代理模式原因:spring的AOP底层

代理模式

、[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-v3crhsDb-1621244126353)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20210516204908060.png)]

代理模式的分类

  1. 静态代理
  2. 动态代理

4.1静态代理模式

4.1.1静态代理角色分析

  • 抽象角色 : 一般使用接口或者抽象类来实现
  • 真实角色 : 被代理的角色
  • 代理角色 : 代理真实角色 ; 代理真实角色后 , 一般会做一些附属的操作 .
  • 客户 : 使用代理角色来进行一些操作 .

4.1.2代码实现

分析:在这个过程中,你直接接触的就是中介,就如同现实生活中的样子,你看不到房东,但是你依旧租到了房东的房子通过代理,这就是所谓的代理模式,程序源自于生活,所以学编程的人,一般能够更加抽象的看待生活中发生的事情。

4.1.2.1案例1

准备工作:新建一个module,在下面新建包com.kuang.demo1,在里面创建类和接口

Rent . java 即抽象角色【接口】

public interface Rent {
    public void rent();
}

Host . java 即真实角色

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

Proxy . java 即代理角色

public class Proxy implements Rent{
    //卖的是房东的房子,就是有的意思
    private  Host host;
    public Proxy(Host host) {
        this.host = host;
    }
    public Proxy() {

    }
    public void rent() {
        //代理要租出去房子,所以要实现rent接口和接口里面的rent方法
        // ,但是他是帮host卖房子,所以租客租房子的时候要传进去房东对象
        //里面的方法还是房东租房子,只是多了一层关系
        seeHouse();
        host.rent();
        fare();
    }
    //看房功能
    public void seeHouse(){
        System.out.println("中介带你看房");
    }
    public void fare(){
        System.out.println("收费");
    }
}

Client . java 即客户【客户端访问代理角色】

public class Client {
    //真正要租房子的人
    public static void main(String[] args) {
        Host host = new Host();
        host.rent();//这是房东直接租房子
        Host host2=new Host();
        //找中介租房子,丢一个房东给他
        Proxy proxy = new Proxy(host2);
        proxy.rent();
    }
}

静态模式的好处

  1. 可以使得我们的真实角色更加纯粹 . 不再去关注一些公共的事情 .
  2. 公共的业务由代理来完成 . 实现了业务的分工 ,
  3. 公共业务发生扩展时变得更加集中和方便 .

缺点 :

类多了 , 多了代理类【一个真实角色就有一个代理】 , 工作量变大了 . 开发效率降低 .

4.1.2.2案例2

准备工作:创建包com.kuang.demo2在里面做操作

1、创建一个抽象角色,比如咋们平时做的用户业务,抽象起来就是增删改查!

public interface UserService {
    //抽象的业务包括增删改查
    public void add();
    public void delete();
    public void update();
    public void query();
}

2、我们需要一个真实对象来完成这些增删改查操作

//真实对象
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、需求来了,现在我们需要增加一个日志功能,怎么实现!

  • 思路1 :在实现类上增加代码 【麻烦!】
  • 思路2:使用代理来做,能够不改变原来的业务情况下,实现此功能就是最好的了!

4、设置一个代理类来处理日志!代理角色

public class UserServiceProxy  implements UserService{
     private UserServiceImpl userService;//被代理的真实角色
     
    public void setUserServiceimpl(UserServiceImpl userService) {
        this.userService = userService;
    }
//真实的增删改查还是userservice去做
    public void add() {
      log("add");
     userService.add();
    }

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

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

    public void query() {
        log("query");
userService.query();
    }
    //提供一个代理可做的方法,打印日志
    public void log(String msg){
        System.out.println("使用了"+msg+"方法");
    }
}

5、测试访问类:

public class Client {
    public static void main(String[] args) {
        UserServiceImpl userService = new UserServiceImpl();//new一个真实的
        UserServiceProxy userServiceProxy = new UserServiceProxy();//new一个代理
        userServiceProxy.setUserServiceimpl(userService);//代理设置真实的,或者new代理的时候传真实的进去
        userServiceProxy.delete();//代理调方法
    }
}

总结:我们在不改变原来的代码的情况下,实现了对原有功能的增强,这是AOP中最核心的思想

我们想要静态代理的好处,又不想要静态代理的缺点,所以 , 就有了动态代理 !

4.2动态代理模式

4.2.1介绍

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

动态代理的代理类是动态生成的 . 静态代理的代理类是我们提前写好的

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

  1. 基于接口的动态代理----JDK动态代理
  2. 基于类的动态代理–cglib

现在用的比较多的是 javasist 来生成动态代理 . 百度一下javasist

我们这里使用JDK的原生代码来实现,其余的道理都是一样的!

JDK的动态代理需要了解两个类

核心 : InvocationHandlerProxy , 打开JDK帮助文档看看

1-----【InvocationHandler:调用处理程序**】java.lang.reflect包下的**,是个接口

作用:调用处理程序并返回一个结果的

在代理实例上调用方法时,方法调用将被编码并分配到其调用处理程序的invoke方法

img

只有一个invoke方法

Object invoke(Object proxy, 方法 method, Object[] args);

//参数1 proxy - 调用该方法的代理实例,生成的代理类
//参数2method -所述方法对应于调用代理实例上的接口方法的实例。方法对象的声明类将是该方法声明的接口,它可以是代理类继承该方法的代理接口的超级接口。【代理和真实都要干的事情例如租房】是真正执行的方法【反射的原理】

//参数3args -包含的方法调用传递代理实例的参数值的对象的阵列,或null如果接口方法没有参数。原始类型的参数包含在适当的原始包装器类的实例中,例如java.lang.Integer或java.lang.Boolean 。【执行某方法需要的参数】

//返回值类型object 就是执行这个方法返回的值

解释:代理类的作用:代理某个对象,然后增强里面的方法【中介代理了房主,除了租房,还要带人去看房和收中介费】,这个方法就是method,其实也是被代理的对象自己要干的事情【租房】

invoke方法里面,去执行方法,就是 Object object= method.invoke(obj, args);这里的第一个参数obj就是要增强的对象【例如租房】,第二个就是参数,返回的类型就是你要返回的类型

2-----【Proxy : 代理,里面有静态方法,类名调】java.lang.reflect包下的

作用:生成动态代理实例的

img

img

有四个静态方法:

1getInvocationHandler返回指定代理实例的调用处理程序

@CallerSensitive
public static InvocationHandler getInvocationHandler(Object proxy)
    throws IllegalArgumentException
{

2isProxyClass仅使用getProxyClass或者newProxyInstance方法将指定的类动态生成代理类时,返回true

public static boolean isProxyClass(Class<?> cl) {
    return Proxy.class.isAssignableFrom(cl) && proxyClassCache.containsValue(cl);
}

3newProxyInstance返回指定接口的代理类实例,该接口将方法调用分配给指定的调用处理程序【重点】

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

img

三个参数:参数1–要用类加载器去加载生成的代理类

​ 参数2–说明你要生成的代理类的接口

​ 参数3–实现了InvocationHandler的类,这个类只需要实现一个invoke方法

4getProxyClass

@CallerSensitive
public static Class<?> getProxyClass(ClassLoader loader,
                                     Class<?>... interfaces)
    throws IllegalArgumentException
{
   //生成代理类proxy,自己写的
   public Object getProxy(){
   return Proxy.newProxyInstance(this.getClass().getClassLoader(),
                                                rent.getClass().getInterfaces(),this);
   }

4.2.2代码实现

抽象角色和真实角色和之前的一样

Rent . java 即抽象角色

public interface Rent {
    public void rent();
}

Host . java 即真实角色【真实角色通过implements实现接口】

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

ProxyInvocationHandler. java 即处理程序,并不是代理类【类的声明上并没有实现接口rent,而是在测试的时候ProxyInvocationHandler通过setRent实现的,需要传一个Rent,这个方法的意思就是生成的代理类要实现Rent接口**】

主要是代理类有变化,之前的代理类是写死的,现在要动态生成代理类

首先要继承InvocationHandler

public class ProxyInvocationHandler implements InvocationHandler {
    //被代理的接口
    private Rent rent;【现在只能实现一种代理,就是租房】
    public void setRent(Rent rent) {
        this.rent = rent;
    }
    //生成得到代理类proxy,第一个参数类加载器
    //重点是第二个参数,获取要代理的抽象角色!得到代理的接口,之前都是一个角色,现在可以代理一类角色【不只是host只一个实现了rent接口的】
    //第三个是InvocationHandler也就是它本身
    public Object getProxy(){
     return  Proxy.newProxyInstance(this.getClass().getClassLoader(),rent.getClass().getInterfaces(),this);
    }
    //处理代理实例,并返回结果,传入的参数是上面生成的代理类proxy,代理类 method : 代理类的调用处理程序的方法对象
    // 处理代理实例上的方法调用并返回结果
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        seeHouse();
        //本质:反射
        Object result = method.invoke(rent, args);//真正实现的是这句话
        fare();
        return result;

    }
    public  void seeHouse(){
        System.out.println("看房子");
    }
    public  void fare(){
        System.out.println("收租");
    }
}

总结:这个处理程序类干了两件事情:1得到代理类proxy,2调用invoke方法执行他真正要执行的方法。这个类只是一个处理程序,他可以生成代理类

Client . java

public class Client {
    public static void main(String[] args) {
        Host host = new Host();//真实角色
        ProxyInvocationHandler handler = new ProxyInvocationHandler();//写完这句还没有创建好代理角色
        //通过调用程序处理角色来处理我们要调用的接口对象
        handler.setRent(host);//生成的代理类实现这个Rent接口,需要传一个rent,而host实现了Rent接口,可以被传进来
/*        Object proxy = handler.getProxy();*/
       Rent proxy = (Rent) handler.getProxy();//通过get方法才得到这个代理角色,动态生成的
       proxy.rent();
    }
}

核心:一个动态代理 , 一般代理某一类业务 , 一个动态代理可以代理多个类,代理的是接口!、

4.2.3深入理解

来使用动态代理实现代理我们后面写的UserService!【重写4.1.2.2】

也可以编写一个通用的动态代理实现的类所有的代理对象设置为Object即可!【4.2.2只有一种代理rent】

ProxyInvocationHandler这个类的公式:代理谁+生成代理类+调用代理程序的一些执行方法。

public class ProxyInvocationHandler implements InvocationHandler {
    //被代理的接口,这里不只是租房Rent一种接口,可以实现很多,所以写Object
    private Object target;
    public void setTarget(Object target) {
        this.target = target;
    }
    //生成得到代理类proxy,第一个参数类加载器
    //重点是第二个参数,获取要代理的抽象角色!得到代理的接口,之前都是一个角色,现在可以代理一类角色
    //传给他什么代理接口就是什么,之前是单一的rent,现在是Object类的target
    //第三个是InvocationHandler也就是它本身
    public Object getProxy(){
     return  Proxy.newProxyInstance(this.getClass().getClassLoader(),target.getClass().getInterfaces(),this);
    }
    //处理代理实例,并返回结果,传入的参数是上面生成的代理类proxy,代理类 method : 代理类的调用处理程序的方法对象
    // 处理代理实例上的方法调用并返回结果
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
         log(method.getName());
        //本质:反射
        Object result = method.invoke(target, args);
     return  result;
    }
    //额外的方法只要在invoke方法附近做就好了,要在他之前做的写到前面,之后做的写到后面,
    // 这个invoke方法执行的是代理的方法,例如租房执行的就是租房,那么带客人看房和收中介费一个在他之前执行,一个在其之后执行
    public  void log(String msg){
        System.out.println("执行了"+msg+"方法");
    }
}

测试!

public class Client {
    public static void main(String[] args) {
        //真实角色一定得有
        UserServiceImpl userService=new UserServiceImpl();
        //代理对象的调用处理程序
        ProxyInvocationHandler pih = new ProxyInvocationHandler();
        pih.setTarget(userService);//要代理的对象,如果租房的话就是host,说白了就是代理真实对象
        UserService proxy =(UserService) pih.getProxy();//动态生成代理类
        proxy.delete();
    }
}

动态代理的好处:

静态代理有的它都有,静态代理没有的,它也有!

  • 可以使得我们的真实角色更加纯粹 . 不再去关注一些公共的事情 .

  • 公共的业务由代理来完成 . 实现了业务的分工

  • 公共业务发生扩展时变得更加集中和方便 .

    一个动态代理 , 一般代理某一类业务

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

【例如案例2,pih.setTarget(userService)这个里面的参数只要是实现了Userservice接口的类都可以】

5AOP

5.1简介

5.1.1聊聊AOP:纵向开发,横向开发【走代理类】

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-zqr5Jkyr-1621244126356)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20210516213235611.png)]

5.1.2AOP是什么:

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

img

5.1.3AOP在spring中的作用

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

以下名词需要了解下:

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

切面(ASPECT):横切关注点 被模块化 的特殊对象。即,它是一个类。.【举例:Log】

通知(Advice):切面必须要完成的工作。即,它是类中的一个方法。.【举例:日志Log里面的一个方法】

目标(Target):被通知对象。

代理(Proxy):向目标对象应用通知之后创建的对象。

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

连接点(JointPoint):与切入点匹配的执行点。
img

SpringAOP中,通过Advice定义横切逻辑,Spring中支持5种类型的Advice:

img

即 Aop 在 不改变原有代码的情况下 , 去增加新的功能 .

Spring的Aop就是将公共的业务 (日志 , 安全等) 和领域业务结合起来 , 当执行领域业务时 , 将会把公共业务加进来 . 实现公共业务的重复利用 . 领域业务更纯粹 , 程序猿专注领域业务 , 其本质还是动态代理

5.2使用Spring实现AOP的准备工作

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

原来的依赖只是spring和junit

现在需要导入AOP织入的包

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

5.3AOP实现方式1—Spring的 Api接口

不同实现方式的公共部分:业务接口和业务实现类是一样的。

步骤1首先编写我们的业务接口【com.kuang.service】和实现类【com.kuang.service】

public interface UserService {
    //抽象的业务包括增删改查
    public void add();
    public void delete();
    public void update();
    public void query();
}
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("查询了一个用户");
    }
}

步骤2写日志–前置日志,后置日志【com.kuang.log】

只需要implements aop包里的前置增强【 MethodBeforeAdvice】和后置增强【AfterReturningAdvice】

public class Log implements MethodBeforeAdvice {
    public void before(Method method, Object[] objects, Object o) throws Throwable {
        //method : 要执行的目标对象的方法
        //objects : 被调用的方法的参数
        //Object : 目标对象
        //这个方法会在执行方法之前自动调用,执行方法就是公共的增删改查
        System.out.println(o.getClass().getName()+"的"+method.getName()+"被执行了");
    }
}
public class AfterLog implements AfterReturningAdvice {
    public void afterReturning(Object returnValue, Method method, Object[] objects, Object target) throws Throwable {
        //returnValue 返回值
        //method被调用的方法
        //args 被调用的方法的对象的参数
        //target 被调用的目标对象

        System.out.println("执行了"+target.getClass().getName()+"的"+method.getName()+"方法,返回值:"+returnValue);
    }
}

步骤3–去spring的文件中注册 , 并实现aop切入实现 , 注意导入约束

在applicationContext.xml中写配置文件【首先写bean的注册,然后配置aop,

配置aop首先得有这三句话

xmlns:aop="http://www.springframework.org/schema/aop"
 http://www.springframework.org/schema/aop
 http://www.springframework.org/schema/ao[/spring-aop.xsd

配置aop首先需要配置切入点,expression:表达式匹配要执行的方法-内容为execution(要执行的位置 修饰符 返回值 类名 方法名 参数)

*代表任意的东西

< aop:config>在这个里面配置

<aop:pointcut id=“pointcut” expression=“execution(* com.kuang.service.UserServiceImpl.*(…))”/>

自己写的时候可以写成固定的方法,看需求

切入点这样写的意思就是,插入的位置是com.kuang.service.UserServiceImpl下的所有方法,(…)意思是参数

然后写执行环绕增加,就是在切入点前后去增加。advice-ref执行方法 . pointcut-ref切入点

<?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 id="userService" class="com.kuang.service.UserServiceImpl"/>
    <bean id="log" class="com.kuang.log.Log"/>
    <bean id="afterLog" class="com.kuang.log.AfterLog"/>
    <aop:config>
        <aop:pointcut id="pointcut" expression="execution(* com.kuang.service.UserServiceImpl.*(..))"/>
        <aop:advisor advice-ref="log" pointcut-ref="pointcut"/>
        <aop:advisor advice-ref="afterLog" pointcut-ref="pointcut"/>
    </aop:config>
</beans>

步骤4–测试【动态代理代理的是接口,所以要强转为接口而不是具体的实现类】

public class Mytest {
    public static void main(String[] args) {
       ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
       //动态代理代理的是接口,所以要强转为接口而不是具体的实现类
        UserService userService = (UserService) context.getBean("userService");//先用这个业务实例
        userService.add();
    }
}

小结:service干自己的业务,log写自己的日志,二者并没关系,通过spring的切入连接到一起。

5.4AOP实现方式2–自定义切入点类【切面】

目标业务类不变依旧是userServiceImpl

步骤1:自定义切入点类【增强类,切面】

目的:就是在增删改查的前面加一段话,后面加一段话。

作用:一个类实现了方式1的前置日志和后置日志两个类。

切面里面的方法叫做通知。

public class DiyPointCut {
    public void before(){
        System.out.println("------方法执行前");
    }
    public void after(){
        System.out.println("------方法执行前");
    }
}

步骤2:去spring中配置【注册bean和sop的配置】

aspect切面,是个类,自己写的类,切入点不变,在切入点执行这个类的哪个方法,之后执行这个类里的哪个方法

< aop:config>在这个里面配置

 <bean id="diy" class="com.kuang.diy.DiyPointCut"/>
 <aop:config>
<aop:aspect ref="diy">//把这个类标记为切面
    <aop:pointcut id="pointcut" expression="execution(* com.kuang.service.UserServiceImpl.*(..))"/>
    <aop:before method="before" pointcut-ref="pointcut"/>
    <aop:after method="after" pointcut-ref="pointcut"/>
</aop:aspect>
 </aop:config>

测试

public class Mytest {
    public static void main(String[] args) {
       ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
       //动态代理代理的是接口,所以要强转为接口而不是具体的实现类
        UserService userService = (UserService) context.getBean("userService");//先用这个业务实例
        userService.add();
    }
}

5.5AOP实现方式3–注解

【之前的配置用注解去做了】其实就是在方式而上做了改动

步骤1编写一个注解实现的增强类【同注解实现切面,在配置文件里面写的有关切面的都用注解标记在类里面】

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.kuang.service.UserServiceImpl.*(..))")//切面有了,通知有了,所以这里写的是切入点
    public void before(){
        System.out.println("------方法执行前");
    }
    @After("execution(* com.kuang.service.UserServiceImpl.*(..))")
    public void after(){
        System.out.println("------方法执行后");
    }
    @Around("execution(* com.kuang.service.UserServiceImpl.*(..))")
    //在环绕增强中,给定一个参数,代表要获取处理切入的点
    public void Around(ProceedingJoinPoint jp) throws Throwable{
        System.out.println("环绕前");
        //执行方法
        Object proceed = jp.proceed();
        System.out.println("环绕后");
    }
}

分析先是环绕前–执行方法—环绕后,然后执行方法的时候,会有执行方法前和后。所以最后的顺序就是环绕前–执行方法前–执行方法–环绕后–执行方法后

步骤2:在Spring配置文件中,注册bean,并增加支持注解的配置

  1. 注册bean的时候可以用注解,也可以写bean标签
  2. 因为用了注解,所以要开启注解支持
 <bean id="annotationPointCut" class="com.kuang.diy.AnnotationPointCut"/>
<aop:aspectj-autoproxy/>

说明

  1. 开启注解支持,默认是jdk,还有另外一种方法cglib
  2. 通过aop命名空间的<aop:aspectj-autoproxy />声明自动为spring容器中那些配置@aspectJ切面的bean创建代理,织入切面。当然,spring 在内部依旧采用AnnotationAwareAspectJAutoProxyCreator进行自动代理的创建工作,但具体实现的细节已经被<aop:aspectj-autoproxy />隐藏起来了
  3. <aop:aspectj-autoproxy />有一个proxy-target-class属性,默认为false,表示使用jdk动态代理织入增强,当配为<aop:aspectj-autoproxy poxy-target-class="**true"/>时,表示使用CGLib动态代理技术织入增强。**不过即使proxy-target-class设置为false,如果目标类没有声明接口,则spring将自动使用CGLib动态代理。方式不同,结果一样。

步骤3:测试

public class Mytest {
    public static void main(String[] args) {
       ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
       //动态代理代理的是接口,所以要强转为接口而不是具体的实现类
        UserService userService = (UserService) context.getBean("userService");//先用这个业务实例
        userService.delete();
    }
}

执行结果:【注意注意】

环绕前
------方法执行前
删除了一个用户
环绕后
------方法执行后

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值