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 Внедрение объекта
Существует три способа внедрения объектов при автоматическом сканировании:
- @Autowired
- @Resources
- @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 — это пустой интерфейс со множеством классов реализации:
Эти реализованные интерфейсы имеют некоторые общие черты:
- оба заканчиваются на Aware
- Оба унаследованы от Aware
- Метод 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
АОП (аспектно-ориентированное программирование), аспектно-ориентированное программирование, являющееся дополнением к объектно-ориентированному мышлению.
Аспектно-ориентированное программирование заключается в динамическом расширении функции метода без изменения исходного кода программы во время работы программы.Существует много распространенных сценариев использования:
- бревно
- дела
- операции с базой данных
- ....
Среди этих операций без исключения много шаблонного кода, а решение шаблонного кода и устранение наворотов — сильная сторона АОП.
В 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 или псевдоним для запрашиваемых полей.
- Псевдоним столбца запроса:
@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);
}
}