Spring ioc和aop小结

IOC

什么是IOC

  • 控制反转,把对象创建和对象之间的调用过程交给Spring进行管理
  • 使用IOC的目的:为了降低耦合度

IOC底层原理

解析xml文件,获取bean标签的class属性,即全类名,通过反射获取到其Class对象,调用newInstance方法来创建一个对象。

IOC容器

IOC思想基于IOC容器完成,IOC容器底层就是对象工厂。

Spring提供IOC容器实现的两种方式(两个接口)

两个接口作用相似,都可以通过加载配置文件,通过对象工厂创建对象。不过两者有一定的区别。

  • BeanFactory

    IOC容器的基本实现,是Spring内部使用的接口,不提供开发人员进行使用。

    特点:延迟加载,调用的时候再创建对象。

  • ApplicationContext

    BeanFactory接口的子接口,提供更多更强大的功能,一般由开发人员进行使用。 基本上所有场合都能直接用ApplicationContext。在加载配置文件时就会创建全部对象。

    Application的三个实现类:

    1. ClassPathXmlApplicationContext:它可以加载类路径下的配置文件,要求配置文件必须在类路径下。不在的话加载不了
    2. FileSystemXmlApplicationContext:它可以加载磁盘任意路径下的配置文件(必须要有访问权限)
    3. AnnotationConfigApplicationContext:他是用于读取注解注解配置容器的类

IOC操作Bean管理

什么是Bean管理

Bean管理指的是两个操作:1. Spring创建对象。2. Spring注入属性

在XML中Bean对象的三种创建方式

  • 通过调用无参构造器创建。如果类中没有无参构造器,就会创建失败。
  • 使用普通工厂中的成员方法创建对象。先注册Bean工厂,然后注册需要的Bean对象,通过factory-bean属性指定工厂,factory-method指定工厂中的方法,就能够创建Bean对象。
  • 使用工厂的静态方法创建对象。通过Bean标签的class属性指向工厂方法,factory-method指向静态方法,这样就可以获得一个Bean对象。相比于上面使用成员方法创建对象,可以少注册一个工厂的Bean对象。

Bean管理操作的两种方式

  • 基于xml配置文件方式实现
  • 基于注解方式实现
基于xml方式
  1. 基于xml方式创建对象

    <!--配置xx对象的创建-->
    <bean id="xx" class="xx.xx.xx"/>
    
    • 在xml文件中,使用bean标签,标签里添加对应的属性,就可以实现对象创建
    • bean标签的常用属性
      • id属性:容器中的唯一标识,不允许重复
      • class属性:类全路径(包类路径)
    • 创建对象时候,默认是执行无参数构造方法完成对象创建
  2. 基于xml方式注入属性

    DI(Dependency Injection,依赖注入): DI是IOC的一种具体实现,就是注入属性,有以下两种方式在xml中注入属性

    1. 使用set方法注入:使用bean标签的property属性进行注入。

      <bean class="com.me.pojo.Dog" id="dog">
              <property name="name" value="汪酱"/>
              <property name="weight" value="20"/>
          </bean>
      
    2. 使用有参构造注入属性

      <bean class="com.me.pojo.Dog" id="dog">
              <constructor-arg name="name" value="汪酱"/>
              <constructor-arg name="weight" value="20"/>
          </bean>
      
基于注解方式
  1. 基于注解创建对象,在类上加入注解@Component,@Service,@Controller,@Repository,可以自动在xml中创建相应的Bean实例。这四个注解的功能是完全一样的,名字不同可以快速确定当前这个类是什么类型的服务。
/*
  这里的value对应的是bean标签中的id属性,class属性会获取当前的类的包类路径。
  如果不加value属性,默认是首字母小写的类名
*/
@Service(value ="xxService")
  1. 基于注解方式实现属性注入

    • @Autowired

      根据属性类型进行注入,在属性上添加@Autowired后,可以不添加该属性的setter方法,因为该注解底层是通过反射进行注入了,并不需要走setter方法。

    • @Qualifier

      根据属性名称进行注入,可以与@Autowired注解一起使用。

    • @Resource

      可以根据类型注入,也可以根据名称注入

    • @Value

      用在成员属性上,给属性添加基本类型+String的值

Bean的作用域

通过bean标签中的scope管理Bean对象的作用域

  1. singleton(默认):单例,在解析xml文件时创建,并全局唯一
  2. prototype:多例(原型),在调用getBean方法时创建对象,每个对象独立,互不干扰。
  3. request:作用于web应用的请求范围
  4. session:作用于web应用的会话范围

Bean的生命周期

生命周期:从对象创建到对象销毁的过程

Bean标签中有两个属性用于配置bean对象在生命周期的一些方法。

init-method=“”:配置初始化方法。

destroy-method=“”:配置销毁方法。

  1. 通过构造器创建Bean实例 bean(无参构造)
  2. 为bean的属性设置值和对其他bean的引用(调用set方法)
  3. 调用bean的初始化方法(需要配置init-method)
  4. bean可以使用了(对象获取到了)
  5. 当容器关闭时,调用bean的销毁方法(需要配置destroy-method)

单例对象的生命周期:与容器的生命的周期相同,容器创建就创建,容器销毁就销毁。

多例对象的生命周期:在被调用时创建,当无用时销毁。

Bean的自动装配

使用bean标签的autowire属性进行自动注入。

一般autowire常用属性有两个:

  1. byType ,根据对象类型进行自动注入,在xml搜索与当前bean中的属性的类型相同的bean,然后进行set注入。

  2. byName,根据name或者id进行查找,查找到与属性的属性名相同的bean时进行注入。

全注解开发

在Java类上使用注解@Configuration表明该Java类是Spring的配置类,用于替代xml配置文件,这样一来就可以不写xml文件。

关于在Junit中无法自动装配的问题

如果出现在Junit单元测试中使用@Autowired无法自动装配,完成以下步骤以可。

  1. 导入spring-test依赖,然后在Test类上加入注解@RunWith(SpringJUnit4ClassRunner.class)
  2. 加入注解@ContextConfiguration,来标明想要导入这个测试类的Bean。
  3. 如果使用的是xml文件作为配置,则使用@ContextConfiguration的locations属性,写入所有配置了Bean的xml文件。要注意xml文件中需要写component-scan标签,扫描所有的bean对象。
  4. 如果使用全注解开发,则使用classes属性添加作为配置的Java类。要注意使用@ComponentScan注解扫描Bean对象所在的位置。

AOP

什么是AOP

面向切面编程,利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。

通俗描述:不通过修改源代码的方式,在主干功能中添加一个新功能。

底层原理

AOP底层使用动态代理,有两种情况的动态代理

  1. 有接口情况,使用JDK动态代理,通过创建一个接口实现类的代理对象并加入额外的功能,最终完成功能的添加。
  2. 没有接口情况,使用CGLIB动态代理,通过继承创建一个子类的代理对象,加入额外的功能,最终完成功能的添加。

JDK动态代理

使用Proxy类(在java.lang.reflect中)里面的方法创建对象。通过调用里面的newProxyInstance方法返回指定接口的代理类的实例。方法有三个参数。

  1. 第一个参数是类加载器。可以通过调用对象的getClass方法获得Class对象,然后调用getClassLoader获得类加载器。
  2. 第二个参数是该类实现的接口。与上述步骤差不多,获得Class对象后,调用getInterfaces获取该类实现的接口。
  3. 第三个参数是InvocationHandler(接口),需要自己写一个其实现类,在里面完成功能的增强。

步骤

  1. 创建接口,定义方法

    public interface CalculateDao {
        int add(int a, int b);
    
        CalculateDao update(String id);
    }
    
  2. 创建接口实现类,实现方法

    public class CalculateDaoImpl implements CalculateDao {
       @Override
        public int add(int a, int b) {
            System.out.println("add方法执行了");
            return a+b;
        }
    
        @Override
        public CalculateDao update(String id) {
            System.out.println("update方法执行了");
            System.out.println(id);
            return this;
        }
    }
    
  3. 使用Proxy类创建接口代理对象前需要实现InvocationHandler

    class ProxyFactory implements InvocationHandler {
        private Object obj;
    
        /**
         * @param obj Object包容所有传进来对象
         */
        public ProxyFactory(Object obj) {
            this.obj = obj;
        }
    
        /**
         * @param proxy  代理对象信息
         * @param method 被调用的方法
         * @param args   方法传入的参数
         * @return 返回值
         * @throws Throwable 抛异常
         */
        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    	    if ("update".equals(method.getName())) {
                method.invoke(obj, args);
                return proxy;
            }
            System.out.println("方法执行前....."+method.getName()+":传递的参数"+ Arrays.toString(args));
            Object result = method.invoke(obj, args);
            System.out.println("方法执行后....."+obj);
            return result;
        }
    }
    
  4. 调用Proxy的newInstance方法创建代理对象

    CalculateDao cal = (CalculateDao) Proxy.newProxyInstance(JDKProxy.class.getClassLoader(), CalculateDaoImpl.class.getInterfaces(), new ProxyFactory(new CalculateDaoImpl()));
    System.out.println("结果为:"+cal.add(1, 5));
    /*
    控制台输出:
    方法执行前.....add:传递的参数[1, 5]
    add方法执行了
    方法执行后.....
    结果为:6
    */
    

关于Invoke方法中的第一个参数proxy

用法:可以在特定条件下让方法返回proxy,达到对该代理对象的连续调用。

注意事项:如果要返回proxy并连续调用,那么被调用的方法的返回值一定要是接口类型

//在Main方法中
CalculateDao cal = (CalculateDao) Proxy.newProxyInstance(JDKProxy.class.getClassLoader(), CalculateDaoImpl.class.getInterfaces(), new ProxyFactory(new CalculateDaoImpl()));
cal.update("13").update("12");
/*
结果为:
update方法执行了
13
update方法执行了
12
*/

AOP术语

  • 连接点

    类里面可以被增强的方法,这些方法被称为连接点。

  • 切入点

    实际被增强的方法被成为切入点。

  • 通知(增强)

    • 实际增强的逻辑部分成为通知(增强)

    • 通知有多种类型

      1. 前置通知( @Before )

        在被增强的方法执行前会执行

      2. 后置通知( @AfterReturning )

        在被增强的方法执行后会执行,被增强方法出现异常后将不会执行

      3. 环绕通知( @Around )

        在被增强的方法执行的前后都会执行,环绕通知的写法比较特殊。环绕通知需要有返回值,并且与被增强方法的返回值一致。 如下所示。环绕通知可以做到其他4个通知的功能。

        @Around(value = "execution(* com.me.spring.aopwithannotation.User.add(..))")
            public Object around(ProceedingJoinPoint proceedingJoinPoint){
                Object result = null;
                try {
                    System.out.println("环绕前置");
                    //proceed方法用于执行被增强的方法
                    result= proceedingJoinPoint.proceed(proceedingJoinPoint.getArgs());
                    System.out.println("环绕后置");
                } catch (Throwable throwable) {
                    throwable.printStackTrace();
                }
                return result;//其他通知获得的返回值就是环绕通知的返回值
            }
        
      4. 异常通知( @afterThrowing)

        在被增强的方法出现异常时会执行

      5. 最终通知(@After)

        类似于java代码块的finally,无论是否出现异常,都会执行

  • 切面

    切面是通知和切入点的结合

  • 织入

    把切面应用到目标对象来创建新的代理对象的过程

AOP操作(准备)

Spring框架一般都是基于AspectJ实现AOP操作

什么是AspectJ

AspectJ不是Spring的组成部分,是一个独立的AOP框架。一般把AspectJ和Spring框架一起使用,进行AOP操作。

切入点表达式

execution([权限修饰符] [返回类型] [类全路径] [方法名称] ([参数列表]))

基于AspectJ实现AOP操作

基于注解方式实现(一般使用)

相同切入点的抽取,如下所示

//抽取了公共点以后,接下来的切入只需要在value值中写入pointCur()即可
@Pointcut(value = "execution(* com.me.spring.aopwithannotation.User.add(..))")
    public void pointCut(){ }

多个代理类同时对一个方法进行增强:

//每个代理类上都加上Order注解,里面的x为数字,代表优先级。数字越小,优先级越高
@Order(x)
  • 创建被增强类User

    @Component
    public class User {
        public int add(int a, int b) {
            return a + b;
        }
    }
    
  • 创建增强类UserProxy

    @Component
    @Aspect
    public class UserProxy {
        @Pointcut(value = "execution(* com.me.spring.aopwithannotation.User.add(int,int))")
        public void pointCut() {
        }
    
        @Before(value = "pointCut()")
        public void logStart(JoinPoint joinPoint) {
            Signature signature = joinPoint.getSignature();
            System.out.println("[" + signature.getName() + "]开始执行,参数为" + Arrays.asList(joinPoint.getArgs()));
        }
    
        @After(value = "pointCut()")
        public void logEnd(JoinPoint joinPoint) {
            Signature signature = joinPoint.getSignature();
            System.out.println("[" + signature.getName() + "]方法执行完成");
        }
        
    
        @AfterThrowing(value = "pointCut()", throwing = "exception")
        public void afterThrowing(JoinPoint joinPoint, Exception exception) {
            Signature signature = joinPoint.getSignature();
            System.out.println("[" + signature.getName() + "] 出现异常,异常信息为" + exception);
        }
    
        @AfterReturning(value = "pointCut()", returning = "result")
        public void logReturn(JoinPoint joinPoint, Object result) {
            Signature signature = joinPoint.getSignature();
            System.out.println("[" + signature.getName() + "]方法正常执行,结果为" + result);
        }
    }
    
  • 配置xml文件

    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="http://www.springframework.org/schema/beans"
           xmlns:context="http://www.springframework.org/schema/context"
           xmlns:aop="http://www.springframework.org/schema/aop"
           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
                            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/aop/spring-aop.xsd">
        <context:component-scan base-package="com.me.spring.aopwithannotation"/>
        <aop:aspectj-autoproxy/>
    </beans>
    
  • 测试结果

    ApplicationContext context=new ClassPathXmlApplicationContext("applicationContext.xml");
    User user = context.getBean("user", User.class);
    user.add(1, 5);
    /*
    运行结果:
    [add]开始执行,参数为[1, 5]
    [add]方法正常执行,结果为6
    [add]方法执行完成
    */
    
基于xml配置文件实现
<!--xml文件配置-->
<!--注册被增强类与增强类-->
    <bean id="user" class="com.me.spring.aopwithannotation.User"/>
    <bean id="userProxy" class="com.me.spring.aopwithannotation.UserProxy"/>
    <aop:config>
        <!--选择切入点-->
        <aop:pointcut id="addPoint" expression="execution(* com.me.spring.aopwithannotation.User.add(..))"/>
        <!--配置切面-->
        <aop:aspect ref="userProxy">
            <!--配置通知-->
            <aop:before method="logStart" pointcut-ref="addPoint"/>
            <aop:after-returning method="logReturn" pointcut-ref="addPoint" returning="result"/>
            <aop:after method="logEnd" pointcut-ref="addPoint"/>
        </aop:aspect>
    </aop:config>
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值