Spring
IoCと依存注入
IoCの主な考え方
IoCは制御の逆転です。従来のコードでは、オブジェクト自身が依存オブジェクトを作成します。Springでは、オブジェクトの作成、構成、依存関係はSpring IoCコンテナによって統合的に管理されます。
依存性注入はIoCの具体的な実装である。オブジェクトは必要な依存関係を宣言するだけで、コンテナはBeanを作成するときに対応するオブジェクトを注入します。
たとえば、DriverはVehicleに依存しています。new Car()を自分で実行する代わりに、ビジネスクラスは容器からCarを作成し、Driverを注入します。
public interface Vehicle {
void run();
}
public class Car implements Vehicle {
private String name;
public void setName(String name) {
this.name = name;
}
@Override
public void run() {
System.out.println(name + "正在行驶");
}
}
public class Driver {
private String name;
private Vehicle vehicle;
public void setName(String name) {
this.name = name;
}
public void setVehicle(Vehicle vehicle) {
this.vehicle = vehicle;
}
public void drive() {
System.out.println(name + "准备出发");
vehicle.run();
}
}Springプロジェクトの作成
Mavenプロジェクトの作成
pom.xmlにspring-contextを導入。次のリリースは、元のコースサンプル環境にのみ対応しています。
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.3.20</version>
</dependency>
コアプロファイルの作成
リソースディレクトリにapplicationContext.xmlを作成します。
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd">
</beans>
コンテナの開始
ApplicationContext context =
new ClassPathXmlApplicationContext("applicationContext.xml");
Driver driver = context.getBean("driver", Driver.class);
driver.drive();
XML
Beanを登録する
<bean id="driver" class="com.hyxy.Driver"/>
<bean id="car" class="com.hyxy.Car"/>
idはコンテナ内のBeanの名前で、classは実装クラスの完全修飾クラス名です。
セッター注入
<bean id="car" class="com.hyxy.Car">
<property name="name" value="小轿车"/>
</bean>
<bean id="driver" class="com.hyxy.Driver">
<property name="name" value="老张"/>
<property name="vehicle" ref="car"/>
</bean>
valueは単純な値の注入に使用され、refはコンテナ内の別のBeanを参照するために使用されます。
Setter 注入は、プロパティに対応するSetterメソッドの存在を要求します。Getterメソッドは注入を完了するために必要ではありませんが、通常はJava Bean 仕様で提供されています。
コンストラクター注入
public class Driver {
private final String name;
private final Vehicle vehicle;
public Driver(String name, Vehicle vehicle) {
this.name = name;
this.vehicle = vehicle;
}
}<bean id="driver" class="com.hyxy.Driver">
<constructor-arg index="0" value="老张"/>
<constructor-arg index="1" ref="car"/>
</bean>
パラメータはnameやtypeで指定することもできますが、コンストラクタのオーバーロードが多い場合は、indexや明示的なパラメータ名を使用する方が曖昧さを避けやすいです。
コンストラクタ注入は必要な依存関係に適しており、オブジェクトが作成されたときに利用可能になります。
Beanのスコープ
Spring Beanはデフォルトでシングルトンスコープです。つまり、同じBean 定義では通常、同じコンテナ内に1つのインスタンスのみが作成されます。
<bean id="driver" class="com.hyxy.Driver" scope="singleton"/>
プロトタイプスコープは、Beanを取得するたびに新しいオブジェクトを作成します。
<bean id="driver" class="com.hyxy.Driver" scope="prototype"/>
一般的なスコープは:
| スコープ | 意味 |
|---|---|
singleton | 各 Springコンテナには通常 1つのインスタンスしかありません。 |
prototype | 取得するたびに新しいインスタンスが作成されます。 |
request | HTTPリクエストごとに1つのインスタンス。 |
session | 各 HTTPセッションのインスタンス。 |
application | Webアプリケーションごとに1つのインスタンス。 |
WebスコープはWebアプリケーションコンテキストで使用する必要があります。
自動アセンブリ
XML 名前によるアセンブリ
<bean id="driver"
class="com.hyxy.Driver"
autowire="byName">
<property name="name" value="老张"/>
</bean>
<bean id="vehicle" class="com.hyxy.Car">
<property name="name" value="小轿车"/>
</bean>byNameは、プロパティー名に基づいて同じ名前のBeanを検索します。前述の例では、Driverの属性名をvehicleとしています。
XMLタイプ別アセンブリ{{XMLたいぷごとにアセンブリ}}
<bean id="driver"
class="com.hyxy.Driver"
autowire="byType">
<property name="name" value="老张"/>
</bean>
<bean id="car" class="com.hyxy.Car"/>
byTypeは、プロパティーのタイプに基づいてBeanを検索します。同じ型の候補が複数ある場合、コンテナは一意の依存関係を決定できず、通常例外をスローします。
XML 自動アセンブリは原理を学習するために使用できますが、実際のプロジェクトではアノテーションやJava 設定と組み合わせてコンストラクタ注入を使用することが多い。
循環依存関係
循環依存性とは、Bean 間の相互依存性の形成を指します。たとえば、AはBに依存し、BはAに依存します。
SpringはシングルトンBeanのセッターまたはフィールド注入ループ依存の一部を解決できますが、すべてのループ依存を解決する保証はありません。通常、次のような状況は自動的に解決できません。
- 建設者間の相互依存。
prototypeBean 間の循環依存性。- 一部のエージェント、初期化プロセス、またはカスタムライフサイクルロジックによって引き起こされる循環的な依存関係。
循環依存関係はしばしばクラス責任の不合理な分離を反映しており、コンテナのバックストップへの依存よりも依存関係の再設計によって解決される。
Beanのライフサイクル
XMLライフサイクル方法
<bean id="driver"
class="com.hyxy.Driver"
init-method="init"
destroy-method="destroy"/>
public class Driver {
public void init() {
System.out.println("初始化 Driver");
}
public void destroy() {
System.out.println("销毁 Driver");
}
}コンテナが閉じられると、シングルトンBeanの破棄コールバックが呼び出されます。SpringはプロトタイプBeanの破棄プロセスを自動的に管理しません。プロトタイプオブジェクトが使用された後にリソースをクリーンアップするのは通常、呼び出し元です。
注釈ライフサイクルアプローチ
Spring 5の一般的なプロジェクトではjavax.annotation、Spring 6とSpring Boot 3ではjakarta.annotationが使用されている。
@PostConstruct
public void init() {
System.out.println("初始化 Driver");
}
@PreDestroy
public void destroy() {
System.out.println("销毁 Driver");
}初期化プロセスの概要
シングルトンBeanの典型的な初期化プロセスは、次のように要約できます。
-
Beanのインスタンス化。
-
属性と依存性の注入。
-
関連する
Awareインタフェースを呼び出します。 -
BeanPostProcessorの前置処理を実行する. -
初期化コールバック
@PostConstruct、InitializingBean、カスタムinit-methodなどを実行します。 -
BeanPostProcessorの後処理を実行し、AOPプロキシは通常この段階で作成されます。 -
Beanが使用可能になります。
コンテナが閉じると、対応する破棄コールバックが実行されます。
BeanFactoryとFactoryBean
BeanFactory
BeanFactoryはSpring IoCコンテナのベースインターフェイスであり、Beanの作成、取得、依存性管理を担当します。ApplicationContextは、イベント、国際化、リソースロードなどの機能を追加しています。
FactoryBean
FactoryBean<T>自体はBeanですが、呼び出し元が通常のBean 名で取得するのは、生成するオブジェクトです。
public class SqlSessionFactoryBean implements FactoryBean<SqlSessionFactory> {
private String configLocation;
public void setConfigLocation(String configLocation) {
this.configLocation = configLocation;
}
@Override
public SqlSessionFactory getObject() throws Exception {
try (InputStream inputStream =
Resources.getResourceAsStream(configLocation)) {
return new SqlSessionFactoryBuilder().build(inputStream);
}
}
@Override
public Class<?> getObjectType() {
return SqlSessionFactory.class;
}
@Override
public boolean isSingleton() {
return true;
}
}<bean id="sqlSessionFactory"
class="com.hyxy.SqlSessionFactoryBean">
<property name="configLocation" value="mybatis-config.xml"/>
</bean>
sqlSessionFactoryを取得すると、FactoryBeanによって生成されたオブジェクトが取得されます。ファクトリBean 自体を取得するには、名前の前に&を付ける必要があります。
Beanの登録方法
SpringのBean 登録の一般的な方法には、XML、コンポーネントスキャン、Java 構成がある。
XML 登録#XMLレジストリ#
<bean id="car" class="com.hyxy.Car"/>
XMLは設定の集中表示に適しており、ソースコードを変更できないサードパーティ製クラスの登録にも便利ですが、設定量が大きいと面倒になります。
コールアウトとアセンブリスキャン
プロファイルでスキャンを開始します:
<context:component-scan base-package="com.hyxy"/>
コンポーネント注釈でクラスをマークするには:
@Component
public class Car {
}
一般的な階層注釈には、次のものがあります。
@Controller:制御層アセンブリ。@Service:ビジネス層コンポーネント。@Repository:Data Access Layerコンポーネント。@Component:共通コンポーネント。
これらの注釈はすべてSpring Beanとしてクラスを登録し、階層名は主に責任を表すために使用されます。注釈の一部はフレームワークの例外変換などのメカニズムにも関与することがあります。
依存性注入注釈
@Autowired
@Autowiredは主にタイプ別に依存関係を検索します。同じ型のBeanが複数存在する場合は、@Qualifierまたは@Primaryと組み合わせて曖昧さを解消できます。
コンストラクタ注入を推奨します。
@Service
public class DriverService {
private final Vehicle vehicle;
public DriverService(@Qualifier("car") Vehicle vehicle) {
this.vehicle = vehicle;
}
}
コンストラクタが1つしかない場合、コンストラクタ上の@Autowiredは通常省略できます。
@Qualifier
@Component("car")
public class Car implements Vehicle {
}
@Component("bus")
public class Bus implements Vehicle {
}
@Service
public class DriverService {
private final Vehicle vehicle;
public DriverService(@Qualifier("bus") Vehicle vehicle) {
this.vehicle = vehicle;
}
}
@Primary
@Component
@Primary
public class Car implements Vehicle {
}
同じタイプのBean 候補が複数ある場合、@Primaryタグ付きBeanがデフォルトの選択となります。
@Resource
@ResourceはJakartaまたはJava 標準アノテーションに属し、通常は名前でマッチし、次に型でマッチする。nameを使用してBean 名を明示的に指定できます。
@Resource(name = "car")
private Vehicle vehicle;
@Value
@Valueは単純な設定値またはSpring 式を注入できます。
@Value("${car.name:默认车辆}")
private String carName;
複雑な設定オブジェクトは、@ConfigurationPropertiesを使用するのに適しています。
@Scope
@Component
@Scope("prototype")
public class Driver {
}
Java 設定#Javaセッテイ#
構成クラスの定義
@Configuration
public class MyConfig {
@Bean
public Car car() {
Car car = new Car();
car.setName("小轿车");
return car;
}
@Bean
@Scope("prototype")
public Driver driver(Car car) {
Driver driver = new Driver();
driver.setName("老张");
driver.setVehicle(car);
return driver;
}
}@Configurationは、このクラスが構成クラスであることを示し、@Beanメソッドの戻りオブジェクトはコンテナに登録されます。メソッドパラメータはコンテナによって自動的に注入されます。
メソッド内で他の@Beanメソッドを直接呼び出すよりも、メソッドパラメータで依存関係を表現する方が明確でテストが簡単です。
Java 構成コンテナの開始
ApplicationContext context =
new AnnotationConfigApplicationContext(MyConfig.class);
Driver driver = context.getBean(Driver.class);
driver.drive();
プロキシモード
プロキシパターンは、ターゲットオブジェクト、プロキシオブジェクト、および両方が従う抽象契約を含む。プロキシオブジェクトは、ターゲットメソッドを呼び出す前後に、ログ、パーミッションチェック、トランザクション管理などの一般的なロジックを追加します。
スタティック·プロキシ
public interface HouseService {
void rentHouse();
}
public class Owner implements HouseService {
@Override
public void rentHouse() {
System.out.println("签合同并收款");
}
}
public class Agent implements HouseService {
private final HouseService target;
public Agent(HouseService target) {
this.target = target;
}
@Override
public void rentHouse() {
System.out.println("寻找房源并协商价格");
target.rentHouse();
}
}静的プロキシは、異なるターゲットタイプに対応するプロキシクラスを記述する必要があり、重複コードが多い。
JDKダイナミックプロキシ
JDK Dynamic Proxyは、インターフェイスに基づいてプロキシオブジェクトを作成します。
HouseService target = new Owner();
HouseService proxy = (HouseService) Proxy.newProxyInstance(
target.getClass().getClassLoader(),
target.getClass().getInterfaces(),
(proxyObject, method, args) -> {
System.out.println("调用前处理");
try {
return method.invoke(target, args);
} finally {
System.out.println("调用后处理");
}
}
);
proxy.rentHouse();CGLIBダイナミックプロキシ
CGLIBは、ターゲットクラスのサブクラスを生成することでエージェントを作成し、ターゲットクラスがインタフェースを実装する必要はありません。ターゲットクラスをfinalにすることはできません。また、エージェントを必要とするメソッドもfinalにすることはできません。
public class DaoMethodInterceptor implements MethodInterceptor {
@Override
public Object intercept(
Object proxy,
Method method,
Object[] args,
MethodProxy methodProxy) throws Throwable {
System.out.println("开启事务");
try {
Object result = methodProxy.invokeSuper(proxy, args);
System.out.println("提交事务");
return result;
} catch (Throwable e) {
System.out.println("回滚事务");
throw e;
}
}
}Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(EmpDao.class);
enhancer.setCallback(new DaoMethodInterceptor());
EmpDao empDao = (EmpDao) enhancer.create();
empDao.save();
古いClass.newInstance()を使用しないでください。反射によってオブジェクトを作成する必要がある場合は、getDeclaredConstructor().newInstance()を使用します。
AOP
AOPはアスペクト指向プログラミングであり、ログ、トランザクション、パーミッションなどのクロスカスロジックをビジネスコードから分離するために使用される。
よく使われる用語
- 接続ポイント:メソッド呼び出しなど、プログラム実行中に拡張できる場所。
- エントリポイント:強化が必要な接続ポイントをフィルタリングします。
- 通知ターゲットメソッドの前後に実行される拡張ロジック。
- 面カットポイントと通知の組み合わせ。
- ターゲット·オブジェクト拡張されたビジネス·オブジェクト。
- プロキシオブジェクトエンハンスメントロジックを適用した後のオブジェクト。
依存性の導入
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>5.3.20</version>
</dependency>
断面の定義
@Aspect
@Component
public class LogAspect {
@Pointcut("execution(* com.hyxy.service..*(..))")
public void serviceMethod() {
}
@Before("serviceMethod()")
public void before() {
System.out.println("执行方法前记录日志");
}
@AfterReturning(pointcut = "serviceMethod()", returning = "result")
public void afterReturning(Object result) {
System.out.println("方法正常返回:" + result);
}
@AfterThrowing(pointcut = "serviceMethod()", throwing = "exception")
public void afterThrowing(Throwable exception) {
System.out.println("方法发生异常:" + exception.getMessage());
}
@After("serviceMethod()")
public void after() {
System.out.println("方法结束");
}
}AOPの開始:
<aop:aspectj-autoproxy/>
@Afterはfinallyと同様に、メソッドが正常に戻るか例外を投げたかにかかわらず実行されます。@AfterReturningは正常に戻る場合にのみ実行されます。
Springトランザクションが概要{{Springとらんざくしょんがいよう}}
Springの宣言型トランザクションはAOPに基づく。一般的なトランザクション属性には、伝播動作、独立性レベル、タイムアウト、読み取り専用、ロールバック·ルールなどがあります。
一般的なコミュニケーション:
| 伝播 | 意味 |
|---|---|
REQUIRED | トランザクションがあれば参加し、トランザクションがなければ作成する。 |
I_NEW | 常に新しいトランザクションを作成し、現在のトランザクションを一時停止します。 |
SUPPORTS | トランザクションがあれば参加し、トランザクションがなければ非トランザクション実行する。 |
NESTED | セーブポイントをサポートする環境で、ネストされたトランザクションスコープを使用します。 |
@Service
public class UserService {
@Transactional(rollbackFor = Exception.class)
public void saveUser(User user) throws Exception {
// 数据库操作
}
}
デフォルトでは、SpringはRuntimeExceptionとErrorをロールバックし、通常のチェック例外を自動的にロールバックしません。チェックされた例外のロールバックが必要な場合は、rollbackForを設定してください。
SpringとMyBatisの統合
統合目標は、データソース、SqlSessionFactory、Mapperエージェント、トランザクションをSpringが管理することです。
Maven 依存関係
次のバージョンは、元のコースサンプルの組み合わせです。
<properties>
<maven.compiler.source>11</maven.compiler.source>
<maven.compiler.target>11</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.3.20</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>5.3.20</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-tx</artifactId>
<version>5.3.20</version>
</dependency>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.1</version>
</dependency>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
<version>2.0.7</version>
</dependency>
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
<version>8.0.33</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.2.23</version>
</dependency>
</dependencies>データベースの構成
jdbc.driverClassName=com.mysql.cj.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/java2601?serverTimezone=UTC&characterEncoding=utf8
jdbc.username=root
jdbc.password=root
jdbc.maxActive=300
jdbc.initialSize=2
jdbc.maxWait=60000
jdbc.minIdle=1
Springの構成
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:mybatis-spring="http://mybatis.org/schema/mybatis-spring"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
https://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/tx
https://www.springframework.org/schema/tx/spring-tx.xsd
http://mybatis.org/schema/mybatis-spring
http://mybatis.org/schema/mybatis-spring.xsd">
<context:property-placeholder location="classpath:db.properties"/>
<context:component-scan base-package="com.hyxy"/>
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
<property name="driverClassName" value="${jdbc.driverClassName}"/>
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
<property name="maxActive" value="${jdbc.maxActive}"/>
<property name="initialSize" value="${jdbc.initialSize}"/>
<property name="maxWait" value="${jdbc.maxWait}"/>
<property name="minIdle" value="${jdbc.minIdle}"/>
</bean>
<bean id="sqlSessionFactory"
class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="dataSource"/>
<property name="mapperLocations"
value="classpath*:com/hyxy/mapper/*.xml"/>
<property name="typeAliasesPackage" value="com.hyxy.po"/>
</bean>
<mybatis-spring:scan base-package="com.hyxy.mapper"/>
<bean id="transactionManager"
class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
<tx:annotation-driven transaction-manager="transactionManager"/>
</beans>SqlSessionFactoryBeanはMyBatisのSqlSessionFactoryの作成を担当し、Mapperスキャナはインターフェイスのプロキシを作成し、Springコンテナに登録します。
マッパーとサービス
public interface UserMapper {
int insert(User user);
}
@Service
public class UserService {
private final UserMapper userMapper;
public UserService(UserMapper userMapper) {
this.userMapper = userMapper;
}
@Transactional(rollbackFor = Exception.class)
public void save(User user) throws Exception {
userMapper.insert(user);
}
}ビジネス層はMapperを注入するだけで、SqlSessionを自分で作成する必要はありません。トランザクションがSpringで管理されている場合は、MapperやServiceでcommit()を手動で呼び出すべきではありません。
気に入ったならばコメントを残してくださいね~