一篇文章带你了解、学习、体验、回顾Spring基础

   日期:2020-05-30     浏览:104    评论:0    
核心提示:文章目录Spring介绍优点组成框架结构Spring配置的可选方案Spring Ioc 与 DI创建第一个Ioc程序创建第一个DI程序总结Java Bean的相关问题`bean`的id和name属性的配置`bean`声明周期的配置`bean`的作用范围的配置Spring中Bean的实例化方式无参构造方法方式静态工厂实例化的方式实例工厂实例化的方式Spring中Bean的属性注入有参构造函数注入set方法的方式注入属性SpEL表达式装配集合Spring的分模块开发的配置加载多个配置文件引入多个配置文件Spr

文章目录

    • Spring介绍
      • 优点
      • 组成
      • 框架结构
    • Spring配置的可选方案
    • Spring Ioc 与 DI
      • 创建第一个Ioc程序
      • 创建第一个DI程序
      • 总结
    • Java Bean的相关问题
      • `bean`的id和name属性的配置
      • `bean`声明周期的配置
      • `bean`的作用范围的配置
    • Spring中Bean的实例化方式
      • 无参构造方法方式
      • 静态工厂实例化的方式
      • 实例工厂实例化的方式
    • Spring中Bean的属性注入
      • 有参构造函数注入
      • set方法的方式注入属性
      • SpEL表达式
      • 装配集合
    • Spring的分模块开发的配置
      • 加载多个配置文件
      • 引入多个配置文件
    • Spring注解开发
      • 简单的注解实例
      • 注入属性注解
      • Scope作用范围注解
      • 生命周期注解
      • 用javaConfig的方式配置注解
      • 为组件扫描的bean命名
      • 设置扫描组件基础包
      • 通过bean添加注解实现自动装配
    • 代理模式
      • 静态代理模式
      • 动态代理
    • 切面编程-AOP
      • 通过Spring API实现的AOP
      • 自定义类来实现AOP
      • 注解实现AOP
      • 常用API

Spring介绍

优点

  • Spring是一个免费开源的框架(容器)
  • Spring是一个轻量级、非入侵式的框架:从框架的角度可以理解为:无需继承框架提供的任何类
    这样我们在更换框架时,之前写过的代码几乎可以继续使用。
  • 反转控制(IoC)、面向切面编程(AOP)
  • 支持事务的处理,对框架整合支持

组成

框架结构

  • Data Access/Integration层包含有JDBC、ORM、OXM、JMS和Transaction模块。

  • Web层包含了Web、Web-Servlet、WebSocket、Web-Porlet模块。

  • AOP模块提供了一个符合AOP联盟标准的面向切面编程的实现。

  • **Core Container(核心容器):**包含有Beans、Core、Context和SpEL模块。

     Bean -- 处理Bean的jar
     context -- 处理spring上下文的jar
     core -- Spring 核心jar
     expression -- Spring 表达式
    
  • Test模块支持使用JUnit和TestNG对Spring组件进行测试。

Spring配置的可选方案

  • 在XML中进行显式配置;
  • 在java中进行显式配置;
  • 隐式的bean发现机制和自动装配;

很多情形下选择哪种配置方案很大程度上是个人喜好的原因,当然还可以进行各种配置方案之间的互配。在《Spring in action》书中,作者建议使用以下顺序:

  1. 自动装配的机制;
  2. 当必须要使用显示配置bean的时候,推荐使用javaConfig,它的优势是比XML显示配置更安全且更加强大;
  3. 最后选择XML配置;

Spring Ioc 与 DI

IoC:Inverse of Control(控制反转) : 它不是什么技术,而是一种设计思想,就是将原本在程序中手动创建对象的控制权,交由Spring框架来管理。

DI:Dependency Injection(依赖注入) : 指 Spring 创建对象的过程中,将对象依赖属性(简单值,集合,对象)通过配置设值给该对象

创建第一个Ioc程序

​ 首先,我们需要创建Spring的配置文件,Spring配置文件的名称和位置没有固定要求,一般建议把该文件放到src目录下面,名称可随便写,官方建议写成applicationContext.xml。然后我们还需要在配置文件中引入约束,Spring学习阶段的约束是schema约束。那么问题来了,这个约束又该怎么写呢?可参考docs\spring-framework-reference\html目录下的xsd-configuration.html文件,在其内容最后面找到如下内容。

​ 将其复制黏贴到配置文件applicationContext.xml中,这样applicationContext.xml文件的内容就是下面的样子了。

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
</beans>
  • 创建一个接口然后将其实现。

    package com.yy.dao;
    
    public interface UserDao {
        void saveUser();
    }
    
    import com.yy.dao.UserDao;
    
    public class UserDaoImpl implements UserDao {
        public void saveUser() {
            System.out.println("存储了User对象....");
        }
    }
    
  • 然后将这个类交给Spring管理,即在文件中配置对象的创建。

    <bean name="userDao" class="com.yy.dao.Impl.UserDaoImpl"></bean>
    
  • 测试类进行测试

    我们先把获取ApplicationContext对象的过程封装成一个工具类,相对能简化一下开发步骤。

    import org.springframework.context.ApplicationContext;
    import org.springframework.context.support.ClassPathXmlApplicationContext;
    
    public class applicationContextUtil {
        public static ApplicationContext getApplicationContext(){
            return new ClassPathXmlApplicationContext("applicationContext.xml");
        }
    }
    

    进行测试

    @Test
    public void saveUser(){
        ApplicationContext context = applicationContextUtil.getApplicationContext();
        UserDao userDao = (UserDao) context.getBean("userDao");
        userDao.saveUser();
    }
    
  • 结果

    存储了User对象....
    
  • 总结:发现虽然我们并没有创建UserDao对象但是我们依然通过applicationContext.xml里面的配置,从而拿取了对象。

创建第一个DI程序

  • 首先创建一个实体类

    package com.yy.pojo;
    
    import lombok.Data;
    
    @Data
    public class User {
        private Integer id;
        private String username;
        private String address;
    }
    
  • 然后在applicationContext.xml中给实体类注入数据

    <bean name="user" class="com.yy.pojo.User">
        <property name="id" value="201601"/>
        <property name="username" value="大脸猫"/>
        <property name="address" value="清华大学"/>
    </bean>
    
  • 测试类

    @Test
    public void getUser(){
        ApplicationContext context = applicationContextUtil.getApplicationContext();
        User user = (User)context.getBean("user");
        System.out.println(user.toString());
    }
    
  • 测试结果

    User(id=201601, username=大脸猫, address=清华大学)
    

总结

IoC 和 DI 其实是同一个概念的不同角度描述,DI 相对 IoC 而言,明确描述了被注入对象依赖 IoC 容器配置依赖对象

DI实现过程:

  • context.getBean("user")先根据要求找到,xml或注解中相应的配置。
  • 看看user依赖的是哪个实体类,拿到类名。
  • 使用反射的API,基于类名实例化对应的对象实例
  • 将对象实例,通过构造函数或者setter,传递给user对象

Java Bean的相关问题

bean的id和name属性的配置

​ 从之前编写的Spring配置文件中,可以发现<bean>标签中有一个id属性,其实它还有一个和id属性类似的name属性,根据它俩的值均能得到配置对象。

**id属性:**在Spring配置文件中会有多个bean标签,但它们的id属性值是不能相同的。Bean起名字时,在约束中采用的是ID约束(唯一约束),而且名字必须以字母开始,可以使用字母、数字、连字符、下划线、句号、冒号等,但id属性值绝对不能有特殊符号;

**name属性:**没有使用约束中的唯一约束,理论上name属性值是可以出现重复的,但是这在实际开发中是不可能出现的,而且它里面可以出现特殊字符。其实,在Spring和Struts1框架进行整合的时候,才有可能会用到。

bean声明周期的配置

​ 通过配置<bean>标签上的init-method作为Bean被初始化的时候执行的方法,配置destroy-method作为bean被销毁的时候执行的方法,要想bean被销毁的时候执行destroy-method属性中配置的方法,那么bean得是单例创建的(默认即单例创建),而且只有工厂被关闭时,destroy-method属性中配置的方法才能被执行。

  • 同样实现一个接口:
package com.yy.dao.Impl;

import com.yy.dao.UserDao;

public class UserDaoImpl implements UserDao {
    public void saveUser() {
        System.out.println("存储了User对象....");
    }

    public void start(){
        System.out.println("初始化...");
    }
    
    public void end(){
        System.out.println("销毁...");
    }
}
  • 在配置文件中配置初始化和销毁方法

    <bean name="userDao" class="com.yy.dao.Impl.UserDaoImpl" init-method="start" destroy-method="end"/>
    
  • 测试

    @Test
    public void saveUser(){
        ApplicationContext context = applicationContextUtil.getApplicationContext();
        UserDao userDao = (UserDao) context.getBean("userDao");
        userDao.saveUser();
    }
    
  • 结果

    初始化...
    存储了User对象....
    
  • 我们发现实际上并没有执行deploy方法,原因是ApplycationContext对象并没有关闭。

    @Test
    public void saveUser(){
        AbstractApplicationContext context = (AbstractApplicationContext) applicationContextUtil.getApplicationContext();
        UserDao userDao = (UserDao) context.getBean("userDao");
        userDao.saveUser();
        context.close();
    }
    
  • 结果

    初始化...
    存储了User对象....
    销毁...
    

    ​ 要想bean被销毁的时候执行destroy-method属性中配置的方法,那么前提bean得是单例创建的,默认即单例创建,就像下面这样。

    <.....scope="singleton"/>	
    

    若是设置成多例模式结果则不同。

bean的作用范围的配置

<bean>标签上有一个scope属性,它代表Bean的作用范围。该属性有五个属性值,分别是:

  • singleton:scope属性的默认值,spring会采用单例模式创建这个对象。

    单例模式时,创建的对象时相同的,多例模式则相反。

  • prototype:spring会采用多例模式创建这个对象。

  • request:应用在web项目中,spring创建这个类以后会将这个类存入到request域当中。

  • session:应用在web项目中,spring创建这个类以后会将这个类存入到session域当中。

  • globalsession:应用在web项目中,必须是prolet环境(你在一个地方存入数据以后,其它一些子系统当中就不需要进行登录了)下才能使用,他要单点登录(即SSO,single sign on)上,但如果没有这种环境,你配置一个global session就相当于配置了一个session。

Spring中Bean的实例化方式

Bean交给Spring管理之后,它在创建这些Bean的时候,有以下三种方式:

  1. 无参构造方法的方式(默认)
  2. 静态工厂实例化的方式
  3. 实例工厂实例化的方式

无参构造方法方式

​ 这种方式是Spring实例化Bean的默认方式,指的是在创建对象的时候,Spring会调用类里面的无参数的构造方法实现。

​ 上面应用的实例全是无参构造方法方式。

静态工厂实例化的方式

  • 首先创建一个Bean类

    package com.yy.spring;
    
    public class Bean {
        
        public Bean(){
            System.out.println("Bean被执行");
        }
    }
    
  • 然后创建一个Bean工厂

    package com.yy.spring;
    
    public class BeanFactory {
        
        public static Bean createBean(){
            System.out.println("BeanFactory被执行....");
            return  new Bean();
        }
    }
    
  • 配置文件

    <bean id="bean" class="com.yy.spring.BeanFactory" factory-method="createBean"></bean>
    
  • 测试结果

    BeanFactory被执行....
    Bean被执行
    

实例工厂实例化的方式

创建一个工厂类,在工厂类里面提供一个普通的方法,这个方法返回类对象,调用工厂类的方法时,创建工厂类对象,使用对象调用方法即可

  • 首先创建一个Bean类

    public class Bean {
        
        public Bean(){
            System.out.println("Bean被执行");
        }
    }
    
  • 然后创建一个Bean工厂

    public class BeanFactory {
        
        public Bean createBean(){
            System.out.println("BeanFactory被执行....");
            return  new Bean();
        }
    }
    
  • 进行相关配置

    <bean id="BeanFactory" class="com.yy.spring.BeanFactory"></bean>
    <bean id="bean" factory-bean="BeanFactory" factory-method="createBean"></bean>
    
  • 测试结果

    BeanFactory被执行....
    Bean被执行
    

Spring中Bean的属性注入

实际上,有关Bean的属性注入共有2种方式

  • 有参构造函数注入
  • set方法注入

有参构造函数注入

  • 创建一个实体类

    package com.yy.spring.domain;
    
    public class Car {
        private String name;
        private Double price;
    
        public Car(String name,Double price){
            super();
            this.name = name;
            this.price = price;
        }
    }
    
  • 然后,在Spring配置文件中对以上JavaBean进行如下配置。

    <bean id="car" class="com.yy.spring.pojo.Car">
        <constructor-arg name="name" value="汽车"></constructor-arg>
        <constructor-arg name="price" value="221.2"></constructor-arg>
    </bean>
    

set方法的方式注入属性

  • 同样创建一个实体类在实体类中生成set方法

    public class Car {
        private String name;
        private Double price;
    
        public void setName(String name) {
            this.name = name;
        }
    
        public void setPrice(Double price) {
            this.price = price;
        }
    }
    
  • 在配置文件中进行相关的配置

    <bean id="car" class="com.yy.spring.domain.Car">
        <property name="name" value="汽车"></property>
        <property name="price" value="2.213"></property>
    </bean>
    

SpEL表达式

​ 在Spring3.0版本以后,提供了一种SpEL表达式语言的属性注入方式。SpEL,即Spring Expression Language,翻译过来就是Spring的表达式语言,其语法格式是#{SpEL}

使用了这种SpEL表达式语言的属性注入方式,还可以调用其他类的属性或者方法。

<bean name="car" class="com.yy.pojo.Car">
    <property name="name" value="兰博基尼"/>
    <property name="price" value="22"/>
</bean>
<bean name="carInfo" class="com.yy.pojo.CarInfo">
    <property name="name" value="#{car.name}"/>
    <property name="price" value="#{22 + 7}"/>
</bean>

如上,carInfo中使用了SpEL表达式,在表达式中既可以引用其它bean中的数据也可以在其中书写表达式

注意事项:CarInfo的属性值是通过SpEl从Car类中的属性值赋值而来,所以Car类中必须要有setget方法,set方法是为了用于配置文件进行依赖注入为Car的属性进行赋值,get方法是为了用于获取Car属性的值,进而为CarInfo进行赋值,而CarInfo只需要接受Car的属性值所以CarInfo类中只需要存在set方法即可

装配集合

<bean id="complexAssembly" class="pojo.ComplexAssembly">
    <!-- 装配Long类型的id -->
    <property name="id" value="1"/>
    
    <!-- 装配List类型的list -->
    <property name="list">
        <list>
            <value>value-list-1</value>
            <value>value-list-2</value>
            <value>value-list-3</value>
        </list>
    </property>
    
    <!-- 装配Map类型的map -->
    <property name="map">
        <map>
            <entry key="key1" value="value-key-1"/>
            <entry key="key2" value="value-key-2"/>
            <entry key="key3" value="value-key-2"/>
        </map>
    </property>
    
    <!-- 装配Properties类型的properties -->
    <property name="properties">
        <props>
            <prop key="prop1">value-prop-1</prop>
            <prop key="prop2">value-prop-2</prop>
            <prop key="prop3">value-prop-3</prop>
        </props>
    </property>
    
    <!-- 装配Set类型的set -->
    <property name="set">
        <set>
            <value>value-set-1</value>
            <value>value-set-2</value>
            <value>value-set-3</value>
        </set>
    </property>
    
    <!-- 装配String[]类型的array -->
    <property name="array">
        <array>
            <value>value-array-1</value>
            <value>value-array-2</value>
            <value>value-array-3</value>
        </array>
    </property>
</bean>

Spring的分模块开发的配置

Spring中的分模块开发有两种配置方式,

加载多个配置文件

Spring分模块开发的第一种配置方式就是指在加载配置文件的时候,一次加载多个配置文件。

new ClassPathXmlApplicationContext("applicationContext.xml","TApplicationContext.xml");

引入多个配置文件

​ Spring分模块开发的第二种配置方式就是指在一个配置文件中引入多个配置文件。这样说的话,我们可以在applicationContext.xml文件中引入TApplicationContext.xml文件。

<import resource="TApplicationContext.xml"/>

Spring注解开发

用IoC进行注解开发,需要进行配置一个扫描组件,就告诉他Spring项目下哪些包使用IoC注解了。

<!--配置一个扫描组件,告诉spring一个注解使用的包-->
<!--当com.yy.spring包下面还有其它子包那么那么同样会被扫描-->    
<context:component-scan base-package="com.yy.spring"></context:component-scan>
<!--当然还可以一次配置多个包,中间用逗号隔开-->
<context:component-scan base-package="com.yy.spring,com.yy.p"></context:component-scan>    
<!--还可以设置扫描范围内哪些注解可不可以用-->    
<context:include-filter>
<context:exclude-filter>
    
<!--下面例子的作用是禁用@Service注解-->  
<!--use-default-filter的作用是fliter标签可否用,默认可用,感觉这个标签有些多余-->    
<context:component-scan base-package="com.yy.spring" use-default-filters="true">
    <context:exclude-filter type="annotation" expression="org.springframework.stereotype.Service"/>
</context:component-scan>

简单的注解实例

  • 配置扫描器

    <context:component-scan base-package="com.yy.pojo,com.yy.dao"/>
    
  • pojo类种加入注解

    import lombok.Data;
    import org.springframework.beans.factory.annotation.Value;
    import org.springframework.stereotype.Component;
    
    @Data
    @Component("car")//相当于<bean id="car" class="..."></bean>
    public class Car {
        @Value("mike")
        private String name;
        @Value("33.4")
        private Double price;
    }
    
  • 测试

    @Test
    public void getUser(){
        ApplicationContext context = applicationContextUtil.getApplicationContext();
        Car car = (Car) context.getBean("car");
        System.out.println(car.toString());
    }
    
  • 结果

    Car(name=mike, price=33.4)
    
  • 声明容器的注解

    • @Component 注解是用于把 SgtPeppers类实例化注入到 Spring容器当中,相当于配置文件当中的<bean id="" class=""/>

      另外与它类似的注解还有:

    • @Controller:标注控制层组件

    • @Service:标注业务层组件

    • @Repository:标注数据层组件

    • @Component:泛指组件,当组件不好归类的时候,我们可以使用这个注解进行标注

注入属性注解

  • Autowired:自动按照类型注入。只要容器中有唯一的一个bean对象类型和要注入的变量类型匹配,就可以注入成功。 如果IoC容器中没有任何bean的类型和要注入的变量类型匹配,则报错。如果IoC容器中有多个类型匹配时:将变量名跟已经匹配的数据类型的key值比较,如果有唯一匹配则注入成功,否则失败。
  • @Qualifier:在自动按照类型注入的基础之上,再按照Bean的id注入。它在给字段注入时不能独立使用,必须和@Autowire一起使用;但是给方法参数注入时,可以独立使用。
  • @Resource:直接按照Bean的id注入。它也只能注入其他bean类型。
  • @Value:注入基本数据类型和String类型数据的 ,不能注入bean类型的

Scope作用范围注解

@scope: 用于指定bean的作用域,一般就是singleton和prototype 。

生命周期注解

  • @PostConstruct: 用于指定初始化方法,相当于bean标签中的init-method属性。
  • @PreDestroy 作用: 用于指定销毁方法。相当于bean标签中的destroy-method属性。

Spring从以下方式实现自动化装配

  • 组件扫描(component scanning):Spring会自动发现应用上下文中所创建的bean;
  • 自动装配(autowiring):Spring自动满足bean之间的依赖;

用javaConfig的方式配置注解

在这里使用一个以 CD播放的例子

  1. 首先创建一个 CompactDisc接口

    package com.spring;
    
    public interface CompactDisc {
        void play();
    }
    
  2. 创建一个SgtPeppers类实现CompactDisc接口

    package com.spring;
    
    import org.springframework.stereotype.Component;
    
    @Component
    public class SgtPepper implements CompactDisc {
        private String title = "Sgt. Pepper's Lonely Hearts Club Band";
        private String artist = "The Beatles";
    
        public void play() {
            System.out.println("Playing " + title + " by " + artist);
        }
    }
    
  3. 使用配置类开启注解扫描

    package com.spring;
    
    import org.springframework.context.annotation.ComponentScan;
    import org.springframework.context.annotation.Configuration;
    
    @Configuration
    @ComponentScan
    public class CDPlayerConfig {
    }
    

    @Configuration用于定义配置类,被定义的类相当于.xml配置文件

    @ComponentScan会开启扫描与配置类相同的包中的注解 此时的包就是 package com.spring

  4. 测试组件是否能够扫描发现CompactDisc

    import com.spring.CompactDisc;
    import com.spring.CDPlayerConfig;
    import org.junit.Test;
    import org.junit.runner.RunWith;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.test.context.ContextConfiguration;
    import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
    
    import static org.junit.Assert.assertNotNull;
    
    @RunWith(SpringJUnit4ClassRunner.class)
    @ContextConfiguration(classes = CDPlayerConfig.class)
    public class CDPlayerTest {
        @Autowired
        private CompactDisc cd;
    
        @Test
        public void cdShouldNotBeNull(){
            assertNotNull(cd);
        }
    }
    

    @RunWith就是一个运行器

    @RunWith(SpringJUnit4ClassRunner.class)就是用Spring的测试环境来运行测试

    @ContextConfiguration通常与@RunWith一起使用来进行测试

    使用方式有以下几种

    单个文件
    @ContextConfiguration(Locations=”../applicationContext.xml”)
    @ContextConfiguration(classes = SimpleConfiguration.class)

    多个文件时,可用{}

    @ContextConfiguration(locations = {“classpath*: public class LogProxy implements InvocationHandler { //被代理对象 Object targetObject; public LogProxy(Object targetObject) { this.targetObject = targetObject; } public Object createLogProxy(){ Object objectProxy = Proxy.newProxyInstance( targetObject.getClass().getClassLoader(), targetObject.getClass().getInterfaces(), this); return objectProxy; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println("----------------------"); System.out.println(" 对"+method.getName()+"方法进行了权限检验"); System.out.println(" 参数个数:"+method.getParameterCount()); System.out.println(" 参数类型:"+method.getParameterTypes()); System.out.println(" 返回参数类型:"+method.getReturnType()); System.out.println(" ......"); System.out.print(" 方法执行打印:"); //执行真正的方法 Object ret = method.invoke(targetObject,args); System.out.println(" 方法执行完毕"); System.out.println("----------------------"); return ret; } }

  5. 创建一个动态代理的接口,同时我们要注意的是动态代理的实现方式也是通过接口实现的。

    package dynamicproxy;
    
    
    public interface ProgramingLanguage {
    
        Object JavaCoder(String name, Integer age);
        Object PHPCoder();
        void CppCoder();
    }
    
  6. 对接口进行实现

    package dynamicproxy;
    
    
    public class ProgramingLanguageImpl implements ProgramingLanguage {
    
        @Override
        public Object JavaCoder(String name, Integer age) {
            System.out.println(name +"是一个"+age+"岁的java工程师");
            return null;
        }
    
        @Override
        public Object PHPCoder() {
            System.out.println("PHP是最好的编程语言");
            return null;
        }
    
        @Override
        public void CppCoder() {
            System.out.println("C++是最好的编程语言");
        }
    }
    
  7. 创建一个测试类进行测试

    import dynamicproxy.LogProxy;
    import dynamicproxy.ProgramingLanguage;
    import dynamicproxy.ProgramingLanguageImpl;
    
    
    public class ProxyTest {
        public static void main(String[] args) {
            ProgramingLanguage proxy = (ProgramingLanguage) new LogProxy(new ProgramingLanguageImpl()).createLogProxy();
            proxy.CppCoder();
            proxy.JavaCoder("小明",23);
            proxy.PHPCoder();
        }
    }
    
  8. 测试结果

    ----------------------
       对CppCoder方法进行了权限检验
       参数个数:0
       参数类型:[Ljava.lang.Class;@306a30c7
       返回参数类型:void
       ......
       方法执行打印:C++是最好的编程语言
       方法执行完毕
    ----------------------
    ----------------------
       对JavaCoder方法进行了权限检验
       参数个数:2
       参数类型:[Ljava.lang.Class;@b81eda8
       返回参数类型:class java.lang.Object
       ......
       方法执行打印:小明是一个23岁的java工程师
       方法执行完毕
    ----------------------
    ----------------------
       对PHPCoder方法进行了权限检验
       参数个数:0
       参数类型:[Ljava.lang.Class;@506c589e
       返回参数类型:class java.lang.Object
       ......
       方法执行打印:PHP是最好的编程语言
       方法执行完毕
    
  9. ``

    切面编程-AOP

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

    ​ 比如说一个公司的工资计算,每个部门都有自己的基本工资和绩效工资标准,因为每个部门干的工作不同,但是公司给每个部门的员工付出的社保、公积金比例是相同的,所以在公司月底进行工资计算时,我们可以把每个部门的基本工资和绩效工资计算写成独立的类,然后社保、公积金的计算就可以使用面向切面的方式来进行计算,这样就会减低代码量、降低业务之间的耦合性。

    以下名词需要了解下:

    • 横切关注点:跨越应用程序多个模块的方法或功能。即是,与我们业务逻辑无关的,但是我们需要关注的部分,就是横切关注点。如日志 , 安全 , 缓存 , 事务等等 …
    • 切面(ASPECT):横切关注点 被模块化 的特殊对象。即,它是一个类。
    • 通知(Advice):切面必须要完成的工作。即,它是类中的一个方法。
    • 目标(Target):被通知对象。
    • 代理(Proxy):向目标对象应用通知之后创建的对象。
    • 切入点(PointCut):切面通知 执行的 “地点”的定义。
    • 连接点(JointPoint):与切入点匹配的执行点。

    通过Spring API实现的AOP

    • 首先创建一个前置日志的类,简单实现一个前置日志通知

      package com.aop;
      
      import org.springframework.aop.MethodBeforeAdvice;
      import org.springframework.stereotype.Component;
      
      import java.lang.reflect.Method;
      
      
      @Component
      public class BeforeLog implements MethodBeforeAdvice {
          public void before(Method method, Object[] objects, Object o) throws Throwable {
              System.out.println("前置通知:" + o.getClass().getName() + "对象的" + method.getName() + "方法被执行了");
          }
      }
      
    • 再创建一个后置通知

      package com.aop;
      
      import org.springframework.aop.AfterReturningAdvice;
      import org.springframework.stereotype.Component;
      
      import java.lang.reflect.Method;
      
      
      @Component
      public class AfterLog implements AfterReturningAdvice {
          @Override
          public void afterReturning(Object returnValue, Method method, Object[] objects, Object target) throws Throwable {
              System.out.println("后置通知:" + target.getClass().getName() + "对象的" + method.getName() + "方法被执行了");
          }
      }
      
    • 再创建接口和实现类用于测试准备

      package com.aop;
      
      
      public interface UserService {
          void add();
          void delete();
          void update();
          void query();
      }
      
      package com.aop;
      
      import org.springframework.stereotype.Component;
      
      
      @Component("userService")
      public class UserServiceImpl implements UserService {
          @Override
          public void add() {
              System.out.println("增加一个用户");
          }
      
          @Override
          public void delete() {
              System.out.println("删除一个用户");
          }
      
          @Override
          public void update() {
              System.out.println("修改一个用户");
          }
      
          @Override
          public void query() {
              System.out.println("查询一个用户");
          }
      }
      
    • 在Spring文件中进行注册

      <?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" 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/aop http://www.springframework.org/schema/aop/spring-aop.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd">
      
          <context:component-scan base-package="com.aop"/>
          <aop:config>
              <!--切入点 expression:表达式匹配要执行的方法-->
              <aop:pointcut id="pointcut" expression="execution(* com.aop.UserServiceImpl.*(..))"/>
              <!--执行环绕; advice-ref执行方法 . pointcut-ref切入点-->
              <aop:advisor advice-ref="beforeLog" pointcut-ref="pointcut"/>
              <aop:advisor advice-ref="afterLog" pointcut-ref="pointcut"/>
          </aop:config>
      
      </beans>
      

      上面涉及到了一个execution表达式:

      1、execution(): 表达式主体。

      2、第一个*号:方法返回类型, *号表示所有的类型。

      3、包名:表示需要拦截的包名。

      4、第二个*号:表示类名,*号表示所有的类。

      5、*(…):最后这个星号表示方法名,*号表示所有的方法,后面( )里面表示方法的参数,两个句点表示任何参数

    • 测试类进行测试

      import com.aop.UserService;
      import org.junit.Test;
      import org.springframework.context.ApplicationContext;
      import org.springframework.context.support.ClassPathXmlApplicationContext;
      
      
      public class AopTest {
          @Test
          public void test(){
              ApplicationContext context = new ClassPathXmlApplicationContext("applicationConfig.xml");
              UserService userService = (UserService) context.getBean("userService");
              userService.query();
          }
      }
      
    • 测试结果

      前置通知:com.aop.UserServiceImpl对象的query方法被执行了
      查询一个用户
      后置通知:com.aop.UserServiceImpl对象的query方法被执行了
      

    自定义类来实现AOP

    • 创建一个通知类

      package com.aop.div;
      
      import org.springframework.stereotype.Component;
      
      
      @Component("advice")
      public class Advice {
          public void before(){
              System.out.println("前置通知");
          }
      
          public void after(){
              System.out.println("后置通知");
          }
      }
      
    • 创建一个实体类

      package com.aop.div;
      
      import org.springframework.stereotype.Component;
      
      
      @Component("pointCut")
      public class PointCut {
          public void sayHello(){
              System.out.println("Hello");
          }
      }
      
    • 在XML中进行注册

      <aop:config>
          <aop:aspect ref="advice">
              <aop:pointcut id="pointcut-1" expression="execution(* com.aop.div.PointCut.*(..))"/>
              <aop:before pointcut-ref="pointcut-1" method="before"/>
              <aop:after pointcut-ref="pointcut-1" method="after"/>
          </aop:aspect>
      </aop:config>
      
    • 测试类进行测试

      @Test
      public void testAdvice(){
          ApplicationContext context = new 							      				                                ClassPathXmlApplicationContext("applicationConfig.xml");
          PointCut pointCut = (PointCut) context.getBean("pointCut");
          pointCut.sayHello();
      }
      
    • 测试结果

      前置通知
      查询一个用户
      后置通知
      

      相比使用spring Api的方式,此方式显得更见便捷,但是这种方式使用起来并不如上一种强大。

    注解实现AOP

    ​ 在这里我们不使用就不使用任何xml文件了直接完全用javaConfig+注解的方式来实现以开始提到的计算社保公积金的一个小程序

    • 首先创建javaConfig类

      package com.aop.annotation;
      
      import org.springframework.context.annotation.Bean;
      import org.springframework.context.annotation.ComponentScan;
      import org.springframework.context.annotation.Configuration;
      import org.springframework.context.annotation.EnableAspectJAutoProxy;
      
      
      @Configuration
      @ComponentScan(basePackages = "com.aop.annotation")
      @EnableAspectJAutoProxy
      public class JavaConfig {
      }
      

      @EnableAspectJAutoProxy注解相当于xml中的<aop:aspectj-autoproxy/>它的作用是声明自动为spring容器中那些配置@aspectJ切面的bean创建代理

    • 创建接口和实现类

      package com.aop.annotation;
      
      import org.springframework.stereotype.Component;
      
      
      @Component
      public interface Department {
          Double developerSalary(Double base,Double performance);
          Double salerSalary(Double base,Double performance);
      }
      
      
      package com.aop.annotation;
      
      import org.springframework.stereotype.Component;
      
      
      @Component
      public class DepartmentImpl implements Department{
      
          @Override
          public Double developerSalary(Double base, Double performance) {
              Double countSalary =  base + performance * 8;
              return countSalary;
          }
      
          @Override
          public Double salerSalary(Double base, Double performance) {
              Double countSalary = base + performance * 6;
              return countSalary;
          }
      }
      
    • 创建增强类

      package com.aop.annotation;
      
      import org.aspectj.lang.JoinPoint;
      import org.aspectj.lang.annotation.After;
      import org.aspectj.lang.annotation.Aspect;
      import org.aspectj.lang.reflect.MethodSignature;
      import org.springframework.aop.AfterReturningAdvice;
      import org.springframework.beans.factory.annotation.Autowired;
      import org.springframework.stereotype.Component;
      
      import java.lang.reflect.InvocationTargetException;
      import java.lang.reflect.Method;
      
      
      @Component
      @Aspect
      public class SocialSecurityPay{
          private Double countSalary;
          private Double socialSePay;
          private Double acFund;
          private Double finalSalary;
      
      
          @After("execution(* com.aop.annotation.DepartmentImpl.*(..))")
          public void after(JoinPoint joinPoint) throws InvocationTargetException, IllegalAccessException {
              MethodSignature signature = (MethodSignature) joinPoint.getSignature();
              Method method = signature.getMethod();
      
              countSalary = (Double) method.invoke(joinPoint.getTarget(),joinPoint.getArgs());
              socialSePay = 0.06 * countSalary;
              acFund = 0.08 * countSalary;
              finalSalary = countSalary - socialSePay - acFund;
              System.out.println("------------------------------------------");
              System.out.println(method.getName()+"本月工资为:" + countSalary);
              System.out.println(method.getName()+"公积金缴纳:" + acFund);
              System.out.println(method.getName()+"社保缴纳为:" + socialSePay);
              System.out.println(method.getName()+"事发薪资为:" + finalSalary);
          }
      }
      

      JoinPoint对象封装了Spring Aop中切面方法的信息,在切面方法中添加JoinPoint参数,就可以获取到封装了该方法信息的JoinPoint对象.

      常用API

      方法名 功能
      Signature getSignature(); 获取封装了署名信息的对象,在该对象中可以获取到目标方法名,所属类的Class等信息
      Object[] getArgs(); 获取传入目标方法的参数对象
      Object getTarget(); 获取被代理的对象
      Object getThis(); 获取代理对象
    • 测试类进行测试

      package com.aop.annotation;
      
      import org.junit.Test;
      import org.junit.runner.RunWith;
      import org.springframework.beans.factory.annotation.Autowired;
      import org.springframework.context.ApplicationContext;
      import org.springframework.context.annotation.AnnotationConfigApplicationContext;
      import org.springframework.context.annotation.ImportResource;
      import org.springframework.test.context.ContextConfiguration;
      import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
      
      import static org.junit.Assert.assertNotNull;
      
      
      public class AnnotationTest {
          @Test
          public void test(){
              AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(JavaConfig.class);
      
              Department department = (Department) context.getBean("departmentImpl");
              department.developerSalary(2000.0,20.2);
              department.salerSalary(2000.0,50.5);
          }
      }
      
    • 输出结果

      ------------------------------------------
      developerSalary本月工资为:2161.6
      developerSalary公积金缴纳:172.928
      developerSalary社保缴纳为:129.696
      developerSalary事发薪资为:1858.976
      ------------------------------------------
      salerSalary本月工资为:2303.0
      salerSalary公积金缴纳:184.24
      salerSalary社保缴纳为:138.18
      salerSalary事发薪资为:1980.5800000000002
      
 
打赏
 本文转载自:网络 
所有权利归属于原作者,如文章来源标示错误或侵犯了您的权利请联系微信13520258486
更多>最近资讯中心
更多>最新资讯中心
更多>相关资讯中心
0相关评论

推荐图文
推荐资讯中心
点击排行
最新信息
新手指南
采购商服务
供应商服务
交易安全
关注我们
手机网站:
新浪微博:
微信关注:

13520258486

周一至周五 9:00-18:00
(其他时间联系在线客服)

24小时在线客服