紫薇星上的SSM——浅谈Spring的使用

学了一遍SSM后发觉没记住多少,所以再快速复习一下,简单整理一下Spring的用法和一些知识点。


0.简介

我们说的Spring是Spring Farmework的称呼,Spring解决了开发者在J2EE开发中遇到的许多常见的问题,提供了功能强大IOC、AOP及Web MVC等功能。Spring可以单独应用于构筑应用程序,也可以和Struts、Webwork、Tapestry等众多Web框架组合使用,并且可以与 Swing等桌面应用程序AP组合。因此, Spring不仅仅能应用于JEE应用程序之中,也可以应用于桌面应用程序以及小应用程序之中。

1.入门 

安装

和MyBatis相同,都要先准备环境,我使用的是IDEA2020.1.1版本,Maven项目,直接在pom文件中写入:

        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-webmvc</artifactId>
            <version>5.2.6.RELEASE</version>
        </dependency>

这是一个基本的jar包,导入后会有这些jar包:

配置

接下来开始配置xml文件,新建一个Module在其resources下建立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
        https://www.springframework.org/schema/beans/spring-beans.xsd">

    <bean id="" class="">
        ...
    </bean>
</beans>

此时的文档中就包含了所需要的内容,我们的每个实体类都可以映射为一个<bean>标签,id为其名称,class为其路径

使用

配置好后就可以进行实体类的编写了:

package com.test.pojo;

public class Hello {
    private String str;

    public Hello() {
    }

    public Hello(String str) {
        this.str = str;
    }

    public String getStr() {
        return str;
    }

    public void setStr(String str) {
        this.str = str;
    }

    @Override
    public String toString() {
        return "Hello{" +
                "str='" + str + '\'' +
                '}';
    }
}

这是有了一个实体类Hello,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
        https://www.springframework.org/schema/beans/spring-beans.xsd">

    <bean id="hello" class="com.test.pojo.Hello">
        <property name="str" value="spring"/>
    </bean>
</beans>

使用一个测试类测试一下是否可行:

package com.test.pojo;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class HelloTest {
    public static void main(String[] args) {
        ApplicationContext context = new ClassPathXmlApplicationContext("bean.xml");
        Hello hello = (Hello) context.getBean("hello");
        System.out.println(hello);
    }
}

这就相当于给Hello类中的str属性赋值为‘spring’,结果如下:

2.IOC

上面所说的是Spring的IOC功能,即控制反转,简单来说就是,之前我们的程序是由程序员决定哪一部分实现某个功能,比如这个部分实现使用Mysql连接数据库,在正常使用的时候并没有什么不妥,但是如果现在用户需要更改需求如使用Druid来连接数据库,那么我们的程序员就不得不开始修改底层源码,手动更改来达到客户的需求。

这时就会有人想到,可以使用工厂类来解决问题,我们只需要写一个工厂类就可以随时的修改需求,但是仍然有些麻烦,这时Spring的IOC功能就发挥了作用,使用配置文件读出需要修改的类,然后使用反射创建类的对象,这样就连工厂类都不用修改就可以实现需求的更改。而整个程序也从客户改需求程序员忙上忙下变成了程序会自己根据客户的需求更改而更改,这就是控制反转。

至于IOC的作用在入门的内容中我们已经见过了,接下来我们看一些其他的:

DI注入:beans.xml的强大

我们在实体类中的属性可不都是简单的String类型,还有很多比如list、map、set、null、对象、properties等等,这种的实体类Spring如何实现IOC呢?我们新建两个实体类:

package com.test.pojo;

import java.util.*;

public class User {
    private String name;
    private Address address;
    private String[] books;
    private List<String> hobbies;
    private Map<String, String> card;
    private Set<String> games;
    private String wife;
    private Properties info;

    public User() {
    }

    public User(String name, Address address, String[] books, List<String> hobbies, Map<String, String> card, Set<String> games, String wife, Properties info) {
        this.name = name;
        this.address = address;
        this.books = books;
        this.hobbies = hobbies;
        this.card = card;
        this.games = games;
        this.wife = wife;
        this.info = 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[] getBooks() {
        return books;
    }

    public void setBooks(String[] books) {
        this.books = books;
    }

    public List<String> getHobbies() {
        return hobbies;
    }

    public void setHobbies(List<String> hobbies) {
        this.hobbies = hobbies;
    }

    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 "User{" +
                "name='" + name + '\'' +
                ", address=" + address +
                ", books=" + Arrays.toString(books) +
                ", hobbies=" + hobbies +
                ", card=" + card +
                ", games=" + games +
                ", wife='" + wife + '\'' +
                ", info=" + info +
                '}';
    }
}
package com.test.pojo;

public class Address {
    private String name;

    public Address() {
    }

    public Address(String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }

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

    @Override
    public String toString() {
        return "Address{" +
                "name='" + name + '\'' +
                '}';
    }
}

分别是一个User类和一个Address类,其中Address是为用户作为组合的类型来实现的,我们看一下这时候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
        https://www.springframework.org/schema/beans/spring-beans.xsd">

    <bean id="address" class="com.test.pojo.Address">
        <property name="name" value="xi'an"/>
    </bean>

    <bean id="student" class="com.test.pojo.User">
        <!--普通属性,value注入-->
        <property name="name" value="zijun"/>
        <!--Bean注入,ref-->
        <property name="address" ref="address"/>
        <!--数组注入-->
        <property name="books">
            <array>
                <value>book1</value>
                <value>book2</value>
                <value>book3</value>
            </array>
        </property>
        <!--list-->
        <property name="hobbies">
            <list>
                <value>hoobies1</value>
                <value>hoobies2</value>
                <value>hoobies3</value>
            </list>
        </property>
        <!--map-->
        <property name="card">
            <map>
                <entry key="IDcard" value="123456"/>
                <entry key="YHcard" value="222222"/>
            </map>
        </property>
        <!--set-->
        <property name="games">
            <set>
                <value>game1</value>
                <value>game2</value>
                <value>game3</value>
            </set>
        </property>
        <!--null-->
        <property name="wife">
            <null/>
        </property>
        <!--properties-->
        <property name="info">
            <props>
                <prop key="学号">123</prop>
                <prop key="性别">man</prop>
            </props>
        </property>
    </bean>
</beans>

依然使用一个测试类看一下:

import com.test.pojo.User;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class MyTest {
    public static void main(String[] args) {
        ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
        User student = (User) context.getBean("student");
        System.out.println(student);
    }
}

结果如下,为了方便观察做了一下缩进处理:

User{
name='zijun', 
address=Address{name='xi'an'},
books=[book1, book2, book3], 
hobbies=[hoobies1, hoobies2, hoobies3], 
card={IDcard=123456, YHcard=222222}, 
games=[game1, game2, game3], 
wife='null', 
info={学号=123, 性别=man}
}

命名空间:C和P

除了这种操作之外,还有C命名空间和P命名空间:

<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:c="http://www.springframework.org/schema/c"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd">
</beans>
<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
        https://www.springframework.org/schema/beans/spring-beans.xsd">
</beans>

这两种命名空间的作用是简化IOC的操作:

P:properties,即根据属性进行注入

<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
        https://www.springframework.org/schema/beans/spring-beans.xsd">

    <bean name="john-classic" class="com.example.Person">
        <property name="name" value="John Doe"/>
        <property name="spouse" ref="jane"/>
    </bean>

    <bean name="john-modern"
        class="com.example.Person"
        p:name="John Doe"
        p:spouse-ref="jane"/>

    <bean name="jane" class="com.example.Person">
        <property name="name" value="Jane Doe"/>
    </bean>
</beans>

C:constructor-arg,即根据构造函数位置进行注入

<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:c="http://www.springframework.org/schema/c"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd">

    <bean id="beanTwo" class="x.y.ThingTwo"/>
    <bean id="beanThree" class="x.y.ThingThree"/>

    <!-- traditional declaration with optional argument names -->
    <bean id="beanOne" class="x.y.ThingOne">
        <constructor-arg name="thingTwo" ref="beanTwo"/>
        <constructor-arg name="thingThree" ref="beanThree"/>
        <constructor-arg name="email" value="something@somewhere.com"/>
    </bean>

    <!-- c-namespace declaration with argument names -->
    <bean id="beanOne" class="x.y.ThingOne" c:thingTwo-ref="beanTwo"
        c:thingThree-ref="beanThree" c:email="something@somewhere.com"/>

</beans>

自动装配

当我们有多个属性为实体组合的时候,需要用到大量的ref来进行注入,但是Spring中有Autowired来进行自动装配,我们先建立三个实体类:

package com.test.pojo;

public class Cat {
    public void shout(){
        System.out.println("mua~");
    }
}
package com.test.pojo;

public class Dog {
    public void shout(){
        System.out.println("wawu~");
    }
}
package com.test.pojo;

public class People {
    private Cat cat;
    private Dog dog;
    private String name;

    public People() {
    }

    public People(Cat cat, Dog dog, String name) {
        this.cat = cat;
        this.dog = dog;
        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;
    }

    public String getName() {
        return name;
    }

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

    @Override
    public String toString() {
        return "People{" +
                "cat=" + cat +
                ", dog=" + dog +
                ", name='" + 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"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd">
    <bean id="cat" class="com.test.pojo.Cat"/>

    <bean id="dog" class="com.test.pojo.Dog"/>

    <bean id="people" class="com.test.pojo.People">
        <property name="cat" ref="cat"/>
        <property name="dog" ref="dog"/>
        <property name="name" value="zijun"/>
    </bean>
</beans>

接下来是自动装配:

<?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
        https://www.springframework.org/schema/beans/spring-beans.xsd">
    <bean id="cat" class="com.test.pojo.Cat"/>

    <bean id="dog" class="com.test.pojo.Dog"/>

    <!--使用自动装配:autowired
        byName: 会根据bean的id来进行自动装配
        byType: 会根据bean的类型来进行自动装配-->
    <bean id="people_autowired" class="com.test.pojo.People" autowire="byName">
        <property name="name" value="zijun"/>
    </bean>
</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"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context
        https://www.springframework.org/schema/context/spring-context.xsd">

    <!--开启注解支持-->
    <context:annotation-config/>

    <bean id="cat" class="com.test.pojo.Cat"/>

    <bean id="dog" class="com.test.pojo.Dog"/>

    <bean id="people" class="com.test.pojo.People"/>

</beans>

然后在我们的实体类中进行注解就可以了:

注解可以写在属性上,也可以写在set方法上,如果严格按照Bean命名甚至不需要set方法。

除了有@Autowired之外,还有@Resource,它们都是用来实现自动装配的,都可以放在属性字段上。

@Autowired通过byType的方式实现,而且这个对象必须要存在,一般都使用@Autowired进行自动装配;

@Resource默认通过byName的方式实现,如果出现id重复或找不到的情况就会通过byType实现,如果依然找不到就会报错;

3.注解开发

在Spring4之后,如果使用注解进行开发就要导入aop的jar包,在我们开始的时候使用speing-webmvc的jar包时就已经自动导入了aop的包,如果发现注解不起作用就请检查一下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:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context
        https://www.springframework.org/schema/context/spring-context.xsd">

    <!--开启注解支持-->
    <context:annotation-config/>
    <!--指定要扫描的包,这个包下的注解就会生效-->
    <context:component-scan base-package="com.test.pojo"/>
</beans>

然后建立实体类:

package com.test.pojo;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

@Component //相当于<bean id="user" class="com.test.pojo.User"/>
public class User {
    @Value("zijun") //相当于<property name="name" value="zijun"/>
    public String name;
}

来测试一下,当然测试依然要用到配置文件:

import com.test.pojo.User;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class MyTest {
    public static void main(String[] args) {
        ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
        User user = (User) context.getBean("user");
        System.out.println(user.name);
    }
}

结果是没有问题的,这里所展示的@Component是“组件”的意思,它还有一些扩展的注解:

  • 对于DAO层来说:@Repository
  • 对于Service层来说:@Service
  • 对于Servlet层来说:@Controller

这三种注解和@Component的作用是一样的,都表示组件的注解,只不过在名称上更加清晰的表示了不同注解的作用,也表现了Spring对日后这些部分发展的野心。

一般来说,XML是比较万能的,适用于任何场景,注解不是自己的类就使用不了,维护起来更加复杂;最好的方式就是XML用来实现Bean,而注解只完成属性的注入,但需要开启注解支持。

Java开发

和MyBatis相同,Spring也有完全使用Java来进行配置的方式,虽然使用配置文件进行配置是主流方式,但依旧要了解一下,我们首先建立一个实体类:

package com.test.pojo;

import org.springframework.beans.factory.annotation.Value;

public class User {
    private String name;

    public User() {
    }

    public User(String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }

    @Value("zijun")
    public void setName(String name) {
        this.name = name;
    }

    @Override
    public String toString() {
        return "User{" +
                "name='" + name + '\'' +
                '}';
    }
}

然后再创建一个config类,config的作用就是取代xml:

package com.test.config;

import com.test.pojo.User;
import org.springframework.context.annotation.Bean;

public class MyConfig {
    @Bean
    public User user() {
        return new User();
    }
}

我们只需要在MyConfig类中实现@Bean注解即可实现,接下来编写一个测试类:

import com.test.config.MyConfig;
import com.test.pojo.User;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

public class MyTest {
    public static void main(String[] args) {
        ApplicationContext context = new AnnotationConfigApplicationContext(MyConfig.class);
        User user = (User) context.getBean("user");
        System.out.println(user.getName());
    }
}

4.AOP

代理模式

代理模式是AOP的底层逻辑,代理又分为静态代理和动态代理:

静态代理

在生活中,我们会遇到租房的问题,以这个问题我们来实现一下代理模式,首先我们有一个租房事件,称为接口:

package com.test.demo1;

public interface Rent {
    public void rent();
}

接下来有一个房东出现了,他会出租房子:

package com.test.demo1;

public class Host implements Rent{
    @Override
    public void rent() {
        System.out.println("我是房东,我来把房子租出去!");
    }
}

我们现在要租房子就要直面房东:

package com.test.demo1;

public class Client {
    public static void main(String[] args) {
        Host host = new Host();
        host.rent();
    }
}

这时我们如果需要进行一些额外的操作,如:看房,签合同,讨价还价等操作,这些操作并不是Host类所具有的,我们就要手动添加修改Host类来实现需求,放在现实生活中就是一个只想出租房子的房东,被迫学习了合同,又要带领其他人看房子,时间成本大大上升,所以我们需要一个房屋中介来帮助房东,这个中介就是我们说的代理:

package com.test.demo1;

public class Proxy implements Rent{
    private Host host;

    public Proxy() {
    }

    public Proxy(Host host) {
        this.host = host;
    }

    public Host getHost() {
        return host;
    }

    public void setHost(Host host) {
        this.host = host;
    }

    @Override
    public String toString() {
        return "Proxy{" +
                "host=" + host +
                '}';
    }

    @Override
    public void rent() {
        host.rent();
    }

    public void seeHouse(){
        System.out.println("看房!");
    }

    public void feeHouse(){
        System.out.println("收中介费!");
    }
}

这个中介代理了什么呢?Proxy类找到Host类,实现Host的rent()方法,同时还可以继续实现seeHouse(),feeHouse()等方法,而Host类只需要专心实现它的rent()方法即可;这时Client类是这样的:

package com.test.demo1;

public class Client {
    public static void main(String[] args) {
        Host host = new Host();
        Proxy proxy = new Proxy(host);
        proxy.seeHouse();
        proxy.rent();
        proxy.feeHouse();
    }
}

这就是一个简单的静态代理模式,当客户端对服务端有更多需求时,不需要改变服务端代码,只需要在代理类中添加方法即可,降低了代码的耦合性。

静态代理深入理解

首先我们创建一个接口:

package com.test.demo2;

public interface UserService {
    public void add();
    public void delete();
    public void update();
    public void query();
}

然后写一个接口的实现类:

package com.test.demo2;

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("查找了一个用户!");
    }
}

用Client类测试一下:

package com.test.demo2;

public class Client {
    public static void main(String[] args) {
        UserServiceImpl userService = new UserServiceImpl();
        userService.add();
    }
}

现在一切正常,这时如果需要实现一个新的需求:在每个方法的实现之前都先输出一条日志,这时我们只能一条一条的加:

package com.test.demo2;

public class UserServiceImpl implements UserService{
    @Override
    public void add() {
        System.out.println("这是add方法!");
        System.out.println("增加了一个用户!");
    }

    @Override
    public void delete() {
        System.out.println("这是delete方法!");
        System.out.println("删除了一个用户!");
    }

    @Override
    public void update() {
        System.out.println("这是update方法!");
        System.out.println("修改了一个用户!");
    }

    @Override
    public void query() {
        System.out.println("这是query方法!");
        System.out.println("查找了一个用户!");
    }
}

但是当方法和类达到一定数量的时候,一条一条的加太浪费时间了,所以我们有了代理类:

package com.test.demo2;

public class Proxy implements UserService{
    private UserServiceImpl userService;

    public Proxy() {
    }

    public Proxy(UserServiceImpl userService) {
        this.userService = userService;
    }

    public UserServiceImpl getUserService() {
        return userService;
    }

    public void setUserService(UserServiceImpl userService) {
        this.userService = userService;
    }

    @Override
    public String toString() {
        return "Proxy{" +
                "userService=" + userService +
                '}';
    }

    @Override
    public void add() {
        log("add");
        userService.add();
    }

    @Override
    public void delete() {
        log("delete");
        userService.delete();
    }

    @Override
    public void update() {
        log("update");
        userService.update();
    }

    @Override
    public void query() {
        log("query");
        userService.query();
    }

    //日志方法
    public void log(String msg){
        System.out.println("这是" + msg + "方法!");
    }
}

在代理类中处理实现原有的方法之外,还添加了一个log()方法,在代理的方法中使用log()方法就可以实现刚才的需求,这时Client类是这样的:

package com.test.demo2;

public class Client {
    public static void main(String[] args) {
        UserServiceImpl userService = new UserServiceImpl();
        Proxy proxy = new Proxy(userService);
        proxy.add();
    }
}

动态代理

静态代理虽然好用,但是一个客户端就会产生一个类,而当客户端数量增加的时候,代码量会翻倍,开发效率会变低,这时候就有了我们的动态代理模式。

静态代理是直接写死的代码,而动态代理是动态生成的,动态代理分为两大类:基于接口的动态代理、基于类的动态代理:

  • 基于接口——JDK动态代理;
  • 基于类——CGLIB;
  • 现在还有基于字节码——Javassist

我们依然使用刚才的租房事件来讲解,首先我们写好Rent接口和Host类:

package com.test.demo3;

public interface Rent {
    public void rent();
}
package com.test.demo3;

public class Host implements Rent {
    @Override
    public void rent() {
        System.out.println("我是房东,我来把房子租出去!");
    }
}

然后来编写动态代理实现类:

package com.test.demo3;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

//这个类会自动生成代理类
public class ProxyInvocationHandler implements InvocationHandler {

    //被代理的接口
    private Rent rent;

    public void setRent(Rent rent){
        this.rent = rent;
    }

    //生成得到代理类
    public Object getProxy(){
        return Proxy.newProxyInstance(this.getClass().getClassLoader(),rent.getClass().getInterfaces(),this);
    }

    /**
     * 处理代理实例,并返回结果
     * @param proxy 代理类
     * @param method 代理类要实现代理对象的方法
     * @param args 传过来的参数
     * @return 返回这个方法的结果
     * @throws Throwable
     */
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

        //动态代理的本质就是使用反射
        Object invoke = method.invoke(rent, args);

        return invoke;
    }
}

这个类中通过invoke()方法来实现代理类代理对象代理方法实现的结果,getProxy()方法通过newProxyInstance方法得到代理类,被代理的接口就是我们传入的接口;在Client类中实现:

package com.test.demo3;

public class Client {
    public static void main(String[] args) {
        Host host = new Host();

        ProxyInvocationHandler proxyInvocationHandler = new ProxyInvocationHandler();
        //通过调用程序处理角色来处理我们要调用的接口对象
        proxyInvocationHandler.setRent(host);

        Rent proxy = (Rent) proxyInvocationHandler.getProxy();

        proxy.rent();
    }
}

这里的proxy就是动态生成的代理类,而整个需求中的方法都不用再次手动实现,只需要在ProxyInvocationHandler类中的invoke()方法里添加即可,灵活性大幅上升,并且这段代码是可重用的。

AOP实现

对于AOP的实现,我借用一张Kuang神的图来说明:

首先我们需要在pom中添加依赖:

    <dependencies>
        <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjweaver</artifactId>
            <version>1.9.5</version>
        </dependency>
    </dependencies>

aspectjweaver用于支持切入点表达式,有两种方式实现:

使用Spring的接口实现切入

首先我们写一个接口:

package com.test.service;

public interface UserService {
    public void add();
    public void delete();
    public void update();
    public void query();
}

然后是它的实现类:

package com.test.service;

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("查找一个用户");
    }
}

如果我们想要在不改变源码的情况下在这些方法之前或者之后打印日志,就需要进行AOP切入,接下来开始编写切入类:

package com.test.log;

import org.springframework.aop.MethodBeforeAdvice;

import java.lang.reflect.Method;

public class BeforeLog implements MethodBeforeAdvice {
    @Override
    public void before(Method method, Object[] objects, Object o) throws Throwable {
        System.out.println(o.getClass().getName() + "的" + method.getName() + "方法被执行!");
    }
}
package com.test.log;

import org.springframework.aop.AfterReturningAdvice;

import java.lang.reflect.Method;

public class AfterLog implements AfterReturningAdvice {
    @Override
    public void afterReturning(Object o, Method method, Object[] objects, Object o1) throws Throwable {
        System.out.println("执行了" + method.getName() + "方法!");
        System.out.println("返回结果为:" + o);
    }
}

这两个类分别是在切入点之前和之后要添加的事件,实现是都使用了AOP的接口,上面这些我们都注册在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"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/aop
        https://www.springframework.org/schema/aop/spring-aop.xsd">
    
    <!--注册bean-->
    <bean id="userService" class="com.test.service.UserServiceImpl"/>
    <bean id="beforeLog" class="com.test.log.BeforeLog"/>
    <bean id="afterLog" class="com.test.log.AfterLog"/>

    <!--方式一:API接口-->
    <!--配置aop:导入约束-->
    <aop:config>
        <!--切入点:expression表达式: execution(任意的 要切入的类.类的方法(以及方法的参数)) -->
        <aop:pointcut id="pointcut" expression="execution(* com.test.service.UserServiceImpl.*(..))"/>

        <!--执行环绕增加-->
        <aop:advisor advice-ref="beforeLog" pointcut-ref="pointcut"/>
        <aop:advisor advice-ref="afterLog" pointcut-ref="pointcut"/>
    </aop:config>
</beans>

要注意的是,pointcut是切入点,而advisor是将advice-ref类切入到pointcut-ref类中,最后实现测试:

import com.test.service.UserService;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class MyTest {
    public static void main(String[] args) {
        ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
        UserService userService = (UserService) context.getBean("userService");
        userService.add();
    }
}

自定义实现AOP切入

我们还可以自己来决定在何时切入哪个类,首先创建一个自定义的切入类:

package com.test.diy;

public class DiyPointCut {
    public void before(){
        System.out.println("=========之前=========");
    }

    public void after(){
        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:aop="http://www.springframework.org/schema/aop"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/aop
        https://www.springframework.org/schema/aop/spring-aop.xsd">

    <!--注册bean-->
    <bean id="userService" class="com.test.service.UserServiceImpl"/>
    <bean id="beforeLog" class="com.test.log.BeforeLog"/>
    <bean id="afterLog" class="com.test.log.AfterLog"/>

    <!--方式二:自定义配置-->
    <bean id="diy" class="com.test.diy.DiyPointCut"/>

    <aop:config>
        <!--自定义切面:即要切入的类-->
        <aop:aspect ref="diy">
            <!--切入点-->
            <aop:pointcut id="point" expression="execution(* com.test.service.UserServiceImpl.*(..))"/>
            <!--通知-->
            <aop:before method="before" pointcut-ref="point"/>
            <aop:after method="after" pointcut-ref="point"/>
        </aop:aspect>
    </aop:config>
</beans>

测试类不变,看一下结果: 

注解实现AOP

上面的两种方式主要是为了了解AOP的实现原理以及拓展知识,真正工作时大多数还是使用注解的方式进行开发。

首先写一个注解切面类:

package com.test.anno;

import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;

@Aspect //标注这个类是一个切面
public class AnnotationPointCut {
    @Before("execution(* com.test.service.UserService.*(..))")
    public void before(){
        System.out.println("=========之前=========");
    }

    @After("execution(* com.test.service.UserService.*(..))")
    public void after(){
        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:aop="http://www.springframework.org/schema/aop"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/aop
        https://www.springframework.org/schema/aop/spring-aop.xsd">

    <!--注册bean-->
    <bean id="userService" class="com.test.service.UserServiceImpl"/>
    <bean id="beforeLog" class="com.test.log.BeforeLog"/>
    <bean id="afterLog" class="com.test.log.AfterLog"/>

    <!--方式三:注解 -->
    <bean id="anno" class="com.test.anno.AnnotationPointCut"/>
    <!--开启注解支持-->
    <aop:aspectj-autoproxy/>
</beans>

测试类不变,结果没有问题;至于一些切入点的区别,大家可以自行感受。


Spring的IOC和AOP功能几乎是每个公司面试的必考问题,所以针对这两点进行了一些整理,接卸来该复习SpringMVC了,等复习过后我会继续整理,我们下次见👋

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值