Весенняя учеба, достаточно прочитать эту статью Сонг Гэ!

Spring
Весенняя учеба, достаточно прочитать эту статью Сонг Гэ!

1. Введение в весну

Мы часто говорим, что Spring на самом деле относится к Spring Framework, а Spring Framework — это просто ветвь семейства Spring. Так что же есть у семьи Спринг?

Spring был создан для решения сложностей разработки корпоративных приложений. До Spring существовал тяжеловесный инструмент под названием EJB. С помощью Spring можно эффективно отделить Java Beans. Раньше эту операцию мог выполнять только EJB. EJB слишком раздут и редко используется. Spring не ограничивается только разработкой серверной части, но также обладает хорошей производительностью с точки зрения тестируемости и слабой связанности.

Вообще говоря, новички в основном осваивают четыре функции Spring:

  • Ioc/DI
  • AOP
  • дела
  • JdbcTemplate

2. Весенняя загрузка

Обычно мы можем использовать Spring напрямую, добавляя зависимости Maven в проект.Если вам нужно загрузить банку отдельно, адрес загрузки выглядит следующим образом:

После успешной загрузки компоненты в Spring примерно обеспечивают следующие функции:

3.1 Ioc

3.1.1 Концепция Ioc

Ioc (инверсия управления) по-китайски называется инверсией управления. Это концепция и идея. Инверсия управления фактически относится к инверсии управления над объектом. Например, следующий код:

public class Book {
    private Integer id;
    private String name;
    private Double price;
//省略getter/setter
}
public class User {
    private Integer id;
    private String name;
    private Integer age;

    public void doSth() {
        Book book = new Book();
        book.setId(1);
        book.setName("故事新编");
        book.setPrice((double) 20);
    }
}

В этом случае управление объектом Книга находится в объекте Пользователь, так что Книга и Пользователь сильно связаны.Если объект Книга нужно использовать в других объектах, его необходимо пересоздать, то есть создание, инициализацию, разрушение объекта и т.д. Вся операция выполняется самим застройщиком. Если эти операции можно передать контейнеру для управления, разработчики могут быть значительно освобождены от создания объектов.

После использования Spring мы можем передать создание, инициализацию и уничтожение объектов контейнеру Spring для управления. То есть, когда проект стартует, все бины зарегистрируются в контейнере Spring (при необходимости), а затем, если другим бинам понадобится использовать этот бин, им не нужно будет переходить на новый, а сразу перейти к контейнеру Spring. Пружинный контейнер, чтобы попросить.

Рассмотрим этот процесс на простом примере.

3.1.2 Первый опыт Ioc

Сначала создайте обычный проект Maven, а затем введите зависимость spring-context следующим образом:

<dependencies>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-context</artifactId>
        <version>5.1.9.RELEASE</version>
    </dependency>
</dependencies>

Затем создайте файл конфигурации 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"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">

</beans>

В этом файле мы можем настроить все bean-компоненты, которые необходимо зарегистрировать в контейнере 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"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">

    <bean class="org.javaboy.Book" id="book"/>
</beans>

Атрибут class представляет собой полный путь к bean-компоненту, который необходимо зарегистрировать, а id представляет собой уникальную метку bean-компонента.Атрибут name также может использоваться в качестве метки bean-компонента.Более чем в 99 % случаев идентификатор и имя на самом деле одинаковы, но в особых случаях они различаются. .

Затем загрузите этот файл конфигурации:

public class Main {
    public static void main(String[] args) {
        ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
    }
}

Выполните основной метод, файл конфигурации будет загружен автоматически, а экземпляр Book будет инициализирован в Spring. На этом этапе мы явно указываем конструктор без аргументов класса Book и печатаем журнал в конструкторе без аргументов Мы видим, что конструктор без аргументов выполняется, что доказывает, что объект был инициализирован в классе Book. Весенний контейнер.

Наконец, через метод getBean можно получить объект из контейнера:

public class Main {
    public static void main(String[] args) {
        ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
        Book book = (Book) ctx.getBean("book");
        System.out.println(book);
    }
}

Метод загрузки, в дополнение к ClassPathXmlApplicationContext (перейдите к пути к классам, чтобы найти файлы конфигурации), вы также можете использовать FileSystemXmlApplicationContext, FileSystemXmlApplicationContext будет искать файлы конфигурации из пути операционной системы.

public class Main {
    public static void main(String[] args) {
        FileSystemXmlApplicationContext ctx = new FileSystemXmlApplicationContext("F:\\workspace5\\workspace\\spring\\spring-ioc\\src\\main\\resources\\applicationContext.xml");
        Book book = (Book) ctx.getBean("book");
        System.out.println(book);
    }
}

3.2 Приобретение бобов

В предыдущем разделе мы использовали метод ctx.getBean для получения bean-компонента из контейнера Spring, а входящим параметром является имя bean-компонента или атрибут id. В дополнение к этому методу вы также можете получить Bean напрямую через Class.

public class Main {
    public static void main(String[] args) {
        ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
        Book book = ctx.getBean(Book.class);
        System.out.println(book);
    }
}

У этого метода есть большой недостаток, если есть несколько экземпляров, этот метод недоступен, например, в xml файле два 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"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">

    <bean class="org.javaboy.Book" id="book"/>
    <bean class="org.javaboy.Book" id="book2"/>
</beans>

На этом этапе, если вы ищете bean-компонент через Class, будет сообщено о следующей ошибке:

Поэтому обычно рекомендуется использовать имя или идентификатор для получения экземпляра компонента.

3.3 Внедрение атрибутов

Существует несколько способов внедрения свойств в XML-конфигурацию.

3.3.1 Внедрение конструктора

Внедрить значения в свойства бина через конструктор бина.

1. Первым шагом является добавление соответствующего конструктора в Bean:

public class Book {
    private Integer id;
    private String name;
    private Double price;

    public Book() {
        System.out.println("-------book init----------");
    }

    public Book(Integer id, String name, Double price) {
        this.id = id;
        this.name = name;
        this.price = price;
    }
}

2. Внедрить бин в 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 class="org.javaboy.Book" id="book">
        <constructor-arg index="0" value="1"/>
        <constructor-arg index="1" value="三国演义"/>
        <constructor-arg index="2" value="30"/>
    </bean>
</beans>

Здесь следует отметить, что индекс в аргументе конструктора соответствует параметрам конструктора в Book один к одному. Порядок записи может быть обратным, но значение index и value должны быть во взаимно однозначном соответствии.

Другой метод внедрения свойств в конструкторе — это внедрение путем прямого указания имени параметра:

<bean class="org.javaboy.Book" id="book2">
    <constructor-arg name="id" value="2"/>
    <constructor-arg name="name" value="红楼梦"/>
    <constructor-arg name="price" value="40"/>
</bean>

Если имеется несколько конструкторов, он будет автоматически сопоставлен с соответствующим конструктором в соответствии с заданным количеством параметров и типов параметров, а затем будет инициализирован объект.

3.3.2 установка метода впрыска

Помимо конструктора мы также можем вводить значения через метод set.

<bean class="org.javaboy.Book" id="book3">
    <property name="id" value="3"/>
    <property name="name" value="水浒传"/>
    <property name="price" value="30"/>
</bean>

Установите метод инъекции, есть очень важная проблема, то есть имя свойства. У многих людей есть иллюзия, что имя атрибута — это имя атрибута, которое вы определили, что неверно. Во всех фреймворках, когда дело доходит до значений внедрения отражения, имена атрибутов — это не имена атрибутов, определенные в Bean-компоненте, а имена атрибутов, проанализированные с помощью механизма самоанализа в Java.Короче говоря, в соответствии с методом get/set. название.

3.3.3 внедрение пространства имен p

Внедрение пространства имен p, которое используется реже, по существу вызывает метод set.

<bean class="org.javaboy.Book" id="book4" p:id="4" p:bookName="西游记" p:price="33"></bean>

3.3.4 Внедрение внешних компонентов

Иногда мы используем некоторые внешние bean-компоненты.Эти bean-компоненты могут не иметь конструктора, но создаются Builder.В настоящее время мы не можем использовать вышеуказанный метод для ввода в него значений.

Например, в сетевом запросе OkHttp нативное написание выглядит следующим образом:

public class OkHttpMain {
    public static void main(String[] args) {
        OkHttpClient okHttpClient = new OkHttpClient.Builder()
                .build();
        Request request = new Request.Builder()
                .get()
                .url("http://b.hiphotos.baidu.com/image/h%3D300/sign=ad628627aacc7cd9e52d32d909032104/32fa828ba61ea8d3fcd2e9ce9e0a304e241f5803.jpg")
                .build();
        Call call = okHttpClient.newCall(request);
        call.enqueue(new Callback() {
            @Override
            public void onFailure(@NotNull Call call, @NotNull IOException e) {
                System.out.println(e.getMessage());
            }

            @Override
            public void onResponse(@NotNull Call call, @NotNull Response response) throws IOException {
                FileOutputStream out = new FileOutputStream(new File("E:\\123.jpg"));
                int len;
                byte[] buf = new byte[1024];
                InputStream is = response.body().byteStream();
                while ((len = is.read(buf)) != -1) {
                    out.write(buf, 0, len);
                }
                out.close();
                is.close();
            }
        });
    }
}

Этот бин имеет особенность, что экземпляры OkHttpClient и Request не являются напрямую новыми, в процессе вызова метода Builder для него будут настроены некоторые параметры по умолчанию. В этом случае мы можем использовать статическое внедрение фабрики или внедрение фабрики экземпляров, чтобы предоставить экземпляр OkHttpClient.

1. Статическая заводская инжекция

Сначала предоставьте статическую фабрику для OkHttpClient:

public class OkHttpUtils {
    private static OkHttpClient OkHttpClient;
    public static OkHttpClient getInstance() {
        if (OkHttpClient == null) {
            OkHttpClient = new OkHttpClient.Builder().build();
        }
        return OkHttpClient;
    }
}

В xml-файле настройте статическую фабрику:

<bean class="org.javaboy.OkHttpUtils" factory-method="getInstance" id="okHttpClient"></bean>

Эта конфигурация указывает, что getInstance в классе OkHttpUtils — это экземпляр, который нам нужен, и имя экземпляра — okHttpClient. Затем в коде Java получите этот экземпляр и используйте его напрямую.

public class OkHttpMain {
    public static void main(String[] args) {
        ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
        OkHttpClient okHttpClient = ctx.getBean("okHttpClient", OkHttpClient.class);
        Request request = new Request.Builder()
                .get()
                .url("http://b.hiphotos.baidu.com/image/h%3D300/sign=ad628627aacc7cd9e52d32d909032104/32fa828ba61ea8d3fcd2e9ce9e0a304e241f5803.jpg")
                .build();
        Call call = okHttpClient.newCall(request);
        call.enqueue(new Callback() {
            @Override
            public void onFailure(@NotNull Call call, @NotNull IOException e) {
                System.out.println(e.getMessage());
            }

            @Override
            public void onResponse(@NotNull Call call, @NotNull Response response) throws IOException {
                FileOutputStream out = new FileOutputStream(new File("E:\\123.jpg"));
                int len;
                byte[] buf = new byte[1024];
                InputStream is = response.body().byteStream();
                while ((len = is.read(buf)) != -1) {
                    out.write(buf, 0, len);
                }
                out.close();
                is.close();
            }
        });
    }
}

2. Заводская инъекция экземпляра

Фабрика экземпляров заключается в том, что фабричный метод является методом экземпляра, поэтому фабричный класс должен быть создан до того, как можно будет вызвать фабричный метод.

На этот раз фабричный класс выглядит следующим образом:

public class OkHttpUtils {
    private OkHttpClient OkHttpClient;
    public OkHttpClient getInstance() {
        if (OkHttpClient == null) {
            OkHttpClient = new OkHttpClient.Builder().build();
        }
        return OkHttpClient;
    }
}

На этом этапе в файле xml вам нужно сначала предоставить экземпляр фабричного метода, а затем вы можете вызвать фабричный метод:

<bean class="org.javaboy.OkHttpUtils" id="okHttpUtils"/>
<bean class="okhttp3.OkHttpClient" factory-bean="okHttpUtils" factory-method="getInstance" id="okHttpClient"></bean>

Бины, написанные вами, как правило, не используют эти два метода для внедрения, однако, если вам нужно ввести внешние jar-файлы и инициализировать классы внешних jar-файлов, вам может понадобиться использовать эти два метода.

3.4 Внедрение сложных свойств

3.4.1 Внедрение объекта

<bean class="org.javaboy.User" id="user">
    <property name="cat" ref="cat"/>
</bean>
<bean class="org.javaboy.Cat" id="cat">
    <property name="name" value="小白"/>
    <property name="color" value="白色"/>
</bean>

Вы можете вводить объекты через xml и ссылаться на объект через ref.

3.4.2 Внедрение массива

Внедрение массива и внедрение коллекции имеют одинаковую конфигурацию в xml. следующее:

<bean class="org.javaboy.User" id="user">
    <property name="cat" ref="cat"/>
    <property name="favorites">
        <array>
            <value>足球</value>
            <value>篮球</value>
            <value>乒乓球</value>
        </array>
    </property>
</bean>
<bean class="org.javaboy.Cat" id="cat">
    <property name="name" value="小白"/>
    <property name="color" value="白色"/>
</bean>

Обратите внимание, что узлы массива также могут быть заменены узлами списка.

Конечно, узлы массива или списка также могут быть объектами.

<bean class="org.javaboy.User" id="user">
    <property name="cat" ref="cat"/>
    <property name="favorites">
        <list>
            <value>足球</value>
            <value>篮球</value>
            <value>乒乓球</value>
        </list>
    </property>
    <property name="cats">
        <list>
            <ref bean="cat"/>
            <ref bean="cat2"/>
            <bean class="org.javaboy.Cat" id="cat3">
                <property name="name" value="小花"/>
                <property name="color" value="花色"/>
            </bean>
        </list>
    </property>
</bean>
<bean class="org.javaboy.Cat" id="cat">
    <property name="name" value="小白"/>
    <property name="color" value="白色"/>
</bean>
<bean class="org.javaboy.Cat" id="cat2">
    <property name="name" value="小黑"/>
    <property name="color" value="黑色"/>
</bean>

Обратите внимание, что вы можете использовать определенные извне bean-компоненты через ref, или вы можете определить bean-компоненты непосредственно в узлах списка или массива.

3.4.3 Вставка карты

<property name="map">
    <map>
        <entry key="age" value="99"/>
        <entry key="name" value="javaboy"/>
    </map>
</property>

3.4.4 Внедрение свойств

<property name="info">
    <props>
        <prop key="age">99</prop>
        <prop key="name">javaboy</prop>
    </props>
</property>

В приведенной выше демонстрации определенный пользователь выглядит следующим образом:

public class User {
    private Integer id;
    private String name;
    private Integer age;
    private Cat cat;
    private String[] favorites;
    private List<Cat> cats;
    private Map<String,Object> map;
    private Properties info;

    @Override
    public String toString() {
        return "User{" +
                "id=" + id +
                ", name='" + name + '\'' +
                ", age=" + age +
                ", cat=" + cat +
                ", favorites=" + Arrays.toString(favorites) +
                ", cats=" + cats +
                ", map=" + map +
                ", info=" + info +
                '}';
    }

    public Properties getInfo() {
        return info;
    }

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

    public Map<String, Object> getMap() {
        return map;
    }

    public void setMap(Map<String, Object> map) {
        this.map = map;
    }

    public List<Cat> getCats() {
        return cats;
    }

    public void setCats(List<Cat> cats) {
        this.cats = cats;
    }

    public String[] getFavorites() {
        return favorites;
    }

    public void setFavorites(String[] favorites) {
        this.favorites = favorites;
    }

    public User() {
    }

    public User(Integer id, String name, Integer age, Cat cat) {
        this.id = id;
        this.name = name;
        this.age = age;
        this.cat = cat;
    }

    public Cat getCat() {
        return cat;
    }

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

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

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

    public Integer getAge() {
        return age;
    }

    public void setAge(Integer age) {
        this.age = age;
    }
}

3.5 Конфигурация Java

В Spring есть три разных способа зарегистрировать bean-компонент в контейнере Spring.

  • Внедрение XML, как упоминалось ранее
  • Конфигурация Java (регистрация bean-компонентов в контейнере Spring с помощью кода Java)
  • Автоматическое сканирование

Здесь мы рассмотрим конфигурацию Java.

Конфигурация Java редко использовалась до появления Spring Boot.Со времен Spring Boot разработка конфигурации Java широко использовалась, потому что в Spring Boot не используется одна строка конфигурации XML.

Например, у меня есть следующий компонент:

public class SayHello {
    public String sayHello(String name) {
        return "hello " + name;
    }
}

В конфигурации Java мы заменяем предыдущий файл applicationContext.xml классом конфигурации Java.

@Configuration
public class JavaConfig {
    @Bean
    SayHello sayHello() {
        return new SayHello();
    }
}

Во-первых, на классе конфигурации есть аннотация @Configuration, которая указывает на то, что этот класс является не обычным классом, а классом конфигурации, и его функция эквивалентна applicationContext.xml. Затем определите метод, метод возвращает объект и добавьте к методу аннотацию @Bean, указывающую, что возвращаемое значение этого метода внедряется в контейнер Spring. Другими словами, метод, соответствующий @Bean, эквивалентен узлу компонента в applicationContext.xml.

Поскольку это класс конфигурации, нам нужно загрузить класс конфигурации при запуске проекта.

public class Main {
    public static void main(String[] args) {
        AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(JavaConfig.class);
        SayHello hello = ctx.getBean(SayHello.class);
        System.out.println(hello.sayHello("javaboy"));
    }
}

Обратите внимание, что загрузка конфигурации реализована с помощью AnnotationConfigApplicationContext.

Здесь есть одно предостережение относительно конфигурации Java: каково имя bean-компонента?

Имя компонента по умолчанию — это имя метода. Возьмем приведенный выше случай в качестве примера. Имя компонента — sayHello. Если разработчик хочет настроить имя метода, его также можно настроить непосредственно в аннотации @Bean. Следующая конфигурация указывает, что имя измененного bean-компонента — javaboy:

@Configuration
public class JavaConfig {
    @Bean("javaboy")
    SayHello sayHello() {
        return new SayHello();
    }
}

3.6 Автоматическая конфигурация

В нашей реальной разработке используется много автоматической настройки.

Автоматическую настройку можно выполнить либо с помощью конфигурации Java, либо с помощью конфигурации xml.

3.6.1 Подготовка

Например, у меня есть UserService, и я хочу, чтобы этот класс автоматически регистрировался в контейнере Spring во время автоматического сканирования, поэтому я могу добавить @Service к этому классу в качестве маркера.

Есть четыре аннотации с функциями, аналогичными аннотации @Service:

  • @Component
  • @Repository
  • @Service
  • @Controller

Среди четырех остальные три основаны на @Component, и из текущего исходного кода функции тоже одинаковы, так зачем же делать три? В основном для удобства при добавлении разных классов.

  • На уровне службы при добавлении аннотаций используйте @Service
  • В слое Dao при добавлении аннотаций используйте @Repository
  • На уровне контроллера при добавлении аннотаций используйте @Controller
  • При аннотировании других компонентов используйте @Component
@Service
public class UserService {
    public List<String> getAllUser() {
        List<String> users = new ArrayList<>();
        for (int i = 0; i < 10; i++) {
            users.add("javaboy:" + i);
        }
        return users;
    }
}

После завершения добавления есть два способа автоматического сканирования: один — настроить автоматическое сканирование через Java-код, а другой — настроить автоматическое сканирование через файл xml.

3.6.2 Автоматическое сканирование конфигурации кода Java

@Configuration
@ComponentScan(basePackages = "org.javaboy.javaconfig.service")
public class JavaConfig {
}

Затем в стартапе проекта загрузите класс конфигурации, в классе конфигурации укажите пакет для проверки через аннотацию @ComponentScan (если не указано, по умолчанию загружаемый пакетом бин, в котором находится класс конфигурации, и пакет под пакетом, где находится класс конфигурации, сканируется класс под подпакетом), после чего можно получить экземпляр UserService:

public class Main {
    public static void main(String[] args) {
        AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(JavaConfig.class);
        UserService userService = ctx.getBean(UserService.class);
        System.out.println(userService.getAllUser());
    }
}

Здесь есть несколько вопросов, о которых следует знать:

1. Как зовут Бин?

По умолчанию имена bean-компонентов являются именами классов в нижнем регистре. Например, UserService выше, имя его экземпляра по умолчанию — userService. Если разработчик хочет настроить имя, его можно добавить прямо в аннотацию @Service.

2. Сколько существует методов сканирования?

В приведенной выше конфигурации мы сканируем в соответствии с расположением пакета. То есть бин должен быть помещен в указанное место сканирования, иначе, даже если у вас есть аннотация @Service, он не будет сканироваться.

Помимо сканирования по расположению посылки, есть еще один способ сканирования по аннотации. Например, следующая конфигурация:

@Configuration
@ComponentScan(basePackages = "org.javaboy.javaconfig",useDefaultFilters = true,excludeFilters = {@ComponentScan.Filter(type = FilterType.ANNOTATION,classes = Controller.class)})
public class JavaConfig {
}

Эта конфигурация означает сканирование всех bean-компонентов в org.javaboy.javaconfig, кроме контроллера.

3.6.3 Автоматическое сканирование конфигурации XML

<context:component-scan base-package="org.javaboy.javaconfig"/>

Приведенная выше строка конфигурации означает сканирование всех bean-компонентов в org.javaboy.javaconfig. Конечно, вы также можете сканировать по классам.

После завершения конфигурации XML загрузите конфигурацию XML в код Java.

public class XMLTest {
    public static void main(String[] args) {
        ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
        UserService userService = ctx.getBean(UserService.class);
        List<String> list = userService.getAllUser();
        System.out.println(list);
    }
}

Также возможно сканирование по типу аннотации в конфигурации XML:

<context:component-scan base-package="org.javaboy.javaconfig" use-default-filters="true">
    <context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
</context:component-scan>

3.6.4 Внедрение объекта

Существует три способа внедрения объектов при автоматическом сканировании:

  1. @Autowired
  2. @Resources
  3. @Injected

@Autowired - искать по типу, а потом его присваивать, есть требование, чтобы у этого типа был только один объект, иначе будет сообщено об ошибке. @Resources ищется по имени.По умолчанию заданное имя переменной является искомым именем.Конечно, разработчики также могут указать его вручную в аннотации @Resources. Поэтому, если есть несколько экземпляров класса, вы должны использовать @Resources для внедрения.Если вы очень часто используете @Autowired, это также возможно.В это время вам нужно сотрудничать с другой аннотацией, @Qualifier, вы можете указать имя переменной в @Qualifier, два Использование обоих вместе (@Qualifier и @Autowired) позволяет найти переменную по ее имени.

@Service
public class UserService {

    @Autowired
    UserDao userDao;
    public String hello() {
        return userDao.hello();
    }

    public List<String> getAllUser() {
        List<String> users = new ArrayList<>();
        for (int i = 0; i < 10; i++) {
            users.add("javaboy:" + i);
        }
        return users;
    }
}

3.7 Условные аннотации

Условные аннотации — это конфигурации, которые вступают в силу при выполнении определенного условия.

3.7.1 Условные аннотации

Как получить информацию об операционной системе в Windows в первую очередь? Команда для просмотра каталога папки в Windows — это dir, а команда для просмотра каталога папки в Linux — ls.Теперь я хочу автоматически распечатать команду отображения каталога в Windows, когда система работает в Windows, и автоматически отображать ее когда работает Linux Команды отображения каталога в Linux.

Сначала определите интерфейс, который отображает каталог папки:

public interface ShowCmd {
    String showCmd();
}

Затем реализуйте экземпляр под Windows и экземпляр под Linux соответственно:

public class WinShowCmd implements ShowCmd {
    @Override
    public String showCmd() {
        return "dir";
    }
}
public class LinuxShowCmd implements ShowCmd {
    @Override
    public String showCmd() {
        return "ls";
    }
}

Затем определите два условия, одно для Windows и одно для Linux.

public class WindowsCondition implements Condition {
    @Override
    public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
        return context.getEnvironment().getProperty("os.name").toLowerCase().contains("windows");
    }
}
public class LinuxCondition implements Condition {
    @Override
    public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
        return context.getEnvironment().getProperty("os.name").toLowerCase().contains("linux");
    }
}

Далее, при определении боба, вы можете настроить условные аннотации.

@Configuration
public class JavaConfig {
    @Bean("showCmd")
    @Conditional(WindowsCondition.class)
    ShowCmd winCmd() {
        return new WinShowCmd();
    }

    @Bean("showCmd")
    @Conditional(LinuxCondition.class)
    ShowCmd linuxCmd() {
        return new LinuxShowCmd();
    }
}

Здесь обязательно дайте двум bean-компонентам одно и то же имя, чтобы они могли автоматически сопоставляться при вызове. Затем добавьте условную аннотацию к каждому bean-компоненту, и когда метод match в условии вернет true, определение этого bean-компонента вступит в силу.

public class JavaMain {
    public static void main(String[] args) {
        AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(JavaConfig.class);
        ShowCmd showCmd = (ShowCmd) ctx.getBean("showCmd");
        System.out.println(showCmd.showCmd());
    }
}

Очень типичным сценарием использования условных аннотаций является переключение между несколькими средами.

3.7.2 Переключение между несколькими средами

В процессе разработки, как быстро переключаться между средами разработки/производства/тестирования? Spring предоставляет Profile для решения этой проблемы.Нижний слой Profile представляет собой условную аннотацию. Это видно из определения аннотации @Profile:

@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Conditional(ProfileCondition.class)
public @interface Profile {

	/**
	 * The set of profiles for which the annotated component should be registered.
	 */
	String[] value();

}
class ProfileCondition implements Condition {

	@Override
	public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
		MultiValueMap<String, Object> attrs = metadata.getAllAnnotationAttributes(Profile.class.getName());
		if (attrs != null) {
			for (Object value : attrs.get("value")) {
				if (context.getEnvironment().acceptsProfiles(Profiles.of((String[]) value))) {
					return true;
				}
			}
			return false;
		}
		return true;
	}

}

Мы определяем источник данных:

public class DataSource {
    private String url;
    private String username;
    private String password;

    @Override
    public String toString() {
        return "DataSource{" +
                "url='" + url + '\'' +
                ", username='" + username + '\'' +
                ", password='" + password + '\'' +
                '}';
    }

    public String getUrl() {
        return url;
    }

    public void setUrl(String url) {
        this.url = url;
    }

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }
}

Затем при настройке bean-компонента укажите другую среду с помощью аннотации @Profile:

@Bean("ds")
@Profile("dev")
DataSource devDataSource() {
    DataSource dataSource = new DataSource();
    dataSource.setUrl("jdbc:mysql://127.0.0.1:3306/dev");
    dataSource.setUsername("root");
    dataSource.setPassword("123");
    return dataSource;
}
@Bean("ds")
@Profile("prod")
DataSource prodDataSource() {
    DataSource dataSource = new DataSource();
    dataSource.setUrl("jdbc:mysql://192.158.222.33:3306/dev");
    dataSource.setUsername("jkldasjfkl");
    dataSource.setPassword("jfsdjflkajkld");
    return dataSource;
}

Наконец, при загрузке класса конфигурации обратите внимание, что вам нужно сначала установить текущую среду, а затем загрузить класс конфигурации:

public class JavaMain {
    public static void main(String[] args) {
        AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
        ctx.getEnvironment().setActiveProfiles("dev");
        ctx.register(JavaConfig.class);
        ctx.refresh();
        DataSource ds = (DataSource) ctx.getBean("ds");
        System.out.println(ds);
    }
}

Это настраивается в коде Java. Переключение среды также можно настроить в файле XML.Следующая конфигурация находится в файле XML и должна быть размещена за другими узлами.

<beans profile="dev">
    <bean class="org.javaboy.DataSource" id="dataSource">
        <property name="url" value="jdbc:mysql:///devdb"/>
        <property name="password" value="root"/>
        <property name="username" value="root"/>
    </bean>
</beans>
<beans profile="prod">
    <bean class="org.javaboy.DataSource" id="dataSource">
        <property name="url" value="jdbc:mysql://111.111.111.111/devdb"/>
        <property name="password" value="jsdfaklfj789345fjsd"/>
        <property name="username" value="root"/>
    </bean>
</beans>

Запустите класс, чтобы установить текущую среду и загрузить конфигурацию:

public class Main {
    public static void main(String[] args) {
        ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext();
        ctx.getEnvironment().setActiveProfiles("prod");
        ctx.setConfigLocation("applicationContext.xml");
        ctx.refresh();
        DataSource dataSource = (DataSource) ctx.getBean("dataSource");
        System.out.println(dataSource);
    }
}

3.8 Другое

3.8.1 Область применения бинов

Компоненты, зарегистрированные в конфигурации XML, или компоненты, зарегистрированные в конфигурации Java, если я выбираю несколько раз, будет ли выбранный объект одним и тем же?

public class Main {
    public static void main(String[] args) {
        ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
        User user = ctx.getBean("user", User.class);
        User user2 = ctx.getBean("user", User.class);
        System.out.println(user==user2);
    }
}

Как и выше, один и тот же bean-компонент получается несколько раз из контейнера Spring.По умолчанию полученный фактически один и тот же экземпляр. Конечно, мы можем настроить его вручную.

<bean class="org.javaboy.User" id="user" scope="prototype" />

Установив атрибут области в узле XML, мы можем настроить количество экземпляров по умолчанию. Значение scope равно singleton (по умолчанию), что означает, что bean-компонент существует в форме singleton в контейнере Spring. множественные приобретения приведут к множественным приобретениям различных экземпляров.

В дополнение к синглтону и прототипу есть еще два значения: запрос и сеанс. Эти два значения действительны в веб-среде. Это конфигурация в XML, мы также можем настроить в Java.

@Configuration
public class JavaConfig {
    @Bean
    @Scope("prototype")
    SayHello sayHello() {
        return new SayHello();
    }
}

В коде Java мы можем указать область действия bean-компонента с помощью аннотации @Scope.

Конечно, в конфигурации автоматического сканирования вы также можете указать область действия бина.

@Repository
@Scope("prototype")
public class UserDao {
    public String hello() {
        return "userdao";
    }
}

3.8.2 Разница между идентификатором и именем

В конфигурации XML мы видим, что мы можем указать уникальный идентификатор для бина по id или по имени.В большинстве случаев эти две функции одинаковы, с небольшой разницей:

имя поддерживает несколько значений. Между несколькими именами разделяйте их с помощью , :

<bean class="org.javaboy.User" name="user,user1,user2,user3" scope="prototype"/>

На данный момент текущий объект может быть получен через user, user1, user2 и user3:

public class Main {
    public static void main(String[] args) {
        ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
        User user = ctx.getBean("user", User.class);
        User user2 = ctx.getBean("user2", User.class);
        System.out.println(user);
        System.out.println(user2);
    }
}

И id не поддерживает несколько значений. Если его принудительно разделить с помощью , это все еще значение. Например, следующая конфигурация:

<bean class="org.javaboy.User" id="user,user1,user2,user3" scope="prototype" />

Эта конфигурация указывает, что bean-компонент назван какuser,user1,user2,user3, конкретный вызов выглядит следующим образом:

public class Main {
    public static void main(String[] args) {
        ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
        User user = ctx.getBean("user,user1,user2,user3", User.class);
        User user2 = ctx.getBean("user,user1,user2,user3", User.class);
        System.out.println(user);
        System.out.println(user2);
    }
}

3.8.3 Гибридная конфигурация

Гибридная конфигурация — это конфигурация Java + конфигурация XML. В случае совмещения конфигурация XML может быть введена в конфигурацию Java.

@Configuration
@ImportResource("classpath:applicationContext.xml")
public class JavaConfig {
}

В конфигурации Java конфигурацию XML можно импортировать с помощью аннотации @ImportResource.

4. Осведомленный интерфейс

Осведомленный интерфейс, буквально понимаемый как захват восприятия. Простой Бин не является разумным.

В сценарии, описанном в разделе 3.6.4, причина, по которой UserDao можно внедрить в UserService, заключается в том, что оба они управляются контейнером Spring. Если вы напрямую создаете UserService, это бесполезно, потому что UserService не управляется контейнером Spring, поэтому он не будет внедрять в него bean-компоненты.

В реальной разработке мы можем столкнуться с некоторыми классами и нам потребуется получить подробную информацию о контейнере, что можно получить через интерфейс Aware.

Aware — это пустой интерфейс со множеством классов реализации:

Эти реализованные интерфейсы имеют некоторые общие черты:

  1. оба заканчиваются на Aware
  2. Оба унаследованы от Aware
  3. Метод set определяется в интерфейсе

Каждый подинтерфейс предоставляет метод set, и параметр метода — это то, что должен воспринимать текущий bean-компонент, поэтому нам нужно объявить соответствующие переменные-члены в bean-компоненте, чтобы принять этот параметр. Получив этот параметр, вы можете получить подробную информацию о контейнере через этот параметр.

@Component
public class SayHello implements ApplicationContextAware {
    private ApplicationContext applicationContext;
    public String sayHello(String name) {
        //判断容器中是否存在某个 Bean
        boolean userDao = applicationContext.containsBean("userDao333");
        System.out.println(userDao);
        return "hello " + name;
    }
    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.applicationContext = applicationContext;
    }
}

5.1 Aop

АОП (аспектно-ориентированное программирование), аспектно-ориентированное программирование, являющееся дополнением к объектно-ориентированному мышлению.

Аспектно-ориентированное программирование заключается в динамическом расширении функции метода без изменения исходного кода программы во время работы программы.Существует много распространенных сценариев использования:

  1. бревно
  2. дела
  3. операции с базой данных
  4. ....

Среди этих операций без исключения много шаблонного кода, а решение шаблонного кода и устранение наворотов — сильная сторона АОП.

В Aop есть несколько общих понятий:

концепция инструкция
Точка отсечки Место для добавления кода называется pointcut.
Уведомления (улучшенные) Уведомление — это код, который динамически добавляется в pointcut.
раздел точка+уведомление
Соединение определение точки

5.1.1 Реализация Аоп

Набор Aop фактически реализован на основе динамических прокси-серверов Java.

Динамические прокси в Java реализуются двумя способами:

  • cglib
  • jdk

5.2 Динамический прокси

Динамический прокси на основе JDK.

1. Определите интерфейс калькулятора:

public interface MyCalculator {
    int add(int a, int b);
}

2. Определить реализацию компьютерного интерфейса:

public class MyCalculatorImpl implements MyCalculator {
    public int add(int a, int b) {
        return a+b;
    }
}

3. Определите класс прокси

public class CalculatorProxy {
    public static Object getInstance(final MyCalculatorImpl myCalculator) {
        return Proxy.newProxyInstance(CalculatorProxy.class.getClassLoader(), myCalculator.getClass().getInterfaces(), new InvocationHandler() {
            /**
             * @param proxy 代理对象
             * @param method 代理的方法
             * @param args 方法的参数
             * @return
             * @throws Throwable
             */
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                System.out.println(method.getName()+"方法开始执行啦...");
                Object invoke = method.invoke(myCalculator, args);
                System.out.println(method.getName()+"方法执行结束啦...");
                return invoke;
            }
        });
    }
}

Метод Proxy.newProxyInstance получает три параметра, первый — загрузчик классов, второй — интерфейс нескольких реализаций прокси, а третий — обработчик метода объекта прокси.Все дополнительные поведения, которые нужно добавить, реализованы в вызове метод .

5.3 Пять типов уведомлений

Существует 5 типов советов по Aop в Spring:

  • предварительное уведомление
  • опубликовать уведомление
  • уведомление об исключении
  • Вернуться к уведомлению
  • Объемное уведомление

Конкретная реализация, здесь тот же случай, что и в 5.2, это все еще функция улучшения метода для калькулятора.

Во-первых, в проекте введите зависимости Spring (на этот раз вам нужно ввести зависимости, связанные с Aop):

<dependencies>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-context</artifactId>
        <version>5.1.9.RELEASE</version>
    </dependency>
    <dependency>
        <groupId>org.aspectj</groupId>
        <artifactId>aspectjweaver</artifactId>
        <version>1.9.5</version>
    </dependency>
    <dependency>
        <groupId>org.aspectj</groupId>
        <artifactId>aspectjrt</artifactId>
        <version>1.9.5</version>
    </dependency>
</dependencies>

Затем определите pointcut.Вот два способа определить pointcut:

  • Используйте пользовательские аннотации
  • правила использования

Среди них использование пользовательских аннотаций для обозначения точечных разрезов является навязчивым, поэтому этот метод не рекомендуется в реальной разработке. Он предназначен только для понимания. Другой способ использования правил для определения точечных разрезов не является навязчивым. Обычно рекомендуется использовать этот способ.

пользовательская аннотация

Сначала настройте аннотацию:

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Action {
}

Затем добавьте эту аннотацию в метод, который нужно перехватить, а в метод add добавьте аннотацию @Action, указывающую, что метод будет перехвачен Aop, а другие методы без этой аннотации затронуты не будут.

@Component
public class MyCalculatorImpl {
    @Action
    public int add(int a, int b) {
        return a + b;
    }

    public void min(int a, int b) {
        System.out.println(a + "-" + b + "=" + (a - b));
    }
}

Затем определите улучшения (Уведомления, Советы):

@Component
@Aspect//表示这是一个切面
public class LogAspect {

    /**
     * @param joinPoint 包含了目标方法的关键信息
     * @Before 注解表示这是一个前置通知,即在目标方法执行之前执行,注解中,需要填入切点
     */
    @Before(value = "@annotation(Action)")
    public void before(JoinPoint joinPoint) {
        Signature signature = joinPoint.getSignature();
        String name = signature.getName();
        System.out.println(name + "方法开始执行了...");
    }

    /**
     * 后置通知
     * @param joinPoint 包含了目标方法的所有关键信息
     * @After 表示这是一个后置通知,即在目标方法执行之后执行
     */
    @After("@annotation(Action)")
    public void after(JoinPoint joinPoint) {
        Signature signature = joinPoint.getSignature();
        String name = signature.getName();
        System.out.println(name + "方法执行结束了...");
    }

    /**
     * @param joinPoint
     * @@AfterReturning 表示这是一个返回通知,即有目标方法有返回值的时候才会触发,该注解中的 returning 属性表示目标方法返回值的变量名,这个需要和参数一一对应吗,注意:目标方法的返回值类型要和这里方法返回值参数的类型一致,否则拦截不到,如果想拦截所有(包括返回值为 void),则方法返回值参数可以为 Object
     */
    @AfterReturning(value = "@annotation(Action)",returning = "r")
    public void returing(JoinPoint joinPoint,Integer r) {
        Signature signature = joinPoint.getSignature();
        String name = signature.getName();
        System.out.println(name + "方法返回:"+r);
    }

    /**
     * 异常通知
     * @param joinPoint
     * @param e 目标方法所抛出的异常,注意,这个参数必须是目标方法所抛出的异常或者所抛出的异常的父类,只有这样,才会捕获。如果想拦截所有,参数类型声明为 Exception
     */
    @AfterThrowing(value = "@annotation(Action)",throwing = "e")
    public void afterThrowing(JoinPoint joinPoint,Exception e) {
        Signature signature = joinPoint.getSignature();
        String name = signature.getName();
        System.out.println(name + "方法抛异常了:"+e.getMessage());
    }

    /**
     * 环绕通知
     *
     * 环绕通知是集大成者,可以用环绕通知实现上面的四个通知,这个方法的核心有点类似于在这里通过反射执行方法
     * @param pjp
     * @return 注意这里的返回值类型最好是 Object ,和拦截到的方法相匹配
     */
    @Around("@annotation(Action)")
    public Object around(ProceedingJoinPoint pjp) {
        Object proceed = null;
        try {
            //这个相当于 method.invoke 方法,我们可以在这个方法的前后分别添加日志,就相当于是前置/后置通知
            proceed = pjp.proceed();
        } catch (Throwable throwable) {
            throwable.printStackTrace();
        }
        return proceed;
    }
}

После завершения определения уведомления в классе конфигурации включите сканирование пакетов и автоматическое проксирование:

@Configuration
@ComponentScan
@EnableAspectJAutoProxy//开启自动代理
public class JavaConfig {
}

Затем в методе Main начните вызывать:

public class Main {
    public static void main(String[] args) {
        AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(JavaConfig.class);
        MyCalculatorImpl myCalculator = ctx.getBean(MyCalculatorImpl.class);
        myCalculator.add(3, 4);
        myCalculator.min(3, 4);
    }
}

Оглядываясь назад на аспект LogAspect, мы обнаруживаем, что определение pointcut недостаточно гибкое. Предыдущий pointcut записывается непосредственно в аннотации. Таким образом, если мы хотим изменить pointcut, мы должны изменить каждый метод. Следовательно, мы можем изменить унифицированное определение pointcut, а затем унифицированный вызов.

@Component
@Aspect//表示这是一个切面
public class LogAspect {

    /**
     * 可以统一定义切点
     */
    @Pointcut("@annotation(Action)")
    public void pointcut() {

    }

    /**
     * @param joinPoint 包含了目标方法的关键信息
     * @Before 注解表示这是一个前置通知,即在目标方法执行之前执行,注解中,需要填入切点
     */
    @Before(value = "pointcut()")
    public void before(JoinPoint joinPoint) {
        Signature signature = joinPoint.getSignature();
        String name = signature.getName();
        System.out.println(name + "方法开始执行了...");
    }

    /**
     * 后置通知
     * @param joinPoint 包含了目标方法的所有关键信息
     * @After 表示这是一个后置通知,即在目标方法执行之后执行
     */
    @After("pointcut()")
    public void after(JoinPoint joinPoint) {
        Signature signature = joinPoint.getSignature();
        String name = signature.getName();
        System.out.println(name + "方法执行结束了...");
    }

    /**
     * @param joinPoint
     * @@AfterReturning 表示这是一个返回通知,即有目标方法有返回值的时候才会触发,该注解中的 returning 属性表示目标方法返回值的变量名,这个需要和参数一一对应吗,注意:目标方法的返回值类型要和这里方法返回值参数的类型一致,否则拦截不到,如果想拦截所有(包括返回值为 void),则方法返回值参数可以为 Object
     */
    @AfterReturning(value = "pointcut()",returning = "r")
    public void returing(JoinPoint joinPoint,Integer r) {
        Signature signature = joinPoint.getSignature();
        String name = signature.getName();
        System.out.println(name + "方法返回:"+r);
    }

    /**
     * 异常通知
     * @param joinPoint
     * @param e 目标方法所抛出的异常,注意,这个参数必须是目标方法所抛出的异常或者所抛出的异常的父类,只有这样,才会捕获。如果想拦截所有,参数类型声明为 Exception
     */
    @AfterThrowing(value = "pointcut()",throwing = "e")
    public void afterThrowing(JoinPoint joinPoint,Exception e) {
        Signature signature = joinPoint.getSignature();
        String name = signature.getName();
        System.out.println(name + "方法抛异常了:"+e.getMessage());
    }

    /**
     * 环绕通知
     *
     * 环绕通知是集大成者,可以用环绕通知实现上面的四个通知,这个方法的核心有点类似于在这里通过反射执行方法
     * @param pjp
     * @return 注意这里的返回值类型最好是 Object ,和拦截到的方法相匹配
     */
    @Around("pointcut()")
    public Object around(ProceedingJoinPoint pjp) {
        Object proceed = null;
        try {
            //这个相当于 method.invoke 方法,我们可以在这个方法的前后分别添加日志,就相当于是前置/后置通知
            proceed = pjp.proceed();
        } catch (Throwable throwable) {
            throwable.printStackTrace();
        }
        return proceed;
    }
}

Однако все также заметили, что использование аннотаций навязчиво, и мы можем продолжить оптимизацию и перейти на ненавязчивые. Переопределить pointcut, определение нового pointcut больше не нуждается в аннотации @Action, а целевой метод, который нужно перехватить, не нужно добавлять с аннотацией @Action. Следующий метод является более общим методом перехвата:

@Component
@Aspect//表示这是一个切面
public class LogAspect {

    /**
     * 可以统一定义切点
     */
    @Pointcut("@annotation(Action)")
    public void pointcut2() {

    }
    /**
     * 可以统一定义切点
     * 第一个 * 表示要拦截的目标方法返回值任意(也可以明确指定返回值类型
     * 第二个 * 表示包中的任意类(也可以明确指定类
     * 第三个 * 表示类中的任意方法
     * 最后面的两个点表示方法参数任意,个数任意,类型任意
     */
    @Pointcut("execution(* org.javaboy.aop.commons.*.*(..))")
    public void pointcut() {

    }

    /**
     * @param joinPoint 包含了目标方法的关键信息
     * @Before 注解表示这是一个前置通知,即在目标方法执行之前执行,注解中,需要填入切点
     */
    @Before(value = "pointcut()")
    public void before(JoinPoint joinPoint) {
        Signature signature = joinPoint.getSignature();
        String name = signature.getName();
        System.out.println(name + "方法开始执行了...");
    }

    /**
     * 后置通知
     * @param joinPoint 包含了目标方法的所有关键信息
     * @After 表示这是一个后置通知,即在目标方法执行之后执行
     */
    @After("pointcut()")
    public void after(JoinPoint joinPoint) {
        Signature signature = joinPoint.getSignature();
        String name = signature.getName();
        System.out.println(name + "方法执行结束了...");
    }

    /**
     * @param joinPoint
     * @@AfterReturning 表示这是一个返回通知,即有目标方法有返回值的时候才会触发,该注解中的 returning 属性表示目标方法返回值的变量名,这个需要和参数一一对应吗,注意:目标方法的返回值类型要和这里方法返回值参数的类型一致,否则拦截不到,如果想拦截所有(包括返回值为 void),则方法返回值参数可以为 Object
     */
    @AfterReturning(value = "pointcut()",returning = "r")
    public void returing(JoinPoint joinPoint,Integer r) {
        Signature signature = joinPoint.getSignature();
        String name = signature.getName();
        System.out.println(name + "方法返回:"+r);
    }

    /**
     * 异常通知
     * @param joinPoint
     * @param e 目标方法所抛出的异常,注意,这个参数必须是目标方法所抛出的异常或者所抛出的异常的父类,只有这样,才会捕获。如果想拦截所有,参数类型声明为 Exception
     */
    @AfterThrowing(value = "pointcut()",throwing = "e")
    public void afterThrowing(JoinPoint joinPoint,Exception e) {
        Signature signature = joinPoint.getSignature();
        String name = signature.getName();
        System.out.println(name + "方法抛异常了:"+e.getMessage());
    }

    /**
     * 环绕通知
     *
     * 环绕通知是集大成者,可以用环绕通知实现上面的四个通知,这个方法的核心有点类似于在这里通过反射执行方法
     * @param pjp
     * @return 注意这里的返回值类型最好是 Object ,和拦截到的方法相匹配
     */
    @Around("pointcut()")
    public Object around(ProceedingJoinPoint pjp) {
        Object proceed = null;
        try {
            //这个相当于 method.invoke 方法,我们可以在这个方法的前后分别添加日志,就相当于是前置/后置通知
            proceed = pjp.proceed();
        } catch (Throwable throwable) {
            throwable.printStackTrace();
        }
        return proceed;
    }
}

5.4 Конфигурация XML Aop

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-context</artifactId>
    <version>5.1.9.RELEASE</version>
</dependency>
<dependency>
    <groupId>org.aspectj</groupId>
    <artifactId>aspectjweaver</artifactId>
    <version>1.9.5</version>
</dependency>
<dependency>
    <groupId>org.aspectj</groupId>
    <artifactId>aspectjrt</artifactId>
    <version>1.9.5</version>
</dependency>

Затем определите уведомление/улучшение, но просто определите свое собственное поведение, без аннотаций:

public class LogAspect {

    public void before(JoinPoint joinPoint) {
        Signature signature = joinPoint.getSignature();
        String name = signature.getName();
        System.out.println(name + "方法开始执行了...");
    }
    
    public void after(JoinPoint joinPoint) {
        Signature signature = joinPoint.getSignature();
        String name = signature.getName();
        System.out.println(name + "方法执行结束了...");
    }

    public void returing(JoinPoint joinPoint,Integer r) {
        Signature signature = joinPoint.getSignature();
        String name = signature.getName();
        System.out.println(name + "方法返回:"+r);
    }
    
    public void afterThrowing(JoinPoint joinPoint,Exception e) {
        Signature signature = joinPoint.getSignature();
        String name = signature.getName();
        System.out.println(name + "方法抛异常了:"+e.getMessage());
    }
    
    public Object around(ProceedingJoinPoint pjp) {
        Object proceed = null;
        try {
            //这个相当于 method.invoke 方法,我们可以在这个方法的前后分别添加日志,就相当于是前置/后置通知
            proceed = pjp.proceed();
        } catch (Throwable throwable) {
            throwable.printStackTrace();
        }
        return proceed;
    }
}

Далее настройте Aop весной:

<bean class="org.javaboy.aop.LogAspect" id="logAspect"/>
<aop:config>
    <aop:pointcut id="pc1" expression="execution(* org.javaboy.aop.commons.*.*(..))"/>
    <aop:aspect ref="logAspect">
        <aop:before method="before" pointcut-ref="pc1"/>
        <aop:after method="after" pointcut-ref="pc1"/>
        <aop:after-returning method="returing" pointcut-ref="pc1" returning="r"/>
        <aop:after-throwing method="afterThrowing" pointcut-ref="pc1" throwing="e"/>
        <aop:around method="around" pointcut-ref="pc1"/>
    </aop:aspect>
</aop:config>

Наконец, загрузите файл конфигурации в методе Main:

public class Main {
    public static void main(String[] args) {
        ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
        MyCalculatorImpl myCalculator = ctx.getBean(MyCalculatorImpl.class);
        myCalculator.add(3, 4);
        myCalculator.min(5, 6);
    }
}

6. JdbcTemplate

JdbcTemplate — это рабочий инструмент JDBC, инкапсулированный Spring с использованием идеи Aop.

6.1 Подготовка

Создайте новый проект и добавьте следующие зависимости:

<dependencies>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-jdbc</artifactId>
        <version>5.1.9.RELEASE</version>
    </dependency>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-context</artifactId>
        <version>5.1.9.RELEASE</version>
    </dependency>
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <version>8.0.17</version>
    </dependency>
</dependencies>

Подготовьте базу данных:

CREATE DATABASE /*!32312 IF NOT EXISTS*/`test01` /*!40100 DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci */ /*!80016 DEFAULT ENCRYPTION='N' */;

USE `test01`;

/*Table structure for table `user` */

DROP TABLE IF EXISTS `user`;

CREATE TABLE `user` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `username` varchar(255) COLLATE utf8mb4_general_ci DEFAULT NULL,
  `address` varchar(255) COLLATE utf8mb4_general_ci DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;

Подготовьте класс сущности:

public class User {
    private Integer id;
    private String username;
    private String address;

    @Override
    public String toString() {
        return "User{" +
                "id=" + id +
                ", username='" + username + '\'' +
                ", address='" + address + '\'' +
                '}';
    }

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public String getAddress() {
        return address;
    }

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

6.2 Конфигурация Java

Укажите класс конфигурации и настройте JdbcTemplate в классе конфигурации:

@Configuration
public class JdbcConfig {
    @Bean
    DataSource dataSource() {
        DriverManagerDataSource dataSource = new DriverManagerDataSource();
        dataSource.setDriverClassName("com.mysql.cj.jdbc.Driver");
        dataSource.setUsername("root");
        dataSource.setPassword("123");
        dataSource.setUrl("jdbc:mysql:///test01");
        return dataSource;
    }
    @Bean
    JdbcTemplate jdbcTemplate() {
        return new JdbcTemplate(dataSource());
    }
}

Здесь предоставляются два bean-компонента, один — bean-компонент DataSource, а другой — bean-компонент JdbcTemplate.Настройка JdbcTemplate очень проста, просто нужно создать новый bean-компонент, а затем настроить DataSource.

public class Main {

    private JdbcTemplate jdbcTemplate;

    @Before
    public void before() {
        AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(JdbcConfig.class);
        jdbcTemplate = ctx.getBean(JdbcTemplate.class);
    }

    @Test
    public void insert() {
        jdbcTemplate.update("insert into user (username,address) values (?,?);", "javaboy", "www.javaboy.org");
    }
    @Test
    public void update() {
        jdbcTemplate.update("update user set username=? where id=?", "javaboy123", 1);

    }
    @Test
    public void delete() {
        jdbcTemplate.update("delete from user where id=?", 2);
    }

    @Test
    public void select() {
        User user = jdbcTemplate.queryForObject("select * from user where id=?", new BeanPropertyRowMapper<User>(User.class), 1);
        System.out.println(user);
    }
}

При запросе, если используется BeanPropertyRowMapper, требуется, чтобы найденные поля находились во взаимно однозначном соответствии с именами свойств бина. Если это не так, не используйте BeanPropertyRowMapper.В этом случае вам нужно настроить RowMapper или псевдоним для запрашиваемых полей.

  1. Псевдоним столбца запроса:
@Test
public void select2() {
    User user = jdbcTemplate.queryForObject("select id,username as name,address from user where id=?", new BeanPropertyRowMapper<User>(User.class), 1);
    System.out.println(user);
}

2. Пользовательский RowMapper

@Test
public void select3() {
    User user = jdbcTemplate.queryForObject("select * from user where id=?", new RowMapper<User>() {
        public User mapRow(ResultSet resultSet, int i) throws SQLException {
            int id = resultSet.getInt("id");
            String username = resultSet.getString("username");
            String address = resultSet.getString("address");
            User u = new User();
            u.setId(id);
            u.setName(username);
            u.setAddress(address);
            return u;
        }
    }, 1);
    System.out.println(user);
}

Запросить несколько записей следующим образом:

@Test
public void select4() {
    List<User> list = jdbcTemplate.query("select * from user", new BeanPropertyRowMapper<>(User.class));
    System.out.println(list);
}

6.3 XML-конфигурация

Вышеуказанная конфигурация также может быть реализована с помощью XML-файлов. Реализация через файл XML предоставляет только экземпляр JdbcTemplate, а оставшийся код по-прежнему является кодом Java, то есть JdbcConfig заменяется файлом XML.

<bean class="org.springframework.jdbc.datasource.DriverManagerDataSource" id="dataSource">
    <property name="username" value="root"/>
    <property name="password" value="123"/>
    <property name="url" value="jdbc:mysql:///test01?serverTimezone=Asia/Shanghai"/>
    <property name="driverClassName" value="com.mysql.cj.jdbc.Driver"/>
</bean>
<bean class="org.springframework.jdbc.core.JdbcTemplate" id="jdbcTemplate">
    <property name="dataSource" ref="dataSource"/>
</bean>

После завершения настройки загрузите файл конфигурации и запустите:

public class Main {

    private JdbcTemplate jdbcTemplate;

    @Before
    public void before() {
        ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
        jdbcTemplate = ctx.getBean(JdbcTemplate.class);
    }

    @Test
    public void insert() {
        jdbcTemplate.update("insert into user (username,address) values (?,?);", "javaboy", "www.javaboy.org");
    }
    @Test
    public void update() {
        jdbcTemplate.update("update user set username=? where id=?", "javaboy123", 1);

    }
    @Test
    public void delete() {
        jdbcTemplate.update("delete from user where id=?", 2);
    }

    @Test
    public void select() {
        User user = jdbcTemplate.queryForObject("select * from user where id=?", new BeanPropertyRowMapper<User>(User.class), 1);
        System.out.println(user);
    }
    @Test
    public void select4() {
        List<User> list = jdbcTemplate.query("select * from user", new BeanPropertyRowMapper<>(User.class));
        System.out.println(list);
    }

    @Test
    public void select2() {
        User user = jdbcTemplate.queryForObject("select id,username as name,address from user where id=?", new BeanPropertyRowMapper<User>(User.class), 1);
        System.out.println(user);
    }

    @Test
    public void select3() {
        User user = jdbcTemplate.queryForObject("select * from user where id=?", new RowMapper<User>() {
            public User mapRow(ResultSet resultSet, int i) throws SQLException {
                int id = resultSet.getInt("id");
                String username = resultSet.getString("username");
                String address = resultSet.getString("address");
                User u = new User();
                u.setId(id);
                u.setName(username);
                u.setAddress(address);
                return u;
            }
        }, 1);
        System.out.println(user);
    }

}

7. Транзакции

Транзакция в Spring в основном использует идею Aop для упрощения конфигурации транзакции, которую можно настроить с помощью Java или XML.

Готов к работе:

Давайте посмотрим на конфигурацию транзакции в Spring через операцию передачи.

Сначала подготовьте SQL:

CREATE DATABASE /*!32312 IF NOT EXISTS*/`test01` /*!40100 DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci */ /*!80016 DEFAULT ENCRYPTION='N' */;

USE `test01`;

/*Table structure for table `account` */

DROP TABLE IF EXISTS `account`;

CREATE TABLE `account` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `username` varchar(255) COLLATE utf8mb4_general_ci DEFAULT NULL,
  `money` int(11) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;

/*Data for the table `account` */

insert  into `account`(`id`,`username`,`money`) values (1,'zhangsan',1000),(2,'lisi',1000);

Затем настройте JdbcTemplate, конфигурация JdbcTemplate соответствует разделу 6.

Затем укажите метод для операции передачи:

@Repository
public class UserDao {
    @Autowired
    JdbcTemplate jdbcTemplate;

    public void addMoney(String username, Integer money) {
        jdbcTemplate.update("update account set money=money+? where username=?", money, username);
    }

    public void minMoney(String username, Integer money) {
        jdbcTemplate.update("update account set money=money-? where username=?", money, username);
    }
}
@Service
public class UserService {
    @Autowired
    UserDao userDao;
    public void updateMoney() {
        userDao.addMoney("zhangsan", 200);
        int i = 1 / 0;
        userDao.minMoney("lisi", 200);
    }
}

Наконец, в файле XML включите автоматическое сканирование:

<context:component-scan base-package="org.javaboy"/>
<bean class="org.springframework.jdbc.datasource.DriverManagerDataSource" id="dataSource">
    <property name="username" value="root"/>
    <property name="password" value="123"/>
    <property name="url" value="jdbc:mysql:///test01?serverTimezone=Asia/Shanghai"/>
    <property name="driverClassName" value="com.mysql.cj.jdbc.Driver"/>
</bean>
<bean class="org.springframework.jdbc.core.JdbcTemplate" id="jdbcTemplate">
    <property name="dataSource" ref="dataSource"/>
</bean>

7.1 XML-конфигурация

Настройка транзакций в XML делится на три шага:

1. Настройте диспетчер транзакций

<bean class="org.springframework.jdbc.datasource.DataSourceTransactionManager" id="transactionManager">
    <property name="dataSource" ref="dataSource"/>
</bean>

2. Настройте метод, который будет обрабатываться транзакцией.

<tx:advice id="txAdvice" transaction-manager="transactionManager">
    <tx:attributes>
        <tx:method name="update*"/>
        <tx:method name="insert*"/>
        <tx:method name="add*"/>
        <tx:method name="delete*"/>
    </tx:attributes>
</tx:advice>

Обратите внимание, что после настройки правил имени метода методы в службе должны следовать правилам имени здесь, иначе конфигурация транзакции не вступит в силу.

3. Настройте АОП

<aop:config>
    <aop:pointcut id="pc1" expression="execution(* org.javaboy.service.*.*(..))"/>
    <aop:advisor advice-ref="txAdvice" pointcut-ref="pc1"/>
</aop:config>

4. Тест

@Before
public void before() {
    ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
    jdbcTemplate = ctx.getBean(JdbcTemplate.class);
    userService = ctx.getBean(UserService.class);
}
@Test
public void test1() {
    userService.updateMoney();
}

7.2 Конфигурация Java

Если вы хотите включить конфигурацию аннотаций Java, добавьте следующую конфигурацию в конфигурацию XML:

<tx:annotation-driven transaction-manager="transactionManager" />

Эта строка конфигурации может заменить следующие две конфигурации:

<tx:advice id="txAdvice" transaction-manager="transactionManager">
    <tx:attributes>
        <tx:method name="update*"/>
        <tx:method name="insert*"/>
        <tx:method name="add*"/>
        <tx:method name="delete*"/>
    </tx:attributes>
</tx:advice>
<aop:config>
    <aop:pointcut id="pc1" expression="execution(* org.javaboy.service.*.*(..))"/>
    <aop:advisor advice-ref="txAdvice" pointcut-ref="pc1"/>
</aop:config>

Затем к методу, которому необходимо добавить транзакцию, добавьте аннотацию @Transactional, указывающую, что метод запускает транзакцию.Конечно, эту аннотацию можно разместить и на классе, указывающую, что все методы в этом классе запускают транзакцию.

@Service
public class UserService {
    @Autowired
    UserDao userDao;
    @Transactional
    public void updateMoney() {
        userDao.addMoney("zhangsan", 200);
        int i = 1 / 0;
        userDao.minMoney("lisi", 200);
    }
}

Обратите внимание на публичный аккаунт WeChat [Небольшой дождь в Цзяннане], ответьте на весну, получите электронную версию этой статьи или посетитеspring.javaboy.orgПроверьте эту электронную книгу.