J2EE学习--Spring

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

2019-02-23  本文已影响57人  _NineSun旭_

什么是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示例代码

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();
属性注入
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>
...
集合类型的注入
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(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("之后");
    }
}
<?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 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>
@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("之后");
    }
}
上一篇下一篇

猜你喜欢

热点阅读