美文网首页J2EE学习--Spring
Spring框架的IOC、DI、AOP应用详解

Spring框架的IOC、DI、AOP应用详解

作者: _NineSun旭_ | 来源:发表于2019-02-23 13:37 被阅读57次

    什么是Spring

    是一个为简化企业级应用开发的开源框架,作为IOC(DI)、AOP容器。


    特性

    轻量级:占用空间小;非侵入性(是否被框架绑架):通过配置的方法,使代码中不引用框架的类,删除jar包后不会报错;
    控制反转、依赖注入:将设计好的对象交给容器,该对象的属性也由容器进行注入;
    面向切面编程:
    容器:管理Java对象(所有的类)的生命周期;
    框架:使用简单的组件配置组合成一个复杂的应用;
    一站式:在IOC和AOP基础上可以整合各种企业的开源框架和第三方类库,Spring本身也提供了视图层的SpringMVC和持久层的Spring JDBC;


    相关术语

    Ioc(控制反转 Inversion of Control)
    Di(依赖注入 Dependency Injection)
    Aop(面向切面编程 Aspect Oriented Programming)
    以上都是一种方法论,不是Spring专有
    Dao(数据访问对象 Data Access Object)
    Orm(对象关系映射 (Object Relational Mapping)
    Service(服务、业务逻辑 等于 Business)
    MVC(模型-视图-控制器 Model View Controller)


    IOC—控制反转、DI—依赖注入

    把创建实例的控制权交给框架,由框架创建实例(控制反转)并把实例分发给调用的程序(依赖注入)。由Spring容器来控制对象的生命周期,默认是单例模式的,目的是层和层之间的解耦。

    如何实现的解耦

    依赖:如果在一个类中想要调用另一个对象的非静态方法,必须先创建这个对象,不创建时编译报错,此时就称这个对象是这个类的依赖。
    某一个类中的依赖在正常编译的情况下,这个类才有可能正常编译。由此,便产生了耦合。
    IOC为此而生,不管所依赖的对象是否存在,都能够编译成功:
    通过实现接口的方式(面向接口编程),类中不再声明实现类,而是声明该类的接口。

    IOC示例代码

    • 新建Java项目
    • 导入相关jar包:Spring框架jar包及commons-logging.jar(https://mvnrepository.com/下载)
    • 创建接口
    package t;
    
    public interface HelloWorld
    {
        public void sayHello();
    }
    
    
    • 创建实现类
    package t;
    
    public class HelloWorldImpl implements HelloWorld
    {
        @Override
        public void sayHello()
        {
            System.out.println("helloworld");
        }
    }
    
    • 编写配置文件
    <?xml version="1.0" encoding="utf-8"?>
    <!-- 上面为xml文件的头,指明xml的版本号和当前文件的编码,必须在第一行 -->
    <!-- 声明spring配置文件的跟标签beans -->
    <!-- 通过xmlns这个命名空间属性声明当前xml文档需要用到的标签所在的包 -->
    <!-- xmlns:xsi声明xsi属性前缀所在的命名空间 -->
    <!-- xsi:schemaLocation声明当前xml文件里用标签的合法性校验 -->
    <!-- xsi:schemaLocation属性的值包含“命名空间和校验文件” -->
    <!-- 扩展名是xsd的文件是xml的语法校验文件 -->
    <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-4.0.xsd">
    
    <!-- 用bean标签声明一个HelloWorldImpl类的实例, 并且把实例的名字定义为helloWorld-->
    <!-- 其中需要注意,class属性里一定是实现类;id属性在整个配置文件里必须唯一-->
        <bean id="helloWorld" class="t.HelloWorldImpl"/>
    
    </beans>
    
    • 编写测试类
    package t;
    
    import org.springframework.context.AbstractApplicationContext;
    import org.springframework.context.support.ClassPathXmlApplicationContext;
    
    public class test
    {
        public static void main(String[] args)
        {
            //通过src目录(即classpath)下的t目录下的IOC.xml配置文件,创建spring容器
            AbstractApplicationContext applicationContext = new ClassPathXmlApplicationContext("t/IOC.xml");
            //去容器里取到id=helloWorldBean的实例,并且把这个实例声明为接口的类型
            HelloWorldImpl bean = (HelloWorldImpl)applicationContext.getBean("helloWorld");
            bean.sayHello();
            applicationContext .close();//关闭容器
        }
    }
    

    DI(依赖注入)

    把有依赖关系的类的实例注入为指定类的属性值。
    需要注入的类和被注入的类都需要交个Spring容器来管理。
    在Controller层中声明service层的接口,注入service的实现类,在Service层中注入Dao层的接口,注入dao的实现类。


    构造器注入
    • 有一个属性
    • 属性要在构造方法中被赋值
    • 创建需要有属性被注入的类
    public class TextPrinter {
        private Formater formater;//该属性被注入
        private int size;
        
        public TextPrinter(Formater formater,int size){
            this.formater=formater;
            this.size=size;
        }
    
        public void print(String info){
            System.out.println("Set size="+this.size);
            this.formater.execute(info);
        }
    }
    
    • 注入属性的类
    public class Formater {
        public void execute(String info) {
            System.out.println("Formater = "+info);
        }
    }
    
    • 配置文件里,配置注入标签
    <!-- 声明id为f的bean实例 -->
    <bean id="f" class="s1.Formater"/>
    <!-- 声明id为tp的bean实例 -->
    <bean id="tp" class="s1.TextPrinter">
    <!-- 通过constructor-arg调用tp实例的构造方法进行注入 -->
    <!--
    index : 标明构造方法里参数的下标,即为第几个参数注入
    ref子标签代表注入一个实例(引用);bean : 指明一个已经在配置文件中配置好的bean的id
    type : 标明这个参数的类型
    value : 直接给这个参数注入一个具体的值
    -->
        <constructor-arg index="0">
            <ref bean="f"/>
        </constructor-arg>
        <constructor-arg index="1" type="int" value="5"/>
    </bean>
    
    • 写测试类测试上面的注入结果
    AbstractApplicationContext context = new ClassPathXmlApplicationContext("f.xml");
    TextPrinter bean = (TextPrinter) context.getBean("tp");
    bean.print("Spring 4");
    context.close();
    
    属性注入
    • 有至少一个属性
    • 这个属性至少有公有set方法
    • 创建需要有属性被注入的类
    public class TextPrinter2 {
        private Formater formater;//被注入的属性
        private int size;//被注入的属性
    
        public Formater getFormater() {
            return formater;
        }
    
        //一定要有这个方法
        public void setFormater(Formater formater) {
            this.formater = formater;
        }
    
        public int getSize() {
            return size;
        }
        //一定要有这个方法
        public void setSize(int size) {
            this.size = size;
        }
    
        public void print(String info){
            System.out.println("Set size="+this.size);
            this.formater.execute(info);
        }
    }
    
    • 注入属性的类 参照上面
    • 配置文件里配置属性注入标签
    ...
    <bean id="tp2" class="s1.TextPrinter2">
    <!--
    通过property标签进行属性注入
    name : 标明类里被注入的属性的变量名
    ref子标签同上
    value子标签 type属性同上
    -->
        <property name="formater">
            <ref bean="f"/>
        </property>
        <property name="size">
            <value type="int">5</value>
        </property>
    </bean>
    ...
    
    • 写测试类测试上面的属性注入结果 参考上面
    集合类型的注入
    • 数组
    • list
    • set
    • map
    • Properties
    • 创建需要有集合属性被注入的类
    import java.util.List;
    import java.util.Map;
    import java.util.Properties;
    import java.util.Set;
    
    public class Department {
        private String name;
        private String [] empName;//数组
        private List<Employee> empList;//list集合
        private Set<Employee> empsets;//set集合
        private Map<String,Employee> empMaps;//map集合
        private Properties pp;//Properties的使用
    
        public Set<Employee> getEmpsets() {
            return empsets;
        }
        public void setEmpsets(Set<Employee> empsets) {
            this.empsets = empsets;
        }
        public String[] getEmpName() {
            return empName;
        }
        public void setEmpName(String[] empName) {
            this.empName = empName;
        }
        public String getName() {
            return name;
        }
        public void setName(String name) {
            this.name = name;
        }
        public List<Employee> getEmpList() {
            return empList;
        }
        public void setEmpList(List<Employee> empList) {
            this.empList = empList;
        }
        public Map<String, Employee> getEmpMaps() {
            return empMaps;
        }
        public void setEmpMaps(Map<String, Employee> empMaps) {
            this.empMaps = empMaps;
        }
        public Properties getPp() {
            return pp;
        }
        public void setPp(Properties pp) {
            this.pp = pp;
        }
    }
    
    • 创建集合里元素的实例类
    public class Employee {
        private String id;
        private String name;
        public String getId() {
            return id;
        }
        public void setId(String id) {
            this.id = id;
        }
        public String getName() {
            return name;
        }
        public void setName(String name) {
            this.name = name;
        }
    }
    
    • 在配置文件中配置集合属性的注入
    ...
        <!-- 声明被注入的实例emp1 -->
        <bean id="emp1" class="s1.Employee">
            <property name="name" value="北京"/>
            <property name="id" value="1"/>
        </bean>
        <!-- 声明被注入的实例emp2 -->
        <bean id="emp2" class="s1.Employee">
            <property name="name" value="天津"/>
            <property name="id" value="2"/>
        </bean>
    
    
        <bean id="department" class="s1.Department">
            <!-- 用list子标签给数组注入值 -->
            <property name="empName">
                <list>
                    <value>小明</value>
                    <value>小明小明</value>
                    <value>小明小明小明小明</value>
                </list>
            </property>
            
            <!-- 用list子标签给list集合属性注入,注入的是Employee类的实例 -->
            <property name="empList">
                <list>
                    <ref bean="emp2"/>
                    <ref bean="emp1"/>
                    <ref bean="emp1"/>
                    <ref bean="emp1"/>
                    <ref bean="emp1"/>
                    <ref bean="emp1"/>
                    <ref bean="emp1"/>
                </list>
            </property>
            
            <!-- 用set子标签给set集合属性注入,注入的是Employee类的实例 -->
            <property name="empsets">
                <set>
                    <ref bean="emp1"/>
                    <ref bean="emp2"/>
                    <ref bean="emp2"/>
                    <ref bean="emp2"/>
                    <ref bean="emp2"/>
                </set>
            </property>
            
            <!-- 用map和entry子标签给map集合属性注入,注入的是Employee类的实例,同时给每个实例一个key -->
            <property name="empMaps">
                <map>
                    <entry key="11" value-ref="emp1"/>
                    <entry key="22" value-ref="emp2"/>
                    <entry key="22" value-ref="emp1"/>
                </map>
            </property>
            
            <!-- 用给props和prop子标签给property属性集合注入,注入的key和value都是String -->
            <property name="pp">
                <props>
                    <prop key="pp1">abcd</prop>
                    <prop key="pp2">hello</prop>
                </props>
            </property>
            </bean>
    ...
    
    • 写测试类测试上面的集合属性注入结果
    ...
            Department department=(Department) context.getBean("department");
            for(String emName:department.getEmpName()){
                System.out.println(emName);
            }
     
            System.out.println("**********取出list集合数据*****");
            for(Employee e : department.getEmpList()){
                System.out.println("name="+e.getName()+" "+e.getId());
            }
    
            System.out.println("**********取出set集合数据*****");
            for(Employee e : department.getEmpsets()){
                System.out.println("name="+e.getName());
            }
        
            System.out.println("*******取出map集合数据方法一****");  
            Map<String,Employee> empmaps=department.getEmpMaps();
            Iterator it=empmaps.keySet().iterator();
            while(it.hasNext()){
                String key=(String) it.next();
                Employee emp=empmaps.get(key);
                System.out.println("key="+key+" "+emp.getName());
            }
            System.out.println("*******取出map集合数据方法二****");
            Set<Map.Entry<String, employee>> entries =         department.getEmpMaps().entrySet();
            for (Map.Entry<String, employee> e:entries
                 ) {
                System.out.println(e.getKey()+"   "+e.getValue().getId()+"                     "+e.getValue().getName());
            }
            System.out.println("*******取出map集合数据方法三****");
            Set<String> keySet = department.getEmpMaps().keySet();
            for (String s:keySet
            ) {
                System.out.print(s+"    ");
                employee employee = d.getEmpmap().get(s);
                System.out.println(employee.getId()+"    "+employee.getName());
            }
    
            System.out.println("**********取出Properties数据*****");
            Properties pp = d.getPp();
            System.out.println(pp.getProperty("name"));
            System.out.println(pp.getProperty("url"));
            Enumeration<?> enumeration = pp.propertyNames();
            while(enumeration.hasMoreElements()){
                System.out.println(enumeration.nextElement());
    }
    
    ...
    
    注解的方式

    语法 : @注解名("参数")

    用注解不能有构造器,否则会报错

    ioc,替代bean标签的注解

    @Component - 其他类
    @Controller - servlet
    @Service - service
    @Repository - dao
    注解在class声明上方,同样的功能,分四个单词,不同层的类用不同单词类注解

    @Service("MyBean") 相当于 <bean id="MyBean" class="被注解的类"/>

    @Service("MyBean")
    public class DIHW {
        private HelloWorld hImpl;
        public void print(){
            hImpl.sayHello(" d i ");
        }
    }
    
    di,替代property和ref标签的注解
    • @Resource - javax
    • @AutoWired @Qualifier - spring
      注解在需要被注入的属性上面,可以声明被注入实例的id,或者默认

    [@Resource(name="MyBean")] 相当于
    [@AutoWired @Qualifier("hello313Impl")] 相当于
    <property name="注解下面的属性名">
    <ref bean="MyBean"/>
    </property>

        @Autowired
        @Qualifier("MyBean")
        或
        @Resource(name="MyBean")
        //@Resource(type=MyBean.class)
        private HelloWorld hImpl;
    
    di注解默认的注入规则

    AutoWired注解的默认规则——按照接口类型自动装配
    只写这个注解,产生的id按照驼峰命名法命名,按照被注入的属性的接口类型来找实例,如果找不到,或找到多个都报错;
    如果想按照名字来找实例,或者有多个类实现这个接口,必须引入Qualifier注解

    Resource注解的默认规则——按照属性名自动装配
    1.如果同时指定了name和type,则从Spring上下文中找到唯一匹配的bean进行装配,找不到则抛出异常。
    2.如果指定了name,则从上下文中查找名称(id)匹配的bean进行装配,找不到则抛出异常。
    3.如果指定了type,则从上下文中找到类似匹配的唯一bean进行装配,找不到或是找到多个,都会抛出异常。
    4.如果既没有指定name,又没有指定type,则自动按照byName方式进行装配;如果没有匹配在按照类型装配。

    自动装配和扫描

    spring配置文件的头部的通用定义,如果要使用注入,要引入新的命名空间和新的校验文件

    <?xml version="1.0" encoding="UTF-8"?>
    ...
    <beans xmlns="http://www.springframework.org/schema/beans" 
    xmlns:context="http://www.springframework.org/schema/context"
    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-4.0.xsd
    http://www.springframework.org/schema/context 
    http://www.springframework.org/schema/context/spring-context-4.0.xsd
    ">  
    ...
    </beans>
    

    启动注解,并且自动扫描那个类被注解,需要在spring的配置文件中添加标签

    ...
    <context:annotation-config/>
    <!--声明4个类帮助系统识别注解-->
    <context:component-scan base-package="要扫描的包"/>
    <!--使用 <context:component-scan/> 后,就可以将 <context:annotation-config/> 移除了--!>
    ...
    

    AOP—面向切面编程(对事务进行控制)

    多个类中有相同的代码块,为减少代码量,将此代码块取出,作为一个切面,切面的前后位置叫做切点,将此切面交给Spring框架管理,自动执行此段代码,减少了方法中的代码量。

    相关概念

    1.切面(Aspect):需要插入的代码块
    2.连接点(JoinPoint):
    3.切点(Pointcut):指定某个方法作为切面切的地方
    4.通知(Advice):指定切面在切点的插入位置

    前置通知(before advice)
    后置通知(after return advice)
    环绕通知(around advice)
    异常通知(after throwing advice)

    5.目标对象(target object)
    6.AOP代理(aop proxy)

    XML和注解方式AOP

    xml方式
    • 新建两个类
    //被切入的类
    public class Car {
        public void go(){
            System.out.println("go go go!");
        }
    }
    //存放切入代码的类
    public class CarLogger {
        public void beforeRun(){
            System.out.println("之前");
        }
        public void afterRun(){
            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:context="http://www.springframework.org/schema/context"
        xmlns:aop="http://www.springframework.org/schema/aop"
        xsi:schemaLocation="http://www.springframework.org/schema/beans 
        http://www.springframework.org/schema/beans/spring-beans-4.0.xsd 
        http://www.springframework.org/schema/aop
        http://www.springframework.org/schema/aop/spring-aop-4.0.xsd">
    <bean id="car" class="aop.Car" />
    <bean id="logger" class="aop.CarLogger" />
    <aop:config>
        <aop:aspect ref="logger">
        <aop:pointcut expression="execution(* aop.Car.go(..))" id="go"/>
         <!--切点表达式:找到切点,“..”为参数任意。“*”为返回值任意-->
                <aop:before pointcut-ref="go" method="beforeRun" />
                <aop:after pointcut-ref="go" method="afterRun" />
            </aop:aspect>
    </aop:config>
    </beans>
    
    • 创建测试类
    import org.springframework.context.ApplicationContext;
    import org.springframework.context.support.ClassPathXmlApplicationContext;
    public class TestAop {
        public static void main( String[] args ){
            ApplicationContext context = new ClassPathXmlApplicationContext("aop-config.xml");
            Car car=(Car) context.getBean("car");
            car.go();
        }
    }
    
    注解方式
    • 同上新建两个类
    • 修改xml配置文件如下
    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="http://www.springframework.org/schema/beans"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xmlns:context="http://www.springframework.org/schema/context"
        xmlns:aop="http://www.springframework.org/schema/aop"
        xsi:schemaLocation="http://www.springframework.org/schema/beans 
        http://www.springframework.org/schema/beans/spring-beans-4.0.xsd 
        http://www.springframework.org/schema/aop
        http://www.springframework.org/schema/aop/spring-aop-4.0.xsd">
    <bean id="car" class="aop.Car" />
    <bean id="logger" class="aop.CarLogger" />
    <!--自动完成创建代理织入切面的工作-->
    <aop:aspectj-autoproxy/>
    </beans>
    
    • 修改CarLogger类如下
    @Aspect
    public class CarLogger {
        @Before("execution(* aop.Car.go(..))")
        public void beforeRun(){
            System.out.println("之前");
        }
        @After("execution(* aop.Car.go(..))")
        public void afterRun(){
            System.out.println("之后");
        }
    }
    

    @Aspect
    public class CarLogger {
        @Pointcut("execution(* aop.Car.go(..))")
        public void anyMethod(){}
        
        @Before("anyMethod()")
        public void beforeRun(){
            System.out.println("之前");
        }
        @After("anyMethod()")
        public void afterRun(){
            System.out.println("之后");
        }
    }
    

    相关文章

      网友评论

        本文标题:Spring框架的IOC、DI、AOP应用详解

        本文链接:https://www.haomeiwen.com/subject/tytpyqtx.html