Spring
# 1. 快速入门
# 1.1 环境搭建
Spring
依赖
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.3.14</version>
</dependency>
Spring
的配置文件
- 配置文件的位置没有硬性要求
- 配置文件的命名:建议
applicationContext.xml
# 1.2 核心API
ApplicationContext
(接口):用于对象的创建 (重量级资源,一个应用只会创建一个对象)ClassPathXmlApplicationContext
:用于非web
环境XmlWebApplicationContext
:用于web
环境(需要spring-webmvc
依赖)
# 1.3 程序开发
# 1.3.1 案例展示
当前项目目录
在
applicationContext.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 id="person" class="com.meinil.demo.Person" /> </beans>
在
Main
中获取对象public class Main { public static void main(String[] args) { ApplicationContext ctx = new ClassPathXmlApplicationContext("/applicationContext.xml"); Person p1 = (Person) ctx.getBean("person"); // 返回Object需要强转 Person p2 = ctx.getBean("person", Person.class); // 可以直接传入类型 Person p3 = ctx.getBean(Person.class); // 如果采用此方式,Spring的配置文件中只能有一个Person的实例 System.out.println(p1); System.out.println(p2); System.out.println(p3); } }
# 1.3.2 其他API
获取配置文件中所有
bean
的id
String[] names = ctx.getBeanDefinitionNames(); for (String name : names) { System.out.println(name); }
获取
Person
类型的所有bean
的id
String[] names = ctx.getBeanNamesForType(Person.class); for (String name : names) { System.out.println(name); }
判断某
id
值的bean
是否存在boolean exist = ctx.containsBeanDefinition("person"); System.out.println(exist);
与上述大概相同,但支持
name
属性boolean exist = ctx.containsBean("person"); System.out.println(exist);
# 1.3.3 配置文件
仅配置
class
属性<bean class="com.meinil.demo.Person" />
对于不配置
id
值的bean
,spring
同样可以获取到,id
默认值为com.meinil.demo.Person#0
public class Main { public static void main(String[] args) { ApplicationContext ctx = new ClassPathXmlApplicationContext("/applicationContext.xml"); Person person = ctx.getBean(Person.class); System.out.println(person); String[] names = ctx.getBeanDefinitionNames(); for (String name : names) { System.out.println(name); } } }
应用场景:如果这个
bean
只需要使用一次,那么就可以省略id
值。如果这个bean
会使用多次,或者被其他bean
引用则需要设置id
值name
属性配置别名<bean id="person" name="p" class="com.meinil.demo.Person" />
public class Main { public static void main(String[] args) { ApplicationContext ctx = new ClassPathXmlApplicationContext("/applicationContext.xml"); Person person = ctx.getBean("p", Person.class); System.out.println(person); } }
区别:
name
可以定义多个,id
只能定义一个。<bean name="p1, p2" class="com.meinil.demo.Person" />
# 1.3.4 其他细节
Spring
工厂可以调用对象私有的构造方法创建对象
Spring
管理的对象一般不包括实体类,因为实体类的数据是需要从数据库进行读取的,所以应该交给DAO
层进行管理
# 2. Spring日志
Spring5.x
默认日志框架是logback
、log4j2
引入相关依赖
<dependency> <groupId>org.apache.logging.log4j</groupId> <artifactId>log4j-core</artifactId> <version>2.17.0</version> </dependency> <dependency> <groupId>org.apache.logging.log4j</groupId> <artifactId>log4j-api</artifactId> <version>2.17.0</version> </dependency> <dependency> <groupId>org.apache.logging.log4j</groupId> <artifactId>log4j-slf4j-impl</artifactId> <version>2.17.0</version> </dependency> <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-api</artifactId> <version>1.7.32</version> </dependency>
新建
log4j2.xml
<?xml version="1.0" encoding="UTF-8"?> <!--设置日志级别 OFF > FATAL > ERROR > WARN > INFO > DEBUG > TRACE > ALL--> <configuration status="DEBUG"> <!--先定义所有的appender --> <appenders> <!--输出到控制台 --> <Console name="Console" target="SYSTEM_OUT"> <!--输出日志的格式 --> <PatternLayout pattern="%d{HH:mm:ss.SSS} %-5level %class{36} %L %M - %msg%xEx%n"/> </Console> </appenders> <!--然后定义logger,只有定义了logger并引入的appender,appender才会生效 --> <loggers> <root level="DEBUG"> <appender-ref ref="Console"/> </root> </loggers> </configuration>
测试
public class Main { private static final Logger log = LoggerFactory.getLogger(Main.class); public static void main(String[] args) { log.info("日志测试"); } }
# 3. set注入
注入:通过Spring
工厂及配置文件,为所创建对象的成员变量赋值
改造person
public class Person {
private Integer id;
private String name;
// get set方法
}
# 3.1 为什么需要注入
通过编码的方式,为成员变量进行赋值,存在耦合
public class Main {
public static void main(String[] args) {
ApplicationContext ctx = new ClassPathXmlApplicationContext("/applicationContext.xml");
Person person = ctx.getBean("person", Person.class);
person.setId(1); // 赋值语句存在耦合
person.setName("小明");
System.out.println(person);
}
}
# 3.2 开发步骤
为类成员变量提供
get
、set
方法在
applicationContext.xml
文件中配置bean
实例<bean id="person" class="com.meinil.demo.Person"> <property name="id" value="1" /> <property name="name" value="小明" /> </bean>
调用测试
public class Main { public static void main(String[] args) { ApplicationContext ctx = new ClassPathXmlApplicationContext("/applicationContext.xml"); Person person = ctx.getBean("person", Person.class); System.out.println(person); } }
Spring
的注入是通过对象的set
方法实现,所以对象的属性必须提供set
方法
# 3.3 注入详解
set
注入的属性不仅仅只是Integer
、String
类型,还可以有以下类型
当前项目结构
# 3.3.1 内置类型
要操作的类
public class Person {
private Integer id;
private String name;
private String[] emails;
private Set<String> tels;
private List<String> addresses;
private Map<String, String> qqs;
private Properties teachers;
// get set
}
String
+8种基本类型直接使用value
标签即可
<property name="id">
<value>18</value>
</property>
<property name="name">
<value>小明</value>
</property>
数组类型
<property name="emails">
<array>
<value>yuunn@163.com</value>
<value>taotao@gmail.com</value>
<value>567723234@qq.com</value>
</array>
</property>
Set
集合
<property name="tels">
<set>
<value>1223452342</value>
<value>1234343335</value>
<value>6786782342</value>
</set>
</property>
List
<property name="addresses">
<list>
<value>北京</value>
<value>上海</value>
<value>广东</value>
</list>
</property>
map
<property name="qqs">
<map>
<entry>
<key>
<value>张某</value>
</key>
<value>4566344</value>
</entry>
<entry>
<key>
<value>徐某</value>
</key>
<value>4564564</value>
</entry>
</map>
</property>
Properties
特殊类型的Map
,键和值都只能是String
<property name="teachers">
<props>
<prop key="生物">张老师</prop>
<prop key="化学">李老师</prop>
</props>
</property>
# 3.3.2 自定义类型
要操作的类
// Person.java
public class Person {
private Student stu;
// get set
}
// Student.java
public class Student {
private String name;
private Integer age;
// get set
}
配置applicationContext.xml
文件
<bean id="person" class="com.meinil.demo.Person">
<property name="stu">
<bean class="com.meinil.demo.Student">
<property name="age" value="18" />
<property name="name" value="小王吧" />
</bean>
</property>
</bean>
也可以使用引用的方式
<bean id="student" class="com.meinil.demo.Student">
<property name="name">
<value>大王</value>
</property>
<property name="age">
<value>12</value>
</property>
</bean>
<bean id="person" class="com.meinil.demo.Person">
<property name="stu">
<ref bean="student" />
</property>
</bean>
# 3.3.3 简化写法
属性简化
value
以及ref
标签可以简化为属性写法
<bean id="student" class="com.meinil.demo.Student">
<property name="name" value="大王"/>
<property name="age" value="12"/>
</bean>
<bean id="person" class="com.meinil.demo.Person">
<property name="stu" ref="student"/>
</bean>
命名空间简化
<bean id="student" class="com.meinil.demo.Student" p:name="大王" p:age="12" />
<bean id="person" class="com.meinil.demo.Person" p:stu-ref="student" />
实际上这个p
指的就是property
# 4. 构造注入
构造注入:Spring
通过调用构造方法为成员变量赋值
# 4.1 开发步骤
提供有参构造方法
public class Customer { private String name; private Integer age; public Customer(String name, Integer age) { this.name = name; this.age = age; } }
Spring
配置文件进行配置<bean id="customer" class="com.meinil.demo.Customer"> <constructor-arg> <value>石昊</value> </constructor-arg> <constructor-arg> <value>5</value> </constructor-arg> </bean>
# 4.2 构造方法重载
public class Customer {
private String name;
private Integer age;
public Customer(String name, Integer age) {
this.name = name;
this.age = age;
}
public Customer(String name) {
this.name = name;
}
public Customer(Integer age) {
this.age = age;
}
}
在配置Spring
配置文件时,填写相应的参数并指定name
即可,
<bean id="customer" class="com.meinil.demo.Customer">
<constructor-arg name="name">
<value>石昊</value>
</constructor-arg>
</bean>
也可以通过指定参数类型来指定构造函数
<bean id="customer" class="com.meinil.demo.Customer">
<constructor-arg type="java.lang.Integer">
<value>3</value>
</constructor-arg>
</bean>
# 5. 依赖注入
# 5.1 反转控制
反转控制IOC Inverse of Control
:即对变量进行赋值的权利,不再由代码控制,而是交给Spring
进行管理(即转移对象赋值的控制权)。
优势:解耦合
底层实现:工厂设计模式
# 5.2 依赖注入
依赖注入Dependency Injection
:当一个类需要另一个类时,就意味着依赖,一旦出现依赖,就可以把另一个类作为本类的成员变量,最终通过Spring
配置文件进行注入(赋值)
优势:解耦合
# 6. 复杂对象
简单对象:可以直接通过new
创建出来的对象
复杂对象:不能直接通过new
的方式创建的对象
# 6.1 FactoryBean
# 6.1.2 开发步骤
实现
FactoryBean
接口public class MyFactoryBean implements FactoryBean<Connection> { // 用于书写创建复杂对象的代码并把复杂对象作为方法的返回值返回 @Override public Connection getObject() throws Exception { Class.forName("com.mysql.cj.jdbc.Driver"); Connection conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/test", "root", "123456"); return conn; } // 返回所创建复杂对象的Class独享 @Override public Class<?> getObjectType() { return Connection.class; } // 对象是否单例 @Override public boolean isSingleton() { return false; } }
编写
Spring
的配置文件<bean id="conn" class="com.meinil.demo.MyFactoryBean" />
如果
class
指定的类时FactoryBean
的实现类,则通过id
获取的对象不是此类,而是此类创建的类测试
public class Main { public static void main(String[] args) throws SQLException { ApplicationContext ctx = new ClassPathXmlApplicationContext("/applicationContext.xml"); Connection conn = ctx.getBean("conn", Connection.class); System.out.println(conn); conn.close(); } }
# 6.1.3 注意事项
如果想要获取
FactoryBean
对象,则需要在id
前添加&
即可public class Main { public static void main(String[] args) throws SQLException { ApplicationContext ctx = new ClassPathXmlApplicationContext("/applicationContext.xml"); MyFactoryBean conn = ctx.getBean("&conn", MyFactoryBean.class); System.out.println(conn); } }
isSingleton
用于控制对象是否单例连接对象一般不建议单例,
Mybatis
的SqlSessionFactory
可以单例改进
MyFactoryBean.java
public class MyFactoryBean implements FactoryBean<Connection> { private String driveClassName; private String url; private String user; private String password; // get set // 用于书写创建复杂对象的代码并把复杂对象作为方法的返回值返回 @Override public Connection getObject() throws Exception { Class.forName(driveClassName); Connection conn = DriverManager.getConnection(url, user, password); return conn; } // 返回所创建复杂对象的Class独享 @Override public Class<?> getObjectType() { return Connection.class; } // 对象是否单例 @Override public boolean isSingleton() { return false; } }
配置文件
<bean id="conn" class="com.meinil.demo.MyFactoryBean"> <property name="driveClassName" value="com.mysql.cj.jdbc.Driver" /> <property name="url" value="jdbc:mysql://192.168.0.103/test" /> <property name="user" value="root" /> <property name="password" value="123456" /> </bean>
# 6.2 实例工厂
应用场景:避免对Spring
框架的依赖、整合遗留系统
假设现在有一个遗留的系统,它已经提供了ConnectionFactory
,我们需要将这个类集成到Spring
中
public class ConnectionFactory {
public Connection getConnection() {
Connection conn = null;
try {
Class.forName("com.mysql.cj.jdbc.Driver");
conn = DriverManager.getConnection("jdbc:mysql://192.168.0.103/test", "root", "123456");
} catch (SQLException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
return conn;
}
}
只需要调整配置文件即可,factory-bean
指定工厂对象,factory-method
指定工厂方法
<bean id="connFactory" class="com.meinil.demo.ConnectionFactory" />
<bean id="conn" factory-bean="connFactory" factory-method="getConnection" />
测试
public class Main {
public static void main(String[] args) throws SQLException {
ApplicationContext ctx = new ClassPathXmlApplicationContext("/applicationContext.xml");
Connection conn = ctx.getBean("conn", Connection.class);
System.out.println(conn);
conn.close();
}
}
# 6.3 静态工厂
静态工厂与实例工厂的区别在于,获取工厂对象的方法是静态的
静态工厂类
public class StaticConnectionFactory {
public static Connection getConnection() {
Connection conn = null;
try {
Class.forName("com.mysql.cj.jdbc.Driver");
conn = DriverManager.getConnection("jdbc:mysql://192.168.0.103/test", "root", "123456");
} catch (SQLException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
return conn;
}
}
修改配置文件
<bean id="conn" class="com.meinil.demo.StaticConnectionFactory" factory-method="getConnection" />
# 7. 对象的创建次数
# 7.1 简单对象
假设有如下类
public class Account {
}
配置文件内容如下
<bean id="account" class="com.meinil.demo.Account" />
可以在bean
标签内部指定属性scope
来管理对象创建的次数
<bean id="account" scope="prototype" class="com.meinil.demo.Account" />
scope
的取值有两个:singleton
单例模式(默认)、prototype
原型模式,创建次数无要求
# 7.2 复杂对象
在实现FactoryBean
的类中,将isSingleton
的返回值设置为true
即为单例模式,反之创建次数无要求
# 8. 生命周期
生命周期:一个对象的创建、存活、消亡的过程
Spring
管理的bean
实例的三个重要阶段:
# 8.1 创建阶段
创建阶段:Spring
工厂何时创建对象
singleton
模式下,Spring
工厂创建的同时,对象也会创建,也可以在bean
标签中加入属性lazy-init="true"
使其在获取时初始化
prototype
模式下,Spring
工厂会在获取对象的同时,创建对象
# 8.2 初始化阶段
初始化阶段:一般用于初始化数据库、IO
、网络等资源的初始化
Spring
工厂在创建万对象后,调用对象的初始化方法,完成对应的初始化操作
初始化方法由编码者提供,初始化方法由Spring
工厂调用
实现
InitializingBean
接口public class Account implements InitializingBean { public Account() { System.out.println("创建"); } @Override public void afterPropertiesSet() throws Exception { System.out.println("初始化操作"); } }
提供普通方法,不需要实现
InitializingBean
接口public class Account { public Account() { System.out.println("创建"); } // 方法名任意 public void init() { System.out.println("自定义初始化方法"); } }
修改配置文件
<bean id="account" class="com.meinil.demo.Account" init-method="init"/>
如果一个bean
同时实现了上述的两种方法,则会先执行接口的初始化方法,再执行用户提供的方法
如果配置文件中配置了set
注入,则初始化方法在set
注入之后执行
# 8.3 销毁阶段
销毁阶段:Spring
销毁对象前,会调用对象的销毁方法,完成销毁工作
Spring
的bean
实例会在Spring
工厂关闭时销毁(调用close
方法)
销毁方法由用户提供,由Spring
调用
实现
DisposableBean
接口public class Account implements DisposableBean { @Override public void destroy() throws Exception { System.out.println("销毁方法"); } }
测试
public class Main { public static void main(String[] args) { ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("/applicationContext.xml"); Account account = ctx.getBean("account", Account.class); System.out.println(account); ctx.close(); // 必须调用close方法 } }
自定义方法
public class Account { public void myDestroy() throws Exception { System.out.println("自定义销毁方法"); } }
配置文件中需要指定销毁方法
<bean id="account" class="com.meinil.demo.Account" destroy-method="myDestroy"/>
⭐️注意:销毁方法的操作只适用于scope="singleton"
的bean
销毁操作:一般用于资源的释放
# 9. 配置文件
把Spring
配置文件中需要经常修改的字符串信息,转移到一个更小的配置文件中
项目目录
MyFactoryBean.java
public class MyFactoryBean implements FactoryBean<Connection> {
private String driveClassName;
private String url;
private String username;
private String password;
// get set
@Override
public Connection getObject() throws Exception {
Class.forName(driveClassName);
Connection conn = DriverManager.getConnection(url, username, password);
return conn;
}
@Override
public Class<?> getObjectType() {
return Connection.class;
}
@Override
public boolean isSingleton() {
return false;
}
}
applicationContext.xml
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:p="http://www.springframework.org/schema/p"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">
<!-- 导入配置文件 -->
<context:property-placeholder location="classpath:/db.properties" />
<bean id="conn" class="com.meinil.demo.MyFactoryBean">
<property name="driveClassName" value="${jdbc.driverClassName}" />
<property name="url" value="${jdbc.url}" />
<property name="username" value="${jdbc.username}" />
<property name="password" value="${jdbc.password}" />
</bean>
</beans>
db.properties
jdbc.driverClassName=com.mysql.cj.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/test?useSSL=false
jdbc.username=root
jdbc.password=123456
Main
测试
public class Main {
public static void main(String[] args) throws SQLException {
ApplicationContext ctx = new ClassPathXmlApplicationContext("/applicationContext.xml");
Connection conn = ctx.getBean("conn", Connection.class);
System.out.println(conn);
conn.close();
}
}
# 10. 类型转换器
类型转换器:Spring
通过类型转换器把配置文件中字符串类型的数据,转换成了对象中成员变量对应类型的数据,进而完成了注入
对于某些特定类型(例如:Date
),Spring
并未提供类型转换器,需要用户自定义类型转换器
现在有如下类
public class Teacher {
private String name;
private Date birthday;
// get set
}
创建自定义的类型转换器
public class DateConverter implements Converter<String, Date> { @Override public Date convert(String source) { SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd"); try { return sdf.parse(source); } catch (ParseException e) { e.printStackTrace(); } return null; } }
注册类型转换器
<!--创建自定义的转换器对象--> <bean id="dateConverter" class="com.meinil.demo.DateConverter" /> <!--创建用于注册转换器的对象--> <bean id="conversionService" class="org.springframework.context.support.ConversionServiceFactoryBean"> <property name="converters"> <set> <!--注册转换器--> <ref bean="dateConverter" /> </set> </property> </bean>
使用
<bean id="teacher" class="com.meinil.demo.Teacher"> <property name="name" value="小明" /> <property name="birthday" value="2022-01-19" /> </bean>
⭐️对于ConversionServiceFactoryBean
的实例对象,id
只能是conversionService
Spring
内置的日期类型转换器格式为
2022/01/19
# 11. 后置处理Bean
# 11.1 简介
BeanPostProcessor
:对Spring
工厂所创建的对象,进行再加工
BeanPostProcessor
:是一个接口,它定义了两个方法
public interface BeanPostProcessor {
// 在初始化方法之前调用
@Nullable
default Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
return bean;
}
// 在初始化方法之后调用
@Nullable
default Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
return bean;
}
}
# 11.2 实例展示
待加工的类
public class Category { private Integer id; private String name; // get set }
类实现
BeanPostProcessor
public class CategoryBeanPostProcessor implements BeanPostProcessor { @Override public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException { return bean; } @Override public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException { if (bean instanceof Category) { Category category = (Category) bean; category.setName("修改后的名字"); } return bean; } }
Spring
配置文件中进行配置<bean id="categoryBeanPostProcessor" class="com.meinil.demo.CategoryBeanPostProcessor" /> <bean id="category" class="com.meinil.demo.Category"> <property name="id" value="100" /> <property name="name" value="李华" /> </bean>
⭐️BeanPostProcessor
会对Spring
中所有的对象进行加工,需要进行判断后再进行加工
# 12. AOP
# 12.1 代理设计模式
通过代理类,为原始类增加额外的功能。优势:利于原始类的维护
# 12.1.1 静态代理
代理类和原始类需要实现相同的接口
User.java
public class User {
}
UserService.java
public interface UserService {
public void register(User user);
public boolean login(String name, String password);
}
UserServiceImpl.java
public class UserServiceImpl implements UserService{
@Override
public void register(User user) {
System.out.println("UserServiceImpl: register方法执行");
}
@Override
public boolean login(String name, String password) {
System.out.println("UserServiceImpl: login方法执行");
return true;
}
}
UserServiceIProxy.java
public class UserServiceProxy implements UserService{
private UserService userService = new UserServiceImpl(); // 注入原始对象
@Override
public void register(User user) {
System.out.println("UserServiceProxy: register方法执行");
userService.register(user);
}
@Override
public boolean login(String name, String password) {
System.out.println("UserServiceProxy: login方法执行");
return userService.login(name, password);
}
}
Main.java
用于测试
public class Main {
public static void main(String[] args) {
UserService userService = new UserServiceProxy();
userService.register(new User());
userService.login("小明", "1234");
}
}
# 12.1.2 Spring动态代理
依赖
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
<version>5.3.14</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjrt</artifactId>
<version>1.9.7</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.7</version>
</dependency>
项目结构
User.java
public class User {
}
UserService.java
public interface UserService {
public void register(User user);
public boolean login(String name, String password);
}
UserServiceImpl.java
public class UserServiceImpl implements UserService{
@Override
public void register(User user) {
System.out.println("UserServiceImpl: register方法执行");
}
@Override
public boolean login(String name, String password) {
System.out.println("UserServiceImpl: login方法执行");
return true;
}
}
定义一个类并实现MethodBeforeAdivice
接口
public class Before implements MethodBeforeAdvice {
// 在执行原始方法之前执行
@Override
public void before(Method method, Object[] args, Object target) throws Throwable {
System.out.println("--Method Before Advice");
}
}
配置文件
<bean id="before" class="com.meinil.proxy.service.Before" />
<bean id="userService" class="com.meinil.proxy.service.impl.UserServiceImpl" />
配置文件切入点(额外功能加入的位置(方法)):以下定义含义为所有方法都为切入点
<aop:config>
<aop:pointcut id="pc" expression="execution(* *(..))"/>
<aop:advisor advice-ref="before" pointcut-ref="pc" />
</aop:config>
测试
public class Main {
public static void main(String[] args) {
ApplicationContext ctx = new ClassPathXmlApplicationContext("/applicationContext.xml");
UserService userService = ctx.getBean("userService", UserService.class);
userService.register(new User());
userService.login("小明", "1234");
}
}
# 12.2 动态代理详解
# 12.2.1 额外功能
MethodBeforeAdvice
接口:这个接口的额外功能是在原始方法执行之前,进行额外功能操作public class Before implements MethodBeforeAdvice { @Override public void before(Method method, Object[] args, Object target) throws Throwable { System.out.println("--Method Before Advice"); } }
Method
:指额外功能附加给的那个原始方法Object[]
:原始方法的参数Object
:原始方法MethodInterceptor
接口:方法拦截器,可以在原始方法执行之前和之后都执行一些操作⭐️新建
Around.java
public class Around implements MethodInterceptor { // MethodInvocation: 封装了原始方法 // invoke的返回值: 表示原始方法的返回值 @Override public Object invoke(MethodInvocation invocation) throws Throwable { System.out.println("原始方法执行之前"); Object ret = invocation.proceed();// 执行原始方法 System.out.println("原始方法执行之后"); return ret; } }
修改配置文件
<bean id="around" class="com.meinil.proxy.service.Around" /> <aop:config> <aop:pointcut id="pc" expression="execution(* *(..))"/> <aop:advisor advice-ref="around" pointcut-ref="pc" /> </aop:config>
应用场景:事务开启提交,性能统计...
⭐️
Object ret = invocation.proceed()
同样可以添加try..catch
,用于异常处理
# 12.2.2 切入点
切入点:决定额外功能加入的位置(方法)
<aop:pointcut id="pc" expression="execution(* *(..))"/>
execution
:切入点函数。(* *(..))
:切入点表达式
第一个*
:修饰符(方法的权限)
第二个*
:方法名
(..)
:参数列表
方法切入点表达式
定义
login
方法为切入点(* login(..))
定义
login(String, String)
为切入点(* login(String, String))
如果参数是自定义的类(非
java.lang
下的类),需要类的全类名支持可变参数
(* login(String, ..))
精准限制方法切入点
(* com.meinil.proxy.service.impl.UserServiceImpl.login(String, ..))
类切入点
考虑包的情况
(* com.meinil.proxy.service.impl.UserServiceImpl.*(String, ..))
不考虑包
<aop:pointcut id="pc" expression="execution(* *.*.*.*.*.UserServiceImpl.*(..))"/>
这种情况一个
*
仅能代表一层包
包切入点
仅代表当前包
<aop:pointcut id="pc" expression="execution(* com.meinil.proxy.service.*.*(..))"/>
当前包及其子包
<aop:pointcut id="pc" expression="execution(* com.meinil.proxy.service..*.*(..))"/>
# 12.2.3 切入点函数
切入点函数:用于执行切入点表达式
execution
execution
:最重要的切入点函数,功能最完整。支持方法、类、包切入点表达式args
:主要用于函数(方法)参数的匹配<aop:pointcut id="pc" expression="execution(* *(String, String))"/> 等价 <aop:pointcut id="pc" expression="args(String, String)"/>
within
:用于进行类、包切入点表达式的匹配execution(* *..UserServiceImpl.*(..)) within(*..UserServiceImpl)
@annotation
:为具有特殊注解的方法或者类提供额外功能首先定义注解
@Target(ElementType.METHOD) // 用在哪儿 @Retention(RetentionPolicy.RUNTIME) // 什么时候起作用 public @interface Log { }
在想要执行的方法上添加此注解
public class UserServiceImpl implements UserService { @Log @Override public void register(User user) { System.out.println("UserServiceImpl: register方法执行"); } @Log @Override public boolean login(String name, String password) { System.out.println("UserServiceImpl: login方法执行"); return true; } }
配置文件
<aop:pointcut id="pc" expression="@annotation(com.meinil.proxy.service.Log)"/>
切入点函数逻辑运算:多个切入点函数一起配合工作,进而完成更为复杂的需求
and
与操作:与操作不能使用同类型的切入点函数比如(execution and execution
)<aop:pointcut id="pc" expression="execution(* login(..)) and args(String, String)"/>
or
或操作<aop:pointcut id="pc" expression="execution(* login(..)) or execution(* register(..))"/>
# 12.3 AOP编程
AOP(Aspect Oriented Programing)
:面向切面编程
# 12.3.1 JDK动态代理
Main.java
public class Main {
public static void main(String[] args) {
// 1. 创建原始对象
final UserService userService = new UserServiceImpl();
// 2. 创建JDK动态代理
UserService userServiceProxy = (UserService)Proxy.newProxyInstance(userService.getClass().getClassLoader(), userService.getClass().getInterfaces(), new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("原始方法执行之前");
Object ret = method.invoke(userService, args);
System.out.println("原始方法执行之后");
return ret;
}
});
userServiceProxy.register(new User());
userServiceProxy.login("小明", "123456");
}
}
Proxy.newProxyInstance
的几个参数
classLoader
:Proxy.newProxyInstance
采用动态字节码技术生成代理对象,JVM
不会主动去加载它,需要借助别的类加载器进行加载interfaces
:被代理对象的接口,因为代理对象需要与被代理对象实现相同的接口invocationHandler
:需要代理对象实现的功能
# 12.3.2 CGlib动态代理
JDK
动态代理要求,原始类实现一定的接口,并且JDK
动态代理返回的代理类也会实现这些接口
CGlib
对于原始类实现的接口没有要求,它要返回的代理类是继承自原始类
public class Main {
public static void main(final String[] args) {
final UserServiceImpl userService = new UserServiceImpl();
Enhancer enhancer = new Enhancer();
enhancer.setClassLoader(userService.getClass().getClassLoader()); // 设置classLoader,不设置有默认值
enhancer.setSuperclass(userService.getClass()); // 设置父类
enhancer.setCallback(new MethodInterceptor() { // 设置增强功能
@Override
public Object intercept(Object o, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
System.out.println("原始方法执行之前");
Object ret = method.invoke(userService, args);
System.out.println("原始方法执行之后");
return ret;
}
});
UserServiceImpl userServiceProxy = (UserServiceImpl)enhancer.create();
userServiceProxy.register(new User());
userServiceProxy.login("小明", "123");
}
}
# 12.3.3 使用Spring
代理对象的创建一般在BeanPostProcessor
的after
方法中进行
项目结构
ProxyBeanPostProxy.java
:其他类与上述相同
public class ProxyBeanPostProxy implements BeanPostProcessor {
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
return bean;
}
@Override
public Object postProcessAfterInitialization(final Object bean, String beanName) throws BeansException {
if (bean instanceof UserService) {
return Proxy.newProxyInstance(this.getClass().getClassLoader(), bean.getClass().getInterfaces(), new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("原始方法执行之前");
Object ret = method.invoke(bean, args);
System.out.println("原始方法执行之后");
return ret;
}
});
}
return bean;
}
}
配置文件
<bean id="proxyBeanPostProcessor" class="com.meinil.proxy.service.factory.ProxyBeanPostProxy" />
<bean id="userService" class="com.meinil.service.impl.UserServiceImpl" />
测试
public class Main {
public static void main(String[] args) {
ApplicationContext ctx = new ClassPathXmlApplicationContext("/applicationContext.xml");
UserService userService = ctx.getBean("userService", UserService.class);
userService.register(new User());
boolean isLogin = userService.login("小明", "123456");
System.out.println(isLogin);
}
}
# 12.4 注解开发
# 12.4.1 实例
MyAspect.java
@Aspect
public class MyAspect {
@Around("execution(* *(..))")
public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
// joinPoint 原始方法
System.out.println("注解开发之前");
Object ret = joinPoint.proceed();
System.out.println("注解开发之后");
return ret;
}
}
切面的方法名任意,ProceedingJoinPoint
为原始方法
配置文件
<!--开启AOP注解开发-->
<aop:aspectj-autoproxy />
<bean id="around" class="com.meinil.aspect.aspect.MyAspect" />
<bean id="userService" class="com.meinil.aspect.service.impl.UserServiceImpl" />
# 12.4.2 注意细节
@Pointcut
:实现切入点表达式的复用@Aspect public class MyAspect { @Pointcut("execution(* *(..))") public void myPointcut() {} @Around(value = "myPointcut()") public Object around1(ProceedingJoinPoint joinPoint) throws Throwable { // joinPoint 原始方法 System.out.println("注解开发之前1"); Object ret = joinPoint.proceed(); System.out.println("注解开发之后1"); return ret; } @Around(value = "myPointcut()") public Object around2(ProceedingJoinPoint joinPoint) throws Throwable { // joinPoint 原始方法 System.out.println("注解开发之前2"); Object ret = joinPoint.proceed(); System.out.println("注解开发之后2"); return ret; } }
动态代理的创建方式
默认
AOP
编程底层应用使用JDK
动态代理,可以通过配置文件切换cglib
注解形式下
<aop:aspectj-autoproxy proxy-target-class="true" />
传统配置文件下
<bean id="around" class="com.meinil.proxy.service.Around" /> <aop:config proxy-target-class="true"> <aop:pointcut id="pc" expression="execution(* login(..)) or execution(* register(..))"/> <aop:advisor advice-ref="around" pointcut-ref="pc" /> </aop:config>
# 12.5 注意事项
如果原始类内部方法中调用自己的方法,由于被调用的方法不是由代理对象调用的,则不会执行增强方法。如:下列的register
方法
public class UserServiceImpl implements UserService {
@Override
public boolean login(String username, String password) {
System.out.println("login");
return false;
}
@Override
public void register(User user) {
System.out.println("register");
// 此处为调用原始对象的login方法,并非Spring的代理方法
login("李华", "3456");
}
}
可以通过实现ApplicationContextAware
接口获取Spring
工厂,进而调用方法
public class UserServiceImpl implements UserService, ApplicationContextAware {
private ApplicationContext ctx;
@Override
public boolean login(String username, String password) {
System.out.println("login");
return false;
}
@Override
public void register(User user) {
System.out.println("register");
UserService userService = ctx.getBean("userService", UserService.class);
userService.login("李华", "3456");
}
// 获取Spring工厂ApplicationContext
@Override
public void setApplicationContext(ApplicationContext ctx) throws BeansException {
this.ctx = ctx;
}
}
# 13. 整合持久层
Spring
整合的持久层
# 13.1 整合MyBatis
项目目录
导入依赖
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.27</version>
</dependency>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.6</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>5.3.15</version>
</dependency>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
<version>2.0.6</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.2.8</version>
</dependency>
数据库搭建
create database test;
use test;
create table t_users(
id int primary key auto_increment,
username varchar(12),
password varchar(12)
);
实体类
public class User implements Serializable {
private Integer id;
private String username;
private String password;
// get set
}
DAO
层接口
public interface UserDAO {
public void save(User user);
}
UserMapper.xml
文件
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.meinil.mybatis.dao.UserDAO">
<insert id="save" parameterType="user">
INSERT INTO t_users(username, password) VALUES (#{username}, #{password})
</insert>
</mapper>
准备applicationContext.xml
配置文件
<!--Druid连接池-->
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
<property name="driverClassName" value="com.mysql.cj.jdbc.Driver" />
<property name="url" value="jdbc:mysql://localhost:3306/test?serverTimezone=GMT&allowPublicKeyRetrieval=true&useSSL=false&characterEncoding=utf8" />
<property name="username" value="root" />
<property name="password" value="123456" />
</bean>
<!--创建SqlSessionFactoryBean-->
<bean id="sqlSessionFactoryBean" class="org.mybatis.spring.SqlSessionFactoryBean">
<!--指定数据源-->
<property name="dataSource" ref="dataSource" />
<!--指定实体包-->
<property name="typeAliasesPackage" value="com.meinil.mybatis.model" />
<!--指定mapper文件的位置 -->
<property name="mapperLocations">
<list>
<value>classpath:com/meinil/mybatis/mapper/*Mapper.xml</value>
</list>
</property>
</bean>
<!--创建DAO对象-->
<bean id="scanner" class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="sqlSessionFactoryBeanName" value="sqlSessionFactoryBean" />
<property name="basePackage" value="com.meinil.mybatis.dao" />
</bean>
测试
public class Main {
public static void main(String[] args) throws IOException {
ApplicationContext ctx = new ClassPathXmlApplicationContext("/applicationContext.xml");
UserDAO userDAO = ctx.getBean("userDAO", UserDAO.class);
User user = new User();
user.setUsername("李华");
user.setPassword("9999");
userDAO.save(user);
}
}
⭐️Druid
会自动控制事务,执行一条sql
时会自动提交
# 13.2 Spring事务
事务的特点:ACID
:即原子性、一致性、隔离性、持久性
Spring
通过AOP
方式进行事务开发控制
Spring
默认已经提供了一个DataSourceTransactionManager
用于管理事务,可以使用@Transactional
注解用于控制事务
依赖
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-tx</artifactId>
<version>5.3.14</version>
</dependency>
项目结构
创建UserService
接口
public interface UserService {
public void register(User user);
}
提供实现类要加@Transactional
注解
@Transactional
public class UserServiceImpl implements UserService{
private UserDAO userDAO;
public UserDAO getUserDAO() {
return userDAO;
}
public void setUserDAO(UserDAO userDAO) {
this.userDAO = userDAO;
}
@Override
public void register(User user) {
userDAO.save(user);
}
}
配置文件
<bean id="userService" class="com.meinil.mybatis.service.UserServiceImpl">
<property name="userDAO" ref="userDAO" />
</bean>
<!--事务管理对象-->
<bean id="dataSourceTransactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource" />
</bean>
<tx:annotation-driven transaction-manager="dataSourceTransactionManager" />
测试
public class Main {
public static void main(String[] args) throws IOException {
ApplicationContext ctx = new ClassPathXmlApplicationContext("/applicationContext.xml");
UserService userService = ctx.getBean("userService", UserService.class);
User user = new User();
user.setUsername("Allan");
user.setPassword("4567");
userService.register(user);
}
}
# 13.3 事务属性
事务属性:描述事务特征的一系列值
- 隔离属性
- 传播属性
- 只读属性
- 超时属性
- 异常属性
Spring
在@Transactional
:中可以添加对应的属性
@Transactional(isloation = , propagation = , readOnly = ,timeout = , rollbackFor = , noRollbackFor = )
# 13.3.1 隔离属性
隔离属性:描述了事务解决并发问题的特征
事务并发产生的问题:
脏读:一个事务,读取了另一个事务中没有提交的数据。也会在本事务中产生数据不一致的问题
解决方案
@Transactional(isolation = Isolation.READ_COMMITTED)
不可重复读:一次事务中,多次读取相同的数据,但读取结果不一致,会在本事务中产生数据不一致的问题
解决方案:加行锁
@Transactional(isolation = Isolation.REPEATABLE_READ)
幻影读:一次事务中,多次对整表进行查询统计,但是结果不一样,会在本次事务中产生数据不一致的问题
解决方案:加表锁
@Transactional(isolation = Isolation.SERIALIZABLE)
并发安全:SERIALIZABLE
>REPEATABLE_READ
>READ_COMMITTED
运行效率:SERIALIZABLE
<REPEATABLE_READ
<READ_COMMITTED
隔离属性 | MySQL | Oracle |
---|---|---|
ISOLATION_READ_COMMITTED | ✅ | ✅ |
ISOLATION_REPEATABLE_READ | ✅ | ❎ |
ISOLATION_SERIALIZABLE | ✅ | ✅ |
对于不可重复读,Oracle
采用多版本比对的方式解决
如果不设置隔离属性,则默认为ISOLATION_DEFAULT
,会调用不同数据库锁设置的默认隔离属性(一般默认即可)
MySQL: REPEATABLE_READ
Oracle: READ_COMMITTED
# 13.3.2 传播属性
传播属性:它描述了事务解决嵌套问题的特征
大事务中融入了很多小的事务,它们彼此影响,最终就会导致外部的大事务丧失原子性
传播属性的值 | 外部不存在事务 | 外部存在事务 | 用法 | 应用场景 |
---|---|---|---|---|
REQUIRED | 开启新的事务 | 融合到外部事务中 | @Transactional(propagation = Propagation.REQUIRED) | 增删改 |
SUPPORTS | 不开始新的事务 | 融合到外部事务中 | @Transactional(propagation = Propagation.SUPPORTS) | 查询 |
REQUIRES_NEW | 开启新的事务 | 挂起外部事务,创建新的事务 | @Transactional(propagation = Propagation.REQUIRES_NEW) | 日志记录 |
NOT_SUPPORTED | 不开启新的事务 | 挂起外部事务 | @Transactional(propagation = Propagation.NOT_SUPPORTED) | |
NEVER | 不开启新的事务 | 抛出异常 | @Transactional(propagation = Propagation.NEVER) | |
MANDATORY | 抛出异常 | 融合到外部事务中 | @Transactional(propagation = Propagation.MANDATORY) |
默认传播属性:REQUIRED
# 13.3.3 只读属性
针对于只进行查询操作的业务方法,可以加入只读属性,提高运行效率
@Transactional(propagation = Propagation.SUPPORTS, readOnly = true)
# 13.3.4 超时属性
当前事务访问数据时,有可能访问的数据被别的事务进行加锁的处理,那么此时本事务就必须进行等待
@Transactional(timeout = 2) // 单位 秒
默认由对应的数据库确定
# 13.3.5 异常属性
Spring
事务处理中,
对于RuntimeException
及其子类采用的是回滚的策略
对于Exception
及其子类,采用的是提交策略
配置回滚以及不会滚的异常
@Transactional(rollbackFor = {java.lang.Exception.class}, noRollbackFor = {java.lang.RuntimeException.class})
# 14. 注解开发
项目结构
想要Spring
支持注解需要在配置文件中添加如下配置
<context:component-scan base-package="com.meinil.annotation" />
Spring
会自动扫描base-package
所指定的包及其子包
# 14.1 基础注解
# 14.1.1 对象创建
@Component
@Component public class User { }
默认是类名为
id
值(首单词首字母小写),如需指定id
值,则直接写在@Component
中即可如果想要通过配置文件覆盖注解,则需要
id
值指定为注解设置的id
@Component
的衍生注解@Repository
:一般用于dao
层对象,⭐️如果是Spring
整合Mybatis
对象由Spring
直接生成,无需配置@Controller
:一般用于controller
层对象@Service
:一般用于service
层对象它们的用法与
Component
注解相同,只不过是为了更好的区分不同bean
的功能@Scope
:控制简单对象的创建次数,默认为单例@Component @Scope("singleton") // 对象为单例模式 @Scope("prototype") // 对象为多例模式 public class User { }
@Lazy
:懒惰初始化,即在使用这个对象时再进行创建@Component @Lazy public class User { }
生命周期注解
@Component public class User { @PostConstruct // 指定初始化方法 public void init() {} @PreDestroy // 指定销毁方法 public void destroy() {} }
# 14.1.2 注入
用户自定义类型注入
dao
层设计public interface UserDAO { } @Repository public class UserDAOImpl implements UserDAO{ }
service
层设计public interface UserService { } @Service public class UserServiceImpl { private UserDAO userDAO; @Autowired public void setUserDAO(UserDAO userDAO) { this.userDAO = userDAO; } }
将
@Autowired
加入到set
方法上或者直接加入到属性上默认是通过类型进行注入,也可以使用
@Qualifier
指定注入对象的id
值(不推荐)@Service public class UserServiceImpl implements UserService{ private UserDAO userDAO; @Autowired @Qualifier("userDAOImpl") public void setUserDAO(UserDAO userDAO) { this.userDAO = userDAO; } }
JavaEE
规范中也提供了用于注入的注解@Resource
:可以使用名字或者类型进行注入@Service public class UserServiceImpl implements UserService{ private UserDAO userDAO; @Resource(name = "userDAOImpl") public void setUserDAO(UserDAO userDAO) { this.userDAO = userDAO; } }
JDK
类型准备初始化对象
@Component public class User { @Value("${id}") private Integer id; @Value("${name}") private String name; // get set }
准备初始化参数
id=10 name=小明
调整配置文件:不推荐<context:property-placeholder location="classpath:init.properties" />
⭐️也可以直接在对应的类上添加相关注解
@Component @PropertySource("classpath:/init.properties") public class User { @Value("${id}") private Integer id; @Value("${name}") private String name; // get set }
注意事项:
@Value
不能加载静态成员变量之上,不能注入集合类型
# 14.1.3 注解扫描
排除方式
如果需要排除某些包的注解扫描需要在配置文件中添加
<context:component-scan base-package="com.meinil.annotation">
<context:exclude-filter type="" expression=""/>
</context:component-scan>
type
的取值有五种
assignable
:排除特定的类型(类),expression
指定类的全类名annotation
:排除含有特定注解的类,expression
指定注解的全类名aspectj
:排除aspectj
表达式所表示的类,expression
指定切入点表达式(包或者类)regex
:排除正则表达式所匹配的类,expression
指定正则表达式custom
:自定义排除策略
排除策略可以叠加使用
包含方式
与排除方式类似,指定要扫描的包及类,在配置文件中添加
<context:component-scan base-package="com.meinil.annotation" use-default-filters="false">
<context:include-filter type="" expression=""/>
</context:component-scan>
type
用法与排除方式相同
# 14.2 高级注解
项目目录
# 14.2.1 配置Bean
@Configuration
:指定配置类用于替代xml
的配置方式
@Configuration
public class ApplicationConfig {
}
获取Spring
配置信息
public class Main {
public static void main(String[] args) {
// 通过类名获取Spring配置信息
ApplicationContext ctx1 = new AnnotationConfigApplicationContext(ApplicationConfig.class);
// 通过包名获取Spring配置信息
ApplicationContext ctx2 = new AnnotationConfigApplicationContext("com.meinil.config");
}
}
# 14.2.2 配置logback
导入依赖
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>jcl-over-slf4j</artifactId>
<version>1.7.33</version>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.2.10</version>
<exclusions>
<exclusion>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.logback-extensions</groupId>
<artifactId>logback-ext-spring</artifactId>
<version>0.1.5</version>
</dependency>
在resources
目录下配置logback.xml
文件
<?xml version="1.0" encoding="UTF-8" ?>
<configuration>
<appender name="consoleAppender" class="ch.qos.logback.core.ConsoleAppender">
<encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
<pattern>
[%-5level] %d{yyyy-MM-dd HH:mm:ss.SSS} %c.%M:%L %thread %m %n
</pattern>
</encoder>
</appender>
<root level="ALL">
<appender-ref ref="consoleAppender"/>
</root>
</configuration>
# 14.2.3 @Bean
等同于配置文件中的bean
标签,用于创建对象
简单对象
@Configuration @Scope("prototype") // 控制对象的创建次数(多例) singleton 单例 public class ApplicationConfig { @Bean public User user() { return new User(); } }
注意事项:
@Bean
所添加的方法必须是public
,返回值注入到Spring
中,方法名为注入到Spring
中的id
值,@Bean
中可以指定id
创建复杂对象
@Configuration public class ApplicationConfig { @Bean public Connection conn() { Connection conn = null; try { Class.forName("com.mysql.cj.jdbc.Driver"); conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/test?serverTimezone=GMT", "root", "123456"); } catch (ClassNotFoundException e) { e.printStackTrace(); } catch (SQLException e) { e.printStackTrace(); } return conn; } }
也可以使用
FactoryBean
的方式集成到@Bean
,不推荐public class ConnectionFactoryBean implements FactoryBean<Connection> { @Override public Connection getObject() throws Exception { Class.forName("com.mysql.cj.jdbc.Driver"); Connection conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/test?serverTimezone=GMT", "root", "123456"); return conn; } @Override public Class<?> getObjectType() { return Connection.class; } @Override public boolean isSingleton() { return false; } }
@Configuration public class ApplicationConfig { @Bean public Connection conn() { Connection conn = null; ConnectionFactoryBean factoryBean = new ConnectionFactoryBean(); try { conn = factoryBean.getObject(); } catch (Exception e) { e.printStackTrace(); } return conn; } }
# 14.2.4 注入
用户自定义类型注入,作为参数传进去即可
@Configuration
public class ApplicationConfig {
@Bean
public UserDAO userDAO() {
return new UserDAOImpl();
}
@Bean
public UserService userService(UserDAO userDAO) {
UserServiceImpl userService = new UserServiceImpl();
userService.setUserDAO(userDAO);
return userService;
}
}
JDK
类型注入:手动注入即可
@Configuration
public class ApplicationConfig {
@Bean
public User user() {
User user = new User();
user.setId(11);
user.setName("22");
return user;
}
}
也可以采用配置文件的方式
@Configuration
@PropertySource("classpath:/init.properties")
public class ApplicationConfig {
@Value("${id}")
private int id;
@Value("${name}")
private String name;
@Bean
public User user() {
User user = new User();
user.setId(id);
user.setName(name);
return user;
}
}
# 14.2.5 @ComponentScan
指定扫描注解的包
@Configuration
@ComponentScan(basePackages = "com.meinil.config")
public class ApplicationConfig {
}
排除扫描
@Configuration
@ComponentScan(basePackages = "com.meinil.config",
excludeFilters = {@ComponentScan.Filter(type = FilterType.ANNOTATION, value = {Service.class})})
public class ApplicationConfig {}
包含扫描
@Configuration
@ComponentScan(basePackages = "com.meinil.config",
includeFilters = {@ComponentScan.Filter(type = FilterType.ANNOTATION, value = {Service.class})})
public class ApplicationConfig {}
优先级
@Component < @Bean < applicationContext.xml // 高优先级的配置可以覆盖低优先级的配置
# 14.2.6 整合多个配置信息
多配置
Bean
的整合假设现在有两个配置类
@Configuration public class ApplicationConfig1 { } @Configuration public class ApplicationConfig2 { }
直接指定配置
Bean
的包即可public class Main { public static void main(String[] args) { ApplicationContext ctx = new AnnotationConfigApplicationContext("com.meinil.config"); } }
或者将一个当做主配置类,导入另一个配置类即可
@Configuration @Import(ApplicationConfig2.class) public class ApplicationConfig1 { @Autowired private UserDAO userDAO; @Bean public UserService userService() { UserServiceImpl userService = new UserServiceImpl(); userService.setUserDAO(userDAO); return userService; } } @Configuration public class ApplicationConfig2 { @Bean public UserDAO userDAO() { return new UserDAOImpl(); } }
配置
Bean
与@Component
相关注解的整合:指定扫描包即可@Configuration @ComponentScan(basePackages = "com.meinil.config") public class ApplicationConfig { }
配置
Bean
与配置文件整合:配置类上指定配置文件路径即可@Configuration @ImportResource("/applicationContext.xml") public class ApplicationConfig { }
# 14.2.7 注解AOP
导入AOP
的包
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
<version>5.3.15</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjrt</artifactId>
<version>1.9.7</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.7</version>
</dependency>
创建切面类
@Aspect
@Component
public class MyAspect {
@Pointcut("execution(* *(..))")
public void myPointcut() {}
@Around(value = "myPointcut()")
public Object around1(ProceedingJoinPoint joinPoint) throws Throwable {
System.out.println("切面方法");
return joinPoint.proceed();
}
}
改写配置类
@Configuration
@EnableAspectJAutoProxy
@ComponentScan(basePackages = "com.meinil.config")
public class ApplicationConfig {
}
@EnableAspectJAutoProxy
中可以指定代理对象的创建方式proxyTargetClass = true
启用CGlib
# 14.2.8 注解MyBatis
@Configuration
@MapperScan(basePackages = "com.meinil.config.dao") // 指明dao层包
@EnableTransactionManagement // 启用事务
public class ApplicationConfig1 {
@Bean
public DataSource dataSource() {
DruidDataSource dataSource = new DruidDataSource();
dataSource.setDriverClassName("com.mysql.cj.jdbc.Driver");
dataSource.setUrl("jdbc:mysql://localhost:3306/test?serverTimezone=GMT&allowPublicKeyRetrieval=true&useSSL=false&characterEncoding=utf8");
dataSource.setUsername("root");
dataSource.setPassword("123456");
return dataSource;
}
@Bean
public SqlSessionFactoryBean sqlSessionFactoryBean(DataSource dataSource) {
SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
sqlSessionFactoryBean.setDataSource(dataSource); // 数据源
sqlSessionFactoryBean.setTypeAliasesPackage("com.meinil.config.model"); // 实体类包
sqlSessionFactoryBean.setMapperLocations(new ClassPathResource("com.meinil.config.mapper/*Mapper.xml"));
return sqlSessionFactoryBean;
}
@Bean
public DataSourceTransactionManager dataSourceTransactionManager(DataSource dataSource) {
return new DataSourceTransactionManager(dataSource);
}
}
直接使用dao
接口即可,如需事务在对应类或方法上添加@Transactional
即可