Spring5学习笔记

Spring5教程笔记

【狂神说Java】Spring5最新完整教程IDEA版通俗易懂

1. Spring背景

  • SSH:Struts2 + Spring + Hibernate
  • SSM:SpringMVC + Spring + MyBatis

一些链接:

  • 官网:https://spring.io/projects/spring-framework#overview
  • 文档:https://docs.spring.io/spring-framework/docs/current/reference/html/
  • 中文文档:https://www.docs4dev.com/docs/zh/spring-framework/5.1.3.RELEASE/reference
  • 官方下载: https://repo.spring.io/release/org/springframework/spring/

Spring优点:

  1. Spring是一个开源的免费的框架(容器)!
  2. Spring是一个轻量级、非入侵式的框架!
  3. 控制翻转(IOC),面向切面编程(AOP)!
  4. 支持事务的处理,对框架整合的支持!

Spring组成:

img

2. IoC概念

IoC:IoC是一种编程思想,指的是从原来手动创建对象到将对象托管到容器中由容器来创建对象。使用Spring后,对象的创建由Spring创建并托管,并不需要在程序中主动地new。

控制:传统的程序通过主动new等方式创建对象,IoC有一个外部容器来控制这些对象的创建。IoC容器控制了对象,主要控制了外部资源的获取(包括文件等)。

反转:程序不再查找了注入依赖对象,程序变成了被动接收对象,获取对象的方法反转了。

在Spring中实现控制反转的方式是依赖注入(Dependency Injection,DI)。

依赖注入:Spring中使用依赖注入在运行时动态地向某个对象提供所需要的资源。


创建第一个Spring项目

  1. 在maven中加入spring依赖
<dependencies>
    <!--加入spring驱动-->
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-webmvc</artifactId>
        <version>5.2.0.RELEASE</version>
    </dependency>
</dependencies>
  1. 创建一个实体类
public class Hello {
    private String str;
    public String getStr() {
        return str;
    }
    public void setStr(String str) {
        this.str = str;
    }
    @Override
    public String toString() {
        return "Hello{" +
                "str='" + str + '\'' +
                '}';
    }
}
  1. 创建spring映射文件beans.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">
<bean id="hello" class="com.pojo.Hello">
    <!--使用Spring来创建对象,在Spring这些都称为Bean
        类型 变量名 = new 类型();
        Hello hello = new Hello();
        id = 变量名
        class = new 的对象
        properties  相当于给对象中的属性设置一个值
    -->
    <property name="str" value="Spring"/>
</bean>
</beans>
  1. 编写测试类
public class MyTest {
    public static void main(String[] args) {
        // 1.获取spring的上下文对象
        ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
        // 2.从Spring容器中取出要使用的对象
        Hello hello = (Hello) context.getBean("hello");
        System.out.println(hello.toString());
    }
}
  1. 测试结果

    Hello(str-'Spring')

3. IoC创建对象的方式

通过无参构造创建对象
  1. 创建实体类
public class User {
    private String name;
    public User() {
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public void show() {
        System.out.println("name = " + name);
    }
}
  1. 创建映射文件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"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
          http://www.springframework.org/schema/beans/spring-beans.xsd">
    <bean id="user" class="com.pojo.User">
        <property name="name" value="马尔扎哈"/>
    </bean>
</beans>
  1. 编写测试类
public class MyTest {
    public static void main(String[] args) {
        ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
        // 执行getBean的时候,对象已经被创建了
        User user = (User) context.getBean("user");
        // 调用对象的方法
        user.show();
    }
}
  1. 测试结果

name = 马儿扎哈

使用有参构造创建对象
  1. 创建实体类
public class User {
    private String name;
    public User(String name) {
        this.name = name;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public void show() {
        System.out.println("name = "+name);
    }
}
  1. 创建映射文件beans.xml

    2.1 通过参数下标赋值

<?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">
    <bean id="user" class="com.pojo.User">
        <!-- index指的是构造方法,值为参数的下标,从0开始 -->
        <constructor-arg index="0" value="光辉女郎"/>
    </bean>
</beans>

​ 2.2 通过参数名字赋值

<?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">
    <bean id="user" class="com.pojo.User">
        <!-- name指的是参数名称 -->
        <constructor-arg name="name" value="光辉女郎"/>
    </bean>
</beans>

​ 2.3 通过参数类型赋值

<?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">
    <bean id="user" class="com.pojo.User">
        <!-- type指的是参数的类型,需要保持全局类型唯一 -->
        <constructor-arg type="java.lang.String" value="光辉女郎"/>
    </bean>
</beans>
  1. 编写测试类
public class MyTest {
    public static void main(String[] args) {
        ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
        User user = (User) context.getBean("user");
        user.show();
    }
}
  1. 测试结果

name = 光辉女郎

4. Spring配置文件

  1. 别名
<!--  别名:如果添加了别名,我们也可以使用别名获取到这个对象 -->
<alias name="User" alias="u1"></alias>
  1. Bean的配置
<!--
  id : bean的唯一标识符,相当于类名
  class : bean对象所对应的完全限定名:包名+类型
  name : name也是别名,而且name值可以取多个,可以使用多种分隔符(空格、逗号、分号等),均指向同一个bean
  -->
<bean id="UserT" class="com.pojo.UserT" name="u2 u21,u22;u23">
    <property name="name" value="123"/>
</bean>
  1. 导入其他配置文件
<!-- import能够将多个配置的xml文件整合到同一个中,并且能够合适地去重 -->
<import resource="beans.xml"/>
<import resource="beans1.xml"/>
<import resource="beans2.xml"/>
  1. Bean的作用域

    作用域
    singleton在spring IoC容器仅存在一个Bean实例,Bean以单例方式存在,bean作用域范围的默认值。在创建容器的时候就已经创建好了bean对象,每次获取的是相同的对象。
    prototype每次从容器中调用Bean时,都返回一个新的实例,即每次调用getBean()时,相当于执行newXxxBean()。在创建容器的时候并不创建bean对象,当获取bean的时候才会创建对象,每次获取的是不同的对象。
    request每次HTTP请求都会创建一个新的Bean,该作用域仅适用于web的Spring WebApplicationContext环境
    session同一个HTTP Session共享一个Bean,不同Session使用不同的Bean。该作用域仅适用于web的Spring WebApplicationContext环境
    application限定一个Bean的作用域为ServletContext的生命周期。该作用域仅适用于web的Spring WebApplicationContext环境
    <bean id="User" class="com.pojo.User" c:age="18" c:name="马超" scope="prototype"/>
    

5. 依赖注入

依赖:指的是Bean对象的创建依赖于容器,Bean的依赖资源。

注入:指的是Bean对象所依赖的资源 , 由容器来设置和装配。

构造器注入

同 -3. IoC创建对象的方式

set注入(重点)
  1. 创建实体类,需要生成对应的set方法

Address.java

public class Address {
    private String address;

    public String getAddress() {
        return address;
    }

    public void setAddress(String address) {
        this.address = address;
    }
}

Student.java

public class Student {
    private String name;
    private Address address;
    private String[] book;
    private List<String> hobby;
    private Map<String,String> card;
    private Set<String> games;
    private String wife;
    private Properties info;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Address getAddress() {
        return address;
    }

    public void setAddress(Address address) {
        this.address = address;
    }

    public String[] getBook() {
        return book;
    }

    public void setBook(String[] book) {
        this.book = book;
    }

    public List<String> getHobby() {
        return hobby;
    }

    public void setHobby(List<String> hobby) {
        this.hobby = hobby;
    }

    public Map<String, String> getCard() {
        return card;
    }

    public void setCard(Map<String, String> card) {
        this.card = card;
    }

    public Set<String> getGames() {
        return games;
    }

    public void setGames(Set<String> games) {
        this.games = games;
    }

    public String getWife() {
        return wife;
    }

    public void setWife(String wife) {
        this.wife = wife;
    }

    public Properties getInfo() {
        return info;
    }

    public void setInfo(Properties info) {
        this.info = info;
    }

    @Override
    public String toString() {
        return "Student{" +
                "name='" + name + '\'' +
                ", address=" + address +
                ", book=" + Arrays.toString(book) +
                ", hobby=" + hobby +
                ", card=" + card +
                ", games=" + games +
                ", wife='" + wife + '\'' +
                ", info=" + info +
                '}';
    }
}
  1. 创建配置文件
<?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">
    
    <bean id="address" class="com.pojo.Address">
        <!-- 普通值注入,使用name,value指定 -->
        <property name="address" value="召唤师峡谷"/>
    </bean>
    
    <bean id="student" class="com.pojo.Student">
        <!-- 普通值注入,使用name,value指定 -->
        <property name="name" value="复仇之矛"/>
        <!-- Bean注入,使用ref指定引用的bean -->
        <property name="address" ref="address"/>
        <!-- 数组注入,使用array标签,value指定值 -->
        <property name="books">
            <array>
                <value>三国演义</value>
                <value>水浒传</value>
                <value>红楼梦</value>
                <value>西游记</value>
            </array>
        </property>
        <!-- Set注入,使用set标签,value指定值 -->
        <property name="games">
            <set>
                <value>LOL</value>
                <value>M3GUO</value>
                <value>The glory of king</value>
            </set>
        </property>
        <!-- Map注入,使用map标签,entry代表一个实体,由key-value组成 -->
        <property name="card">
            <map>
                <entry key="key1" value="value1"/>
                <entry key="key2" value="value2"/>
            </map>
        </property>
        <!-- List注入,使用list标签,value指定值 -->
        <property name="hobbys">
            <list>
                <value>打篮球</value>
                <value>健身</value>
                <value>听歌</value>
            </list>
        </property>
        <!-- Null注入,使用null标签注入 -->
        <property name="wife">
            <null/>
        </property>
        <!-- property注入,使用property标签,prop标签中值需要放在中间 -->
        <property name="info">
            <props>
                <prop key="eamil">test@mail.com</prop>
                <prop key="user">root</prop>
                <prop key="password">123456</prop>
            </props>
        </property>
    </bean>
</beans>
  1. 创建测试类

    public class MyTest {
        public static void main(String[] args) {
            ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
            Student student =  context.getBean("student",Student.class);
            System.out.println(student.toString());
        }
    }
    
  2. 测试结果

Student{name='复仇之矛', address=Address{address='召唤师峡谷'}, books=[三国演义, 水浒传, 红楼梦, 西游记 ], hobbys=[打篮球, 健身, 听歌], card={key1=value1, key2=value2}, games=[LOL, M3GUO, The glory of king], wife='null', info={email=test@mail.com, user=root, password=123456} }

拓展方式(p注入和c注入)

p注入:

使用p注入之前需要先在配置文件中引入p标签

xmlns:p="http://www.springframework.org/schema/p"

对User对象注入属性name

<?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"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans.xsd">
           <!--p命名(properties)注入,可以直接注入属性的值:-->
    <bean id="user" class="com.pojo.User" p:name="瑟提" />

c注入:

使用c注入之前需要现在配置文件中引入c标签

xmlns:c="http://www.springframework.org/schema/c"

对User对象注入属性name

<?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"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans.xsd">
       
    <!--c命名(construct-args)注入,通过构造器注入,需要写有参构造器 -->
    <bean id="user2" class="com.pojo.User" c:name="瑟提" />
</beans>

使用c命名和p命名需要有无参构造

c命名注入:就是构造器注入(construct),使用要写有参构造器

6. 自动装配

Spring中自动装配是Spring满足bean依赖一种方式,Spring会在上下文中自动地寻找,并自动给bean装配属性。

Spring中有三种装配方式:

1. xml中显式配置
2. 在java中显式配置
3. **隐式的自动转配**

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

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

byName和byType自动装配
  1. 创建实体类
public class Dog {
    public void shout(){
        System.out.println("汪~");
    }
}
public class Cat {
    public void shout(){
        System.out.println("瞄~");
    }
}
public class People {
    private String name;
    private Cat cat;
    private Dog dog;

    public String getName() {return name; }

    public void setName(String name) {this.name = name;}

    public Cat getCat() { return cat; }

    public void setCat(Cat cat) {this.cat = cat;}

    public Dog getDog() {return dog;}

    public void setDog(Dog dog) {this.dog = dog;}

    @Override
    public String toString() {
        return "People{" +
                "name='" + name + '\'' +
                ", cat=" + cat +
                ", dog=" + dog +
                '}';
    }
}
  1. 创建配置文件
<bean id="Cat" class="com.pojo.Cat"/>
<bean id="Dog" class="com.pojo.Dog"/>

<!-- 1. 使用byName的方式,会在上下文中查找beanid与setName中name相同的对象 -->
<bean id="People1" class="com.pojo.People" autowire="byName">
    <property name="name" value="阿卡丽"/>
</bean>

<!-- 2. 使用byType的方式,会寻找和对象class一样的bean,需要保证class相同的bean全局唯一 -->
<bean id="people" class="com.pojo.People" autowire="byType">
   <property name="name" value="阿卡丽"/>
</bean>
  • 使用byName自动装配时,需要保证有与beanid相同的set方法,否则会报错。

  • 使用byType自动装配时,需要保证所有的bean的class唯一,并且这个bean需要和自动注入的属性的类型一致。

  1. 创建测试方法
@Test
public void test1(){
    ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
    People people = context.getBean("people", People.class);
    people.getCat().shout();
    people.getDog().shout();
}
  1. 测试结果

喵~

汪~

使用注解自动装配
  1. 导入注解的约束
xmlns:context="http://www.springframework.org/schema/context"

http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd
  1. 开启注解
<context:annotation-config/>
  1. 配置文件总览
<?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/>
    <bean id="cat" class="com.pojo.Cat"/>
    <bean id="dog" class="com.pojo.Dog"/>
    <bean id="people" class="com.pojo.People"/>
</beans>
@Autowired

@Autowired可以使用byType自动装配,在使用时需要导入spring-aop的包。

在实体类中,加入@Autowired注解可以使用require,如果require = false表示该对象可以为null,否则不允许为空。

public class People {
    @Autowired
    private Dog dog;
    @Autowired(required = false) //表示当前对象可以为null
    private Cat cat;
    private String name;
}
@Qualifier
  • 当@Autowired自动装配的环境比较复杂的时候,需要通过@Qualifier进行bean的指定。
  • @Qualifier不能单独使用
public class People {
    @Autowired
    private Dog dog;
    @Autowired
    @Qualifier(value = "cat777") //指定使用beanid为cat777注入
    private Cat cat;
    private String name;
}
@Resource

@Resource是java原生的注解,和@Autowried使用方法类似,@Resource使用的是byName实现的,可以使用name属性指定bean此时用byName寻找bean,如果找不到就用byType实现。

public class People {
    @Resource
    private Dog dog;
    @Resource(name = "cat777") //指定bean的id为cat777
    private Cat cat;
    private String name;
}
@Nullable

@Nullable可以标价一个字段,表示该字段值可以为null

小结

@Autowired和@Resource的区别

1. 都是用来自动装配的,都可以使用在字段上(写在属性或者set方法上面),但是执行顺序不同@Autowired通过byType实现,@Resource默认通过byName实现

2. @Autowired方法默认按照类型装配(byType),默认情况下要求依赖对象必须存在,如果要允许null 值,可以设置它的required属性为false,如:@Autowired(required=false) ,如果想使用名称装配可以使用@Qualifier(value = "xxx")

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

7. 使用注解开发

使用注解开发时,创建对象和依赖注入不再依靠applicationContext.xml文件,而是直接在实体类上加上注解完成对象创建和依赖注入。

@Component和@Value
  1. 导入AOP的依赖jar包
	<!-- 可以通过导入spring-webmvc包来一次性导入多个包 -->
<dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-webmvc</artifactId>
            <version>5.2.8.RELEASE</version>
 </dependency>
  1. 在配置文件中导入context约束并开启注解支持
<?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:component-scan base-package="com.zp"/>
    	<!--开启注解支持-->
    	<context:annotation-config/>
</beans>
  1. 创建对应的实体类
@Component //相当于相当于配置文件中 <bean id="user" class="当前注解的类"/>
public class User {
    @Value("阿卡丽") //相当于<property name="name" value="阿卡丽"/> 也可以写在set方法上面
    public String name ;
    }
}
  1. 创建测试类
@Test
public void test(){
    ApplicationContext applicationContext = new ClassPathXmlApplicationContext("beans.xml");
    User user = (User) applicationContext.getBean("user");
    System.out.println(user.name);
}
  1. 测试结果

阿卡丽

衍生注解

@Component有三个衍生注解,功能一样,我们在web开发中,会按照mvc三层架构分层。

  • @Controller:web层
  • @Service:service层
  • @Repository:dao层

这四个注解的功能一样,都是代表将某个类注册到spring中

自动装配
  • @Autowired:自动装配,先通过类型判断,再是名字,如果无法通过@Autowired唯一装配上属性,则需要通过@Qualifier(value=”xxx”)
  • @Nullable:字段标记了这个注解,说明这个字段可以为null
  • @Resource:也是自动装配,java自带的,先通过名字进行判断,再通过类型进行判断

作用域

通过@scope注解中value来定义作用域,与配置文件中bean作用域相同

@Component //等价于  <bean id="user" class="当前类"/>
@Scope("singleton") // 默认使用单例模式(singleton)
public class User {
    @Value("阿卡丽") //相当于 <property name="name" value="阿卡丽"/>
    public String name;
}
小结
  • XML可以适用任何场景 ,结构清晰,维护方便
  • 注解不是自己提供的类使用不了,开发简单方便

xml与注解最佳的实践:

  • xml管理Bean
  • 注解完成属性注入
  • 我们在使用的过程中,只需要注意一个问题:必须让注解生效,就需要开启注解的扫描支持
    <!--指定要扫描的包-->
    <context:component-scan base-package="com.magic"/>
    <!--开启注解扫描-->
    <context:annotation-config/>

<!--  作用:
			1. 进行注解驱动注册,从而使注解生效
			2. 用于激活那些已经在spring容器中注册过的bean上的注解,也就是显式的向spring注册
			3. 如果不扫描包,就需要手动配置bean
			4. 如果不加注解驱动,则注入的值为null
 -->

8. 使用java方式配置Spring

使用java方式配置spring可以完全抛弃applicationContext.xml配置文件

  1. 创建实体类
@Component
public class User {
    public String name;
    public String getName() {
        return name;
    }
    @Value("布隆")//注入值
    public void setName(String name) {
        this.name = name;
    }
}
  1. 创建java配置类
//@Configuration代表当前类是配置类相当于applicationContext.xml,其本身也是一个@Component
@Configuration 
@ComponentScan("com.pojo") //相当于<context:compent-scan base-package="package">指定初始化容器时要扫描的包
@Import(MyConfig.class) //相当于配置文件中的import标签,导入其他的配置类
public class MyConfig {
    //@Bean注册一个bean,就相当于xml中的bean标签
    //方法的名字相当于bean标签中的id属性
    //方法的返回值相当于bean标签中的class属性
    @Bean
    public User getUser(){
        return new User(); //就是返回要注入到bean的对象
    }
}
  1. 创建测试类

此时需要使用AnnotationConfig获取容器

public class myTest {
    @Test
    public void Test(){
        //如果使用了配置类的方式,只能使用AnnotationConfig上下文去获取容器,通过配置类的class对象来加载!
        ApplicationContext context = new AnnotationConfigApplicationContext(MyConfig.class);
        // 获取通过配置类中的方法getUser方法来获得的User对象
        User user = context.getBean("getUser",User.class);
        System.out.println(user.getName());
    }
}
  1. 测试结果

布隆

如果出现了下面的异常,查看你的配置类引入格式是否正确,不能加双引号,然后就是你的bean的id是否引入错误

org.springframework.beans.factory.NoSuchBeanDefinitionException

9. 代理模式

静态代理

代理模式:以租房为例,我们现实中要租房,可以找中介代理,中介再找房东,中介(代理)会添加一些额外的服务,例如中介费、拟定合同等。

角色分析:

  • 抽象角色 : 一般使用接口或者抽象类来实现
  • 真实角色 : 被代理的角色(房东)
  • 代理角色 : 代理真实角色 ; 代理真实角色后 , 一般会做一些附属的操作 (房产中介).
  • 客户 : 访问代理对象的人(租客) .

实现步骤:

1. 租房接口
public interface Rent {
    public void rent();
}
  1. 真实对象(房东)
public class Host implements Rent{
    public void rent() {
        System.out.println("房东要出租房子!");
    }
}
  1. 代理角色(房产中介)
public class Proxy implements Rent{
    private Host host;
    public Proxy(){}
    public Proxy(Host host){this.host = host;}
    // 因为要帮房东租房子,所以代理也要实现租房子
    public void rent() {
        host.rent();
    }
    // 中介的额外操作
    //看房
    public void seeHouse(){
        System.out.println("带房客看房");
    }
    //收中介费
    public void fare(){
        System.out.println("收中介费");
    }
}
  1. 客户(租客)
public class Client {
    public static void main(String[] args) {
        // 创建房东对象
        Host host = new Host();
        // 中介代理房东
        Proxy proxy = new Proxy(host);
        // 通过中介租房
        proxy.rent();
        // 中介额外操作,交中介费
        proxy.fare();
    }
}

静态代理的好处:

  • 可以使得我们的真实角色更加纯粹 . 不再去关注一些公共的事情 .
  • 公共也就是交给我们的代理角色!实现了业务的分工!
  • 公共业务发生扩展的时候,方便集中管理!

静态代理的缺点:

  • 一个真实角色就会产生一个代理角色;代码量会翻倍-开发效率会变低

动态代理
  • 动态代理和静态代理的角色一样 .
  • 动态代理的代理类是动态生成的 . 静态代理的代理类是我们提前写好的
  • 动态代理分为两类 : 一是基于接口动态代理 , 二是基于类的动态代理
    • 基于接口的动态代理----JDK动态代理
    • 基于类的动态代理–cglib
    • java字节码实现:javassistt

JDK的动态代理需要了解两个类:Proxy(代理)InvocationHandler(调用处理程序)

具体实现:

还是上面的房东类和租房接口

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

接下来我们就是创建动态代理类(中介)

public class ProxyInvocationHandler implements InvocationHandler {
    //被代理的接口
    private Object target;
    public void setRent(Object target) {
        this.target = target;
    }
    //生成动态代理类
    public Object getProxy() {
        return Proxy.newProxyInstance(this.getClass().getClassLoader(),
                target.getClass().getInterfaces(), this);
    }
    //处理代理实例,并返回结果
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        //动态代理的本质,就是使用反射机制实现!
        Object result = method.invoke(target, args);
        return result;
    }
}

测试:

public class Client {
    public static void main(String[] args) {
        //创建房东对象
        Host host = new Host();
        //创建动态代理对象,也就是中介
        ProxyInvocationHandler pih = new ProxyInvocationHandler();
        //放入需要代理的对象,也就是房东
        pih.setRent(host);
        Rent proxy = (Rent) pih.getProxy();
        proxy.rent();
    }
}

如果想要在多个旧接口上增加新的功能,静态代理需要新建多个新的接口,动态代理只需要改代理的对象

还是之前上面的静态代理的例子,在增删改查中加一个日志功能

public interface UserService {
    public void add();
    public void delete();
    public void update();
    public void select();
}
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 select() {
        System.out.println("查询了用户");
    }
}

创建动态代理对象,并加入新增的日志功能

public class ProxyInvocationHandler implements InvocationHandler {
    //被代理的接口
    private Object target;
    public void setRent(Object target) {
        this.target = target;
    }
    //生成动态代理类
    public Object getProxy() {
        return Proxy.newProxyInstance(this.getClass().getClassLoader(),
                target.getClass().getInterfaces(), this);
    }
    //处理代理实例,并返回结果
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        //动态代理的本质,就是使用反射机制实现!
        this.log(method.getName());//使用反射method获取到执行方法的name
        Object result = method.invoke(target, args);
        return result;
    }
    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.setRent(userService);
        //动态生成代理类
        UserService proxy = (UserService) pih.getProxy();
        proxy.update();
    }
}

测试结果:可以看到日志已经成功加入进来了

执行了update方法!修改了用户

动态代理的好处:

  • 可以使真实角色的操作更加纯粹!不用去关注一些公共的业务
  • 公共的业务由代理来完成 . 实现了业务的分工
  • 公共业务发生扩展时变得更加集中和方便 .
  • 一个动态代理类代理的是一个接口,一般就是对应的一类业务
  • 一个动态代理类可以代理多个类,只要实现了同一个接口即可

10. AOP

什么是AOP

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

AOP常见术语
  • Aspect(切面):我们需要关注的部分,通常是一个类,里面可以定义切入点和通知。
  • JointPoint(连接点):与切入点匹配的执行点,一般是方法的调用。
  • Advice(通知):切面必须要完成的工作,在特定的切入点上执行的增强处理,有before,after,afterReturning,afterThrowing,around
  • Pointcut(切入点):切面通知执行的 “地点”,在程序中主要体现为书写切入点表达式。
  • Proxy(代理):向目标对象应用通知之后创建的对象,代理就是目标对象的加强。Spring中的AOP代理可以使JDK动态代理,也可以是cglib代理,前者基于接口,后者基于子类。
  • 目标(Target):被通知对象
使用Spring实现AOP

引入相关依赖

<dependencies>
    <dependency>
        <groupId>org.aspectj</groupId>
        <artifactId>aspectjweaver</artifactId>
        <version>1.9.6</version>
    </dependency>
</dependencies>
一、使用spring的API接口
  1. 编写业务接口和实现类
public interface UserService {
	public void add();
	public void delete();
    public void update();
    public void search();
}
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 search() {
        System.out.println("查询用户");
    }
}
  1. 编写增强类 ,

首先创建一个Log类,实现MethodBeforeAdvice接口,代表要在操作之前执行

public class Log implements MethodBeforeAdvice {
    //method:要执行的目标对象的方法
    //args:被调用的方法的参数
    //target:目标对象
    @Override
    public void before(Method method, Object[] objects, Object target) throws Throwable {
        System.out.println(target.getClass().getName()+"的"+method.getName()+"被执行了");
    }
}

再创建一个AfterLog类,实现AfterReturningAdvice,代表在操作之后执行

public class AfterLog implements AfterReturningAdvice {
    //returnValue 返回值
    //method被调用的方法
    //args 被调用的方法的对象的参数
    //target 被调用的目标对象
    @Override
    public void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable {
        System.out.println("执行了" + target.getClass().getName()
        +"的"+method.getName()+"方法,"
        +"返回值:"+returnValue);
    }
}
  1. 配置applicationContext.xml(导入aop约束)
<?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-->
    <bean id="userService" class="com.service.UserServiceImpl"/>
    <bean id="Log" class="com.log.Log"/>
    <bean id="afterLog" class="com.log.AfterLog"/>

    <!--aop的配置-->
    <aop:config>
        <!--切入点  expression:表达式匹配要执行的方法 -->
        <!-- excution事要执行的位置中第一个参数为返回值,第二个参数为方法*代表该类下的所有方法名 (..)代表所有参数 -->
        <aop:pointcut id="pointcut" expression="execution(* com.service.UserServiceImpl.*(..))"/>
        <!--执行环绕:advice-ref执行方法 . pointcut-ref切入点 -->
        <aop:advisor advice-ref="Log" pointcut-ref="pointcut"/>
        <aop:advisor advice-ref="afterLog" pointcut-ref="pointcut"/>
    </aop:config>
</beans>
  1. 编写测试类
public class MyTest {
    @Test
    public void test1(){
        ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
        //动态代理的是接口!
        UserService userService = (UserService) context.getBean("userService");
        userService.add();
    }
}
  1. 测试结果

com.service.UserServiceImpl的add被执行了 添加了用户 执行了add方法,返回结果为:null

二、使用自定义类来实现AOP
  1. 创建自定义的切面
public class DiyPointCut {
    public void before(){
        System.out.println("=========方法执行之前=======");
    }
    public void after(){
        System.out.println("=========方法执行之后=======");
    }
}
  1. 在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"
       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-->
    <bean id="userService" class="com.UserServiceImpl"/>
    <bean id="log" class="com.log.Log"/>
    <bean id="afterLog" class="com.log.AfterLog"/>
    <!--方式二:自定义类-->
    <bean id="diy" class="com.DiyPointCut"/>
    <aop:config>
        <!--自定义切面-->
        <aop:aspect ref="diy">
            <!--切入点-->
            <aop:pointcut id="pointcut" expression="execution(* com.service.UserServiceImpl.*(..))"/>
            <!--通知-->
            <aop:before method="before" pointcut-ref="pointcut"/>
            <aop:after method="after" pointcut-ref="pointcut"/>
        </aop:aspect>
    </aop:config>
</beans>
  1. 创建测试类
public class MyTest {
    @Test
    public void test1(){
        ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
        //动态代理的是接口!
        UserService userService = (UserService) context.getBean("userService");
        userService.add();
    }
}
  1. 测试结果

=========方法执行之前=======

添加了用户

=========方法执行之后=======

三、使用注解实现
  1. 编写一个注解实现的增强类
@Aspect //标注这个类是一个切面
public class AnnotationPointcut {
    @Before("execution(* com.service.UserServiceImpl.*(..))")
    public void before(){
        System.out.println("---------方法执行前---------");
    }

    @After("execution(* com.service.UserServiceImpl.*(..))")
    public void after(){
        System.out.println("---------方法执行后---------");
    }

    @Around("execution(* com.ervice.UserServiceImpl.*(..))")
    public void around(ProceedingJoinPoint jp) throws Throwable {
        System.out.println("环绕前");
        System.out.println("签名:"+jp.getSignature());
        Object proceed = jp.proceed();        //执行目标方法proceed
        System.out.println("环绕后");
        System.out.println(proceed);
    }
}
  1. 在Spring配置文件中,注册bean,并增加支持注解的配置
<?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-->
    <bean id="userService" class="com.service.UserServiceImpl"/>
    <bean id="annotationPointCut" class="com.AnnotationPointCut"/>
    <!--开启注解支持-->
    <aop:aspectj-autoproxy/>
</beans>
通过aop命名空间的<aop:aspectj-autoproxy />声明自动为spring容器中那些配置@aspectJ切面的bean创建代理,植入切面。当然,spring 在内部依旧采用AnnotationAwareAspectJAutoProxyCreator进行自动代理的创建工作,但具体实现的细节已经被<aop:aspectj-autoproxy />隐藏起来了

<aop:aspectj-autoproxy/>有一个proxy-target-class属性,默认为false,表示使用jdk动态代理织入增强,当配为<aop:aspectj-autoproxy  poxy-target-class="true"/>时,表示使用CGLib动态代理技术织入增强。不过即使proxy-target-class设置为false,如果目标类没有声明接口,则spring将自动使用CGLib动态代理。
  1. 创建测试类
public class MyTest {
    @Test
    public void test1(){
        ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
        //动态代理的是接口
        UserService userService = (UserService) context.getBean("userService");
        userService.add();
    }
}
  1. 测试结果

环绕前 =====方法执行前====== 添加了用户 环绕后 =====方法执行后======

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值