个人博客:www.hellocode.top
⭐所有文章均在上方博客首发,其他平台同步更新
本文专栏:《流行框架》
如没有JavaWEB基础,请先前往《Java Web从入门到实战》专栏学习相应知识
⚡如有问题,欢迎指正,一起学习~~
框架的作用
分层:可以把Spring中的一部分单独拿出来使用,也可以整体一起使用(更加高效)
full-stack:Spring提供一站式解决方案
体系结构
Spring不是只有Java的,其他语言也有Spring
优势
耦合与内聚
程序书写的目标:高内聚,低耦合
就是同一个模块内各个元素之间要高度紧密,但是各个模块之间的相互依存度却不要那么紧密
工厂模式发展史
如果类耦合的话,修改后需要重新编译、打包、发布,但是如果是和配置文件耦合,不需要做这些事情
IoC
案例环境说明
步骤
导入spring坐标(5.1.9.release)
<dependencies>
<dependency>
<groupId>org.springframeworkgroupId>
<artifactId>spring-contextartifactId>
<version>5.1.9.RELEASEversion>
dependency>
dependencies>
编写业务层与表现层(模拟)接口与实现类
public interface UserService {
public void save();
}
public class UserServiceImpl implements UserService {
public void save() {
System.out.println("user service running...");
}
}
建立spring配置文件(applicationContext.xml)
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="userService" class="top.hellocode.service.impl.UserServiceImpl">bean>
beans>
配置所需资源(Service)为spring控制的资源
表现层(App)通过spring获取资源(Service实例)
public static void main(String[] args) {
// 2. 加载配置文件
ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
// 3. 获取资源
UserService userService = (UserService) ctx.getBean("userService");
userService.save();
}
名称:bean
类型:标签
归属:beans标签
作用:定义spring中的资源,受此标签定义的资源将受到spring控制
格式
<beans>
<bean />
beans>
基本属性
<bean id="beanId" name="beanName1,beanName2,..." class="ClassName"/>
scope属性
名称:scope
类型:属性
归属:bean标签
作用:定义bean的作用范围
格式
<bean scope="singleton"/>
取值
单例模式:通过单例模式的方法创建的类在当前进程中只有一个实例
单例和非单例模式创建对象的时机不同:单例模式是在加载时创建,非单例是在使用时创建
bean生命周期
名称:init-method,destroy-method
类型:属性
归属:bean标签
作用:定义bean对象在初始化或销毁时完成的工作
格式
<bean init-method="init" destroy-method="destroy"/>
取值:bean对应的类中对应的具体方法名
注意事项
scope="singleton"
时,spring容器中有且仅有一个对象,init方法在创建容器时仅执行一次scope="prototype"
时,spring容器要创建同一类型的多个对象,init方法在每个对象创建时均执行一次scope="singleton"
时,关闭容器会导致bean实例的销毁,调用destroy方法一次scope="prototype"
时,对象的销毁由垃圾回收机制gc()控制,destroy方法将不会被执行当scope为非单例模式时,销毁不归spring管理
静态工厂与实例工厂创建bean(了解)
名称:factory-bean,factory-method
类型:属性
归属:bean标签
作用:定义bean对象创建方式,使用实例工厂的形式创建bean,兼容早期遗留系统的升级工作
格式
<bean factory-bean="factoryBeanId" factory-method="factoryMethodName"/>
取值:工厂bean中用于获取对象的实例方法名
注意事项
set注入(主流)
名称:property
类型:标签
归属:bean标签
作用:使用set方法的形式为bean提供资源
格式
<bean>
<property />
bean>
基本属性
<property name="propertyName" value="propertyValue" ref="beanId"/>
注意:一个bean可以有多个property标签
字符串当作非引用类型处理
构造器注入(了解)
名称:constructor-arg
类型:标签
归属:bean标签
作用:使用构造方法的形式为bean提供资源,兼容早期遗留系统的升级工作
格式
<bean>
<constructor-arg />
bean>
基本属性
<constructor-arg name="argsName" value="argsValue"/>
其他属性
注意:一个bean可以有多个constructor-arg标签
集合注入
名称:array,list,set,map,props
类型:标签
归属:property标签 或 constructor-arg标签
作用:注入集合数据类型属性
格式
<property>
<list>list>
property>
<property name="myList">
<list>
<value>hellocodevalue>
<value>666value>
<ref bean="userService" />
<bean class="top.hellocode.service.UserService"/>
list>
property>
<property name="myProps">
<props>
<prop key="username">rootprop>
<prop key="password">123456prop>
props>
property>
<property name="myArray">
<array>
<value>hellocodevalue>
<value>666value>
<ref bean="userService" />
<bean class="top.hellocode.service.UserService"/>
array>
property>
<property name="mySet">
<set>
<value>hellocodevalue>
<value>666value>
<ref bean="userService" />
<bean class="top.hellocode.service.UserService"/>
set>
property>
<property name="myMap">
<map>
<entry key="name" value-ref="hellocode"/>
<entry key="fame" value="666"/>
<entry key="userService">
<ref bean="userService">ref>
entry>
<entry key="applyService">
<bean class="applyService">bean>
entry>
map>
property>
array和list是互通的,二者可以相互注入
使用p命名空间简化配置(了解)
名称:p:peopertyName,p:peopertyName-ref
类型:属性
归属:bean标签
作用:为bean注入属性值
格式
<bean p:propertyName="propertyValue" p:propertyName-ref="beanId"/>
注意:使用p命令空间需要先开启spring对p命令空间的支持,在beans标签中添加对应空间支持
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:p="http://www.springframework.org/schema/p"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd">
xmlns:p="http://www.springframework.org/schema/p"
SpEL(了解)
Spring提供了对EL表达式的支持,统一属性注入格式
类型:属性值
归属:value属性
作用:为bean注入属性值
格式
<bean>
<property value="EL" />
bean>
注意:所有属性不区分是否引用类型,统一使用value赋值
所有格式统一使用 value="******"
操作步骤
准备外部properties文件
开启context命名空间支持
xmlns:context="http://www.springframework.org/schema/context"
加载指定的properties文件
<context:property-placeholder location="classpath:filename.properties">
使用加载的数据
<property name="propertyName" value="${propertiesName}"/>
*.properties
表示加载所有的properties文件${propertiesName}
格式进行,其中propertiesName指properties文件中的属性名在读取username值时,出现的是当前电脑的用户名,是因为context中已经有username属性,为了区分,尽量避免使用username,可以用user代替
名称:import
类型:标签
归属:beans标签
作用:在当前配置文件中导入其他配置文件中的项
格式
<beans>
<import />
beans>
基本属性
<import resources="config.xml" />
Spring容器加载多个配置文件(了解)
new ClassPathXmlApplicationContext("config1.xml","config2.xml");
Spring容器中的bean定义冲突问题
对比BeanFactory
FileSystemXmlApplicationContext
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
<property name="driverClassName" value="com.mysql.jdbc.Driver" />
<property name="url" value="jdbc:mysql://localhost:3306/spring_db" />
<property name="username" value="root" />
<property name="password" value="hellocode" />
bean>
案例分析
非spring环境
spring环境
制作步骤
环境准备
业务类与接口准备
基础配置文件
整合前基础准备工作
导入Spring整合Mybatis坐标
<dependencies>
<dependency>
<groupId>org.springframeworkgroupId>
<artifactId>spring-contextartifactId>
<version>5.1.9.RELEASEversion>
dependency>
<dependency>
<groupId>org.springframeworkgroupId>
<artifactId>spring-jdbcartifactId>
<version>5.1.9.RELEASEversion>
dependency>
<dependency>
<groupId>org.mybatisgroupId>
<artifactId>mybatisartifactId>
<version>3.5.3version>
dependency>
<dependency>
<groupId>mysqlgroupId>
<artifactId>mysql-connector-javaartifactId>
<version>5.1.46version>
dependency>
<dependency>
<groupId>com.alibabagroupId>
<artifactId>druidartifactId>
<version>1.1.20version>
dependency>
<dependency>
<groupId>org.mybatisgroupId>
<artifactId>mybatis-springartifactId>
<version>1.3.0version>
dependency>
dependencies>
将Mybatis配置成spring管理的bean(sqlSessionFactoryBean)
通过spring加载mybatis的映射配置文件到spring环境中
设置类别名
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
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:*.properties"/>
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
<property name="driverClassName" value="${jdbc.driver}"/>
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
bean>
<bean id="accountService" class="top.hellocode.service.impl.AccountServiceImpl">
<property name="accountDao" ref="accountDao"/>
bean>
<bean class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="typeAliasesPackage" value="top.hellocode.domain"/>
<property name="dataSource" ref="dataSource"/>
bean>
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="basePackage" value="top.hellocode.dao"/>
bean>
beans>
测试结果
public class App {
public static void main(String[] args) {
ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
AccountService accountService = (AccountService) ctx.getBean("accountService");
Account account = new Account();
account.setId(1);
account.setName("xxx");
account.setMoney(888.88);
// accountService.save(account);
accountService.update(account);
}
}
什么是注解驱动
弊端
启动注解功能
启动注解扫描,加载类中配置的注解项
<context:component-scan base-package="packageName" />
说明
注意
bean的定义
名称:@Component
@Controller
@Service
@Repository
类型:类注解
位置:类定义上方
作用:设置该类为spring管理的bean
范例
@Component
public class ClassName{}
说明
@Controller
、@Service
、@Repository
是@Component
的衍生注解,功能同@Component
相关属性
value
(默认):定义bean的访问idbean的作用域
名称:@Scope
类型:类注解
位置:类定义上方
作用:设置该类作为bean对应的scope属性
范例
@Scope
public class ClassName{}
相关属性
生命周期
名称:@PostConstruct
、PreDestroy
类型:方法注解
位置:方法定义上方
作用:设置该类作为bean对应的生命周期方法
范例
@PostConstruct
public void init(){
System.out.println("init...");
}
名称:@Bean
类型:方法注解
位置:方法定义上方
作用:设置该方法的返回值作为spring管理的bean
范例
@Bean("dataSource")
public DruidDataSource createDataSource(){
return ...;
}
说明
相关属性
bean的非引用类型属性注入
名称:@Value
类型:属性注解、方法注解
位置:属性定义上方、方法定义上方
作用:设置对应属性的值或对方法进行传参
范例
@Value("$(jdbc.username)")
private String username;
说明
相关属性
bean的引用类型属性注入
名称:@Autowired
、@Qualifier
类型:属性注解、方法注解
位置:属性定义上方、方法定义上方
作用:设置对应属性的对象或对方法进行引用类型传参
范例
@Autowired(required = false)
@Qualifier("userDao")
private UserDao userDao;
说明
相关属性
名称:@Primary
类型:类注解
位置:类定义上方
作用:设置类对应的bean按类型装配时优先装配
范例
@Primary
public class ClassName{}
说明
了解
@Inject
、@Named
、@Resource
名称:@PropertySource
类型:类注释
位置:类定义上方
作用:加载properties文件中的属性值
范例
@PropertiesSource(value = "classpath:filename.properties")
public class ClassName{
@Value("${propertiesAttributeName}")
private String attributeName;
}
说明
相关属性
名称:@Configuration
、@ComponentScan
类型:类注解
位置:类定义上方
作用:设置当前类为spring核心配置加载类
范例
@Configuration
@ComponentScan("scanPackageName")
public class SpringConfigClassName{
}
说明
@ComponentScan
替代名称:@Import
类型:类注解
位置:类定义上方
作用:导入第三方bean作为spring控制的资源
范例
@Configuration
@Import(OtherClassName.class)
public class ClassName{
}
说明
@Import
注解在同一个类上,仅允许添加一次,如果需要导入多个,使用数组的形式进行设定@Import
导入其他资源(了解)@Bean
所在的类可以使用导入的形式进入spring容器,无需声明为bean依赖加载
名称:@DependsOn
类型:类注解、方法注解
位置:bean定义的位置(类上或方法上)
作用:控制bean的加载顺序,使其在指定bean加载完毕后再加载
范例
@DependsOn("beanId")
public class ClassName{
}
说明
@DependsOn
指定的bean优先于@Bean
配置的bean进行加载@DependsOn
指定的bean优先于当前类中所有@Bean
配置的bean进行加载@DependsOn
指定的bean优先于@Component
等配置的bean进行加载相关属性
名称:@Order
类型:配置类注解
位置:配置类定义的位置(类上)
作用:控制配置类的加载顺序
范例
@Order(1)
public class SpringConfigClassName{
}
名称:@Lazy
类型:类注解、方法注解
位置:bean定义的位置(类上或方法上)
作用:控制bean的加载时机,使其延迟加载
范例
@Lazy
public class ClassName{
}
依赖加载应用场景
@DependsOn
@Lazy
@Order
步骤
Dao
package top.hellocode.dao;
import org.apache.ibatis.annotations.Delete;
import org.apache.ibatis.annotations.Insert;
import org.apache.ibatis.annotations.Select;
import org.apache.ibatis.annotations.Update;
import org.springframework.stereotype.Repository;
import top.hellocode.domain.Account;
import java.util.List;
/**
* @author HelloCode
* @site https://www.hellocode.top
* @date 2022年06月06日 16:33
*/
public interface AccountDao {
// 新增
@Insert("INSERT INTO account(name,money) values(#{name},#{money})")
public int save(Account account);
// 删除
@Delete("DELETE FROM account WHERE id = #{id}")
public int delete(Integer id);
// 修改
@Update("UPDATE account SET name=#{name},money=#{money} where id = #{id}")
public int update(Account account);
// 查询
@Select("SELECT * FROM account")
public List<Account> findAll();
@Select("SELECT * FROM account WHERE id = #{id}")
public Account findById(Integer id);
}
Service
package top.hellocode.service.impl;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import top.hellocode.dao.AccountDao;
import top.hellocode.domain.Account;
import top.hellocode.service.AccountService;
import java.util.List;
/**
* @author HelloCode
* @site https://www.hellocode.top
* @date 2022年06月06日 16:35
*/
@Service("accountService")
public class AccountServiceImpl implements AccountService {
@Autowired
private AccountDao accountDao;
// public void setAccountDao(AccountDao accountDao) {
// this.accountDao = accountDao;
// }
public void save(Account account) {
accountDao.save(account);
}
public void delete(Integer id) {
accountDao.delete(id);
}
public void update(Account account) {
accountDao.update(account);
}
public List<Account> findAll() {
return accountDao.findAll();
}
public Account findById(Integer id) {
return accountDao.findById(id);
}
}
config
package top.hellocode.config;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.context.annotation.PropertySource;
/**
* @author HelloCode
* @site https://www.hellocode.top
* @date 2022年06月09日 14:24
*/
@Configuration
@ComponentScan("top.hellocode")
@PropertySource("classpath:jdbc.properties")
@Import({JDBCConfig.class,MyBatisConfig.class})
public class SpringConfig {
}
package top.hellocode.config;
import com.alibaba.druid.pool.DruidDataSource;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import javax.sql.DataSource;
/**
* @author HelloCode
* @site https://www.hellocode.top
* @date 2022年06月09日 14:26
*/
public class JDBCConfig {
@Value("${jdbc.driver}")
private String driver;
@Value("${jdbc.url}")
private String url;
@Value("${jdbc.username}")
private String userName;
@Value("${jdbc.password}")
private String password;
@Bean("dataSource")
public DataSource getDataSource(){
DruidDataSource ds = new DruidDataSource();
ds.setDriverClassName(driver);
ds.setUrl(url);
ds.setUsername(userName);
ds.setPassword(password);
return ds;
}
}
package top.hellocode.config;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.mapper.MapperScannerConfigurer;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import javax.sql.DataSource;
/**
* @author HelloCode
* @site https://www.hellocode.top
* @date 2022年06月09日 14:34
*/
public class MyBatisConfig {
@Bean
public SqlSessionFactoryBean getSqlSessionFactoryBean(@Autowired DataSource dataSource){
SqlSessionFactoryBean ssfb = new SqlSessionFactoryBean();
ssfb.setTypeAliasesPackage("top.hellocode.domain");
ssfb.setDataSource(dataSource);
return ssfb;
}
@Bean
public MapperScannerConfigurer getMapperScannerConfigurer(){
MapperScannerConfigurer msc = new MapperScannerConfigurer();
msc.setBasePackage("top.hellocode.dao");
return msc;
}
}
注意
导入Spring整合Junit坐标
<dependency>
<groupId>junitgroupId>
<artifactId>junitartifactId>
<version>4.12version>
dependency>
<dependency>
<groupId>org.springframeworkgroupId>
<artifactId>spring-testartifactId>
<version>5.1.9.RELEASEversion>
dependency>
Spring整合junit测试用例注解格式
@RunWith(SpringJunit4ClassRunner.class)
@ContextConfiguration(classes = SpringConfig.class)
public class UserServiceTest{
}
测试代码
package top.hellocode.service;
import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import top.hellocode.config.SpringConfig;
import top.hellocode.domain.Account;
/**
* @author HelloCode
* @site https://www.hellocode.top
* @date 2022年06月09日 14:51
*/
// 设定spring专用的类加载器
@RunWith(SpringJUnit4ClassRunner.class)
// 设定加载的Spring上下文对应的配置
@ContextConfiguration(classes = SpringConfig.class)
public class UserServiceTest {
@Autowired
private AccountService accountService;
@Test
public void testFindById(){
Account ac = accountService.findById(1);
Assert.assertEquals("xxx",ac.getName());
}
}
BeanFactory:提供Bean的基本操作
HierarchicalBeanFactory:提供bean分层结构,提出父子容器概念
AutowireCapableBeanFactory:提供bean自动装配功能
ListableBeanFactory:提供容器内部遍历搜索bean的功能
设定组件扫描加载过滤器
名称:@ComponentScan
类型:类注解
位置:类定义上方
作用:设置spring配置加载类扫描规则
范例
@ComponentScan(
value = "top.hellocode" // 设置基础扫描路径
excludeFilters = // 设置过滤规则,当前为排除过滤
@ComponentScan.Filter( // 设置过滤器
type = FilterType.ANNOTATION, // 设置过滤方式为按照注解进行过滤
classes = Repository.class // 设置具体的过滤项,过滤所有@Repository修饰的bean
)
)
自定义组件过滤器
名称:TypeFilter
类型:接口
作用:自定义类型过滤器
范例
public class MyTypeFilter implements TypeFilter {
@Override
public boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory) throws IOException {
// 加载的类满足要求,匹配成功
ClassMetadata classMetadata = metadataReader.getClassMetadata();
String className = classMetadata.getClassName();
if(className.equals("top.hellocode.service.impl.UserServiceImpl")){
return true;
}
return false;
}
}
标签配置@Component
及衍生注解配置自定义导入器
名称:ImportSelector
类型:接口
作用:自定义bean导入器
范例
public class MyImportSelector implements ImportSelector{
public String[] selectImports(AnnotationMetadata icm){
return new String[]("top.hellocode.service.impl.UserServiceImpl");
}
}
@Configuration
@ComponentScan("top.hellocode")
@Import(MyImportSelector.class)
public class SpringConfig{
}
名称:ImportBeanDefinitionRegistrar
类型:接口
作用:自定义bean定义注册器
范例
public class MyImportRegistrar implements ImportBeanDefinitionRegistrar {
@Override
public void registerBeanDefinitions(AnnotationMetadata annotationMetadata, BeanDefinitionRegistry beanDefinitionRegistry) {
ClassPathBeanDefinitionScanner scanner = new ClassPathBeanDefinitionScanner(beanDefinitionRegistry,false);
scanner.addIncludeFilter(new TypeFilter() {
@Override
public boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory) throws IOException {
return true;
}
});
scanner.scan("top.hellocode");
}
}
使用:@Import(MyImportRegistrar.class)
BeanFactoryPostProcessor
BeanPostProcessor
InitializingBean
作用:定义了每个bean的初始化前进行的动作,属于非统一性动作,用于对bean进行创建前业务处理
运行时机:当前操作伴随着任意一个bean的创建过程,保障其个性化业务处理
注意:上述操作均需要被spring容器加载放可运行(@Import)
@Import只能有一个,需要导入多个时使用数组形式
繁琐的bean初始化过程处理
FactoryBean与BeanFactory的区别
OOP开发思路
作用
优势
AOP相关概念
AOP开发过程
AOP开发方式
入门案例制作分析
导入相关坐标
<dependencies>
<dependency>
<groupId>org.springframeworkgroupId>
<artifactId>spring-contextartifactId>
<version>5.1.9.RELEASEversion>
dependency>
<dependency>
<groupId>org.aspectjgroupId>
<artifactId>aspectjweaverartifactId>
<version>1.9.4version>
dependency>
dependencies>
确认要抽取的功能,并将其制作成方法保存到专用的类中,删除原始业务中对应的功能
public class UserServiceImpl implements UserService {
public void save() {
// 抽取共性功能
// System.out.println("共性功能");
System.out.println("user service running...");
}
}
AOPAdvice
public class AOPAdvice {
public void function(){
System.out.println("共性功能");
}
}
将所有AOP操作的资源加载到IoC容器中
使用配置的方式描述被抽取功能的位置,并描述被抽取功能与对应位置的关系
运行程序
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd">
<bean id="userService" class="top.hellocode.impl.UserServiceImpl"/>
<bean id="myAdvice" class="top.hellocode.aop.AOPAdvice"/>
<aop:config>
<aop:pointcut id="pt" expression="execution(* *..*(..))"/>
<aop:aspect ref="myAdvice">
<aop:before method="function" pointcut-ref="pt"/>
aop:aspect>
aop:config>
beans>
AspectJ
AOP配置
名称:aop:config
类型:标签
归属:beans标签
作用:设置AOP
格式
<beans>
<aop:config>...aop:config>
<aop:config>...aop:config>
beans>
说明:一个beans标签中可以配置多个aop:config标签
名称:aop:aspect
类型:标签
归属:aop:config
标签
作用:设置具体的AOP通知对应的切入点
格式
<aop:config>
<aop:aspect ref="beanId">...aop:aspect>
<aop:aspect ref="beanId">...aop:aspect>
aop:config>
说明:一个aop:config中可以配置多个aop:aspect标签
基本属性
名称:aop:pointcut
类型:标签
归属:aop:config
标签、aop:aspect
标签
作用:设置切入点
格式
<aop:config>
<aop:pointcut id="pointcutId" expression="...."/>
<aop:aspect>
<aop:pointcut id="pointcutId" expression="...."/>
aop:aspect>
aop:config>
说明:一个aop:config可以配置多个aop:pointcut标签,且该标签可以配置在aop:aspect标签内
基本属性
切入点表达式
语法格式
关键字 (访问修饰符 返回值 包名.类名.方法名 (参数) 异常名)
execution (public User top.hellocode.service.UserService.findById(int))
访问修饰符为public时可以省略
关键字
关键字 | 描述 |
---|---|
execution【常用】 | 匹配执行指定方法 |
args【了解】 | 匹配带有指定参数类型的方法 |
within | … |
this | … |
target | … |
@within | … |
@target | … |
@args | … |
@annotation | … |
bean | … |
reference pointcut | … |
除了前两个,后面的关键字基本不会用,需要使用的时候再查阅资料即可
通配符
*
:单个独立的任意符号,可以独立出现,也可以作为前缀或者后缀的匹配符出现execution(public * top.hellocode.*.UserService.find*(*))
..
:多个连续的任意符号,可以独立出现,常用于简化报名与参数的书写execution(public User top..UserService.findById(..))
+
:专用于匹配子类类型execution(* *..*Service+.*(..))
逻辑运算符
&&
:连接两个切入点表达式,表示两个切入点表达式同时成立的匹配||
:连接两个切入点表达式,表示两个切入点表达式成立任意一个的匹配!
:连接单个切入点表达式,表示该切入点表达式不成立的匹配三种切入点配置方式
<aop:config>
<aop:pointcut id="pt" expression="execution(* *..*(..))"/>
<aop:aspect ref="myAdvice">
<aop:pointcut id="pt2" expression="execution(* *..*(..))"/>
<aop:before method="before" pointcut-ref="pt2"/>
<aop:before method="before" pointcut="execution(* *..*(..))"/>
aop:aspect>
aop:config>
切入点配置经验
(以上规则适用于XML配置格式)
五种通知类型配置
AOP的通知类型共五种
before
):原始方法执行前执行,如果通知中抛出异常,阻止原始方法运行
after
):原始方法执行后执行,无论原始方法中是否出现异常,都将执行通知
after-returning
):原始方法正常执行完毕后返回结果后执行,如果原始方法中抛出异常,无法执行
after-throwing
):原始方法抛出异常后执行,如果原始方法没有抛出异常,无法执行
around
):在原始方法执行前后均有对应方法执行,还可以阻止原始方法的执行
名称:aop:before
类型:标签
归属:aop:aspect
标签
作用:设置前置通知
格式
<aop:aspect ref="adviceId">
<aop:before method="methodName" pointcut="..."/>
aop:aspect>
说明:一个aop:aspect
标签中可以配置多个aop:before
标签
基本属性
环绕通知开发方式
环绕通知是在原始方法的前后添加功能,在环绕通知中,存在对原始方法的显示调用
public Object around(ProceedingJoinPoint pjp) throws Throwable {
System.out.println("around before");
// 对原始方法调用
Object ret = pjp.proceed();
System.out.println("around after");
return ret;
}
环绕通知方法相关说明
通知顺序(了解)
当同一个切入点配置了多个通知时,通知会存在运行的先后顺序,该顺序以通知配置的顺序为准
通知中获取参数
设定通知方法第一个参数为JoinPoint
,通过该对象调用getArgs()
方法,获取原始方法运行的参数数组
public void before(JoinPoint jp){
Object[] args = jp.getArgs();
System.out.println("before..." + args[0]);
}
所有的通知均可以获取参数
设定切入点表达式为通知方法传递参数(锁定通知变量名)
原始方法
public void save(int param1, int param2){
System.out.println("user service running...");
}
AOP配置
<aop:aspect ref="myAdvice">
<aop:pointcut id="pt" expression="execution(* *..*(..)) && args(a,b) "/>
<aop:before method="before" pointcut-ref="pt"/>
aop:aspect>
通知类
public void before(int a, int b){
System.out.println("a=" + a);
System.out.println("b=" + b);
}
通知中获取返回值
设定返回值变量名
原始方法
public int save(){
System.out.println("user service running...");
return 100;
}
AOP配置
<aop:aspect ref="myAdvice">
<aop:pointcut id="pt" expression="execution(* *..*(..))"/>
<aop:after-returning method="afterReturning" pointcut-ref="pt" returning="ret"/>
aop:aspect>
通知类
public void afterReturning(Object ret){
System.out.println(ret);
}
适用于返回后通知(after-returning)
在通知类的方法中调用原始方法获取返回值
原始方法
public int save(){
System.out.println("user service running...");
return 100;
}
AOP配置
<aop:aspect ref="myAdvice">
<aop:pointcut id="pt" expression="execution(* *..*(..))"/>
<aop:around method="around" pointcut-ref="pt"/>
aop:aspect>
通知类
public Object around(Object ret){
Object ret = pjp.proceed();
return ret;
}
适用于环绕通知(around)
通知中获取异常对象
设定异常对象变量名
原始方法
public void save(){
System.out.println("user service running...");
int i = 1 / 0;
}
AOP配置
<aop:aspect ref="myAdvice">
<aop:pointcut id="pt" expression="execution(* *..*(..))"/>
<aop:after-throwing method="afterThrowing" pointcut-ref="pt" throwing="t"/>
aop:aspect>
通知类
public Object afterThrowing(Throwable t){
System.out.println(t.getMessage());
}
适用于返回后通知(after-throwing)
在通知类的方法中调用原始方法捕获异常
原始方法
public void save(){
System.out.println("user service running...");
int i = 1 / 0;
}
AOP配置
<aop:aspect ref="myAdvice">
<aop:pointcut id="pt" expression="execution(* *..*(..))"/>
<aop:around method="around" pointcut-ref="pt"/>
aop:aspect>
通知类
public Object around(ProceedingJoinPoint pjp) throws Throwable {
Object ret = pjp.proceed(); // 对此处进行try...catch...捕获异常,或者抛出异常
return ret;
}
适用于环绕通知(around)
在XML格式基础上
@Aspect
@Pointcut
@Before
注解开发AOP注意事项
()
不能省略类名.方法名()
”引用(可以专门定义一个类来存放切入点)AOP注解开发通知执行顺序控制(了解)
AOP使用XML配置情况下,通知的执行顺序由配置顺序决定,在注解情况下由于不存在配置顺序的概念,参照通知所配置的方法名字符串对应的编码值顺序,可以简单理解为字母排序
@Order
注解通过变更bean的加载顺序改变通知的加载顺序企业开发经验
AOP注解驱动
名称:@EnableAspectJAutoProxy
类型:注解
位置:Spring注解配置类定义上方
作用:设置当前类开启AOP注解驱动的支持,加载AOP注解
格式
@Configuration
@ComponentScan("top.hellocode")
@EnableAspectJAutoProxy
public class SpringConfig{
}
**案例介绍:**对项目进行业务层接口执行监控,测量业务层接口的执行效率
案例分析
制作步骤
package top.hellocode.aop;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;
/**
* @author HelloCode
* @site https://www.hellocode.top
* @date 2022年07月13日 15:16
*/
@Component
@Aspect
public class RunTimeMonitorAdvice {
// 定义切入点,监控业务层接口
@Pointcut("execution(* top.hellocode.*Service.find*(..))")
public void pt(){}
@Around("pt()")
public Object runtimeAround(ProceedingJoinPoint pjp) throws Throwable{
Signature signature = pjp.getSignature();
String className = signature.getDeclaringTypeName(); // 获取接口名
String methodName = signature.getName(); // 获取方法名
long sum = 0L;
Object ret = null;
// 为了增强精确度,使用循环
for(int i = 0; i < 10000; i++){
long startTime = System.currentTimeMillis();
ret = pjp.proceed(pjp.getArgs());
long endTime = System.currentTimeMillis();
sum += endTime - startTime;
}
System.out.println(className + ":" + methodName + "(万次)run:" + sum + "ms");
return ret;
}
}
案例后续思考与设计
测量真实性
测量结果展示
装饰者模式(Decorator Pattern):在不惊动原始设计的基础上,为其添加功能
package base.decorator;
import top.hellocode.service.UserService;
/**
* @author HelloCode
* @site https://www.hellocode.top
* @date 2022年07月13日 18:05
*/
public class UserServiceImplDecorator implements UserService {
private UserService userService;
public UserServiceImplDecorator(UserService userService){
this.userService = userService;
}
public void save() {
userService.save();
// 增强功能
System.out.println("刮大白");
}
}
public static void main(String[] args) {
UserService userService = new UserServiceImpl();
UserService userService1 = new UserServiceImplDecorator(userService);
userService1.save(); // 此处增强了功能
}
JDKProxy动态代理是针对对象做代理,要求原始对象具有接口实现,并对接口方法进行增强
package base.proxy;
import top.hellocode.service.UserService;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
/**
* @author HelloCode
* @site https://www.hellocode.top
* @date 2022年07月13日 18:11
*/
public class UserServiceJDKProxy {
public static UserService createUserServiceJDKProxy(final UserService userService){
ClassLoader cl = userService.getClass().getClassLoader();
Class[] classes = userService.getClass().getInterfaces();
InvocationHandler ih = new InvocationHandler() {
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Object ret = method.invoke(userService, args);
System.out.println("刮大白");
return ret;
}
};
UserService service = (UserService) Proxy.newProxyInstance(cl,classes,ih);
return service;
}
}
public static void main(String[] args) {
UserService userService = new UserServiceImpl();
UserService userService1 = UserServiceJDKProxy.createUserServiceJDKProxy(userService);
userService1.save();
}
package base.cglib;
import org.springframework.cglib.proxy.Enhancer;
import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;
import top.hellocode.service.UserService;
import java.lang.reflect.Method;
/**
* @author HelloCode
* @site https://www.hellocode.top
* @date 2022年07月13日 18:30
*/
public class UserServiceCglibProxy {
public static UserService createUserServiceCglibProxy(Class clazz) {
Enhancer enhancer = new Enhancer(); // 创建动态字节码
enhancer.setSuperclass(clazz);
enhancer.setCallback(new MethodInterceptor() {
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
Object ret = methodProxy.invokeSuper(o, objects); // 调用原始方法
if (method.getName().equals("save")) {
System.out.println("刮大白"); // 对原始方法做增强
}
return ret;
}
});
return (UserService) enhancer.create();
}
}
public static void main(String[] args) {
UserService userService = UserServiceCglibProxy.createUserServiceCglibProxy(UserServiceImpl.class);
userService.save();
}
代理模式的选择
Spring可以通过配置的形式控制使用的代理形式,默认使用JDKProxy,通过配置可以修改为使用CGLib
XML配置
<aop:config proxy-target-class="false">
aop:config>
XML注解支持
<aop:aspectj-autoproxy proxy-target-class="false"/>
注解驱动
@EnableAspectJAutoProxy(proxyTargetClass = true)
false(默认):JDKProxy;
true:CGLibProxy
Spring使用的是运行期织入
事务特征(ACID)
事务隔离级
脏读:允许读取未提交的信息
不可重复读:读取过程中单个数据发生了变化
幻读:读取过程中数据条目发生了变化
J2EE开发使用分层设计的思想进行,对于简单的业务层转调数据层的单一操作,事务开启在业务层或者数据层并无太大差别,当业务中包含多个数据层的调用时,需要在业务层开启事务,对数据层中多个操作进行组合并归属于同一个事务进行处理
Spring为业务层提供了整套的事务解决方案
PlatformTransactionManager(平台事务管理器实现类)
PlatformTransactionManager接口定义了事务的基本操作
获取事务
TransactionStatus getTransaction(TransactionDefinition definition)
提交事务
void commit(TransactionStatus status)
回滚事务
void rollback(TransactionStatus status)
TransactionDefinition
此接口定义了事务的基本信息
获取事务定义名称
String getName()
获取事务的读写属性
boolean isReadOnly()
获取事务隔离级别
int getIsolationLevel()
获取事务超时时间
int getTimeout()
获取事务传播行为特征
int getPropagationBehavior()
TransactionStatus
此接口定义了事务在执行过程中某个时间点上的状态信息及对应的状态操作
boolean isNewTransaction()
boolean isCompleted()
boolean isRollbackOnly()
void flush()
boolean hasSavepoint()
void setRollbackOnly()
基于Spring、Mybatis整合
银行转账业务说明
银行转账操作中,涉及从A账户到B账户的资金转移操作。数据层仅提供单条数据的基础操作,未设计多账户间的业务操作
业务层接口提供转账操作
package top.hellocode.service;
/**
* @author HelloCode
* @site https://www.hellocode.top
* @date 2022年07月14日 17:34
*/
public interface AccountService {
public void transfer(String outName, String inName, Double money);
}
业务层实现提供转账操作
public void transfer(String outName, String inName, Double money) {
accountDao.inMoney(outName, money);
accountDao.outMoney(inName, money);
}
数据层提供对应的入账与出账操作
<mapper namespace="top.hellocode.dao.AccountDao">
<update id="inMoney">
update account set money = money + #{money} where name = #{name}
</update>
<update id="outMoney">
update account set money = money - #{money} where name = #{name}
</update>
</mapper>
package top.hellocode.service.impl;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.TransactionDefinition;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.support.DefaultTransactionDefinition;
import top.hellocode.dao.AccountDao;
import top.hellocode.service.AccountService;
import javax.sql.DataSource;
/**
* @author HelloCode
* @site https://www.hellocode.top
* @date 2022年07月14日 17:33
*/
public class AccountServiceImpl implements AccountService {
private AccountDao accountDao;
private DataSource dataSource;
public void setDataSource(DataSource dataSource){
this.dataSource = dataSource;
}
public void setAccountDao(AccountDao accountDao){
this.accountDao = accountDao;
}
@Override
public void transfer(String outName, String inName, Double money) {
// 创造事务管理器
PlatformTransactionManager ptm = new DataSourceTransactionManager(dataSource);
// 创建事务定义对象
TransactionDefinition td = new DefaultTransactionDefinition();
// 创建事务状态对象,用于控制事务执行
TransactionStatus ts = ptm.getTransaction(td);
accountDao.inMoney(outName, money);
int i = 1 / 0; // 模拟错误
accountDao.outMoney(inName, money);
// 提交事务
ptm.commit(ts);
}
}
AOP改造编程式事务
package top.hellocode.service.impl;
import javax.sql.DataSource;
/**
* @author HelloCode
* @site https://www.hellocode.top
* @date 2022年07月14日 17:33
*/
public class AccountServiceImpl implements AccountService {
private AccountDao accountDao;
public void setAccountDao(AccountDao accountDao){
this.accountDao = accountDao;
}
@Override
public void transfer(String outName, String inName, Double money) {
accountDao.inMoney(outName, money);
int i = 1 / 0;
accountDao.outMoney(inName, money);
}
}
package top.hellocode.aop;
import javax.sql.DataSource;
/**
* @author HelloCode
* @site https://www.hellocode.top
* @date 2022年07月14日 18:53
*/
public class TxAdvice {
private AccountDao accountDao;
private DataSource dataSource;
public void setDataSource(DataSource dataSource){
this.dataSource = dataSource;
}
public Object transactionManager(ProceedingJoinPoint pjp) throws Throwable {
// 开启事务
PlatformTransactionManager ptm = new DataSourceTransactionManager(dataSource);
// 事务定义对象
TransactionDefinition td = new DefaultTransactionDefinition();
// 事务状态对象
TransactionStatus ts = ptm.getTransaction(td);
Object ret = pjp.proceed(pjp.getArgs());
// 提交事务
ptm.commit(ts);
return ret;
}
}
<aop:config>
<aop:pointcut id="pt" expression="execution(* *..transfer(..))"/>
<aop:aspect ref="txAdvice">
<aop:around method="transactionManager" pointcut-ref="pt"/>
aop:aspect>
aop:config>
开启tx命名空间
xmlns:tx="http://www.springframework.org/schema/tx"
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx.xsd
使用tx命名空间配置事务专属通知类
<bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
bean>
<tx:advice id="txAdvice" transaction-manager="txManager">
<tx:attributes>
<tx:method name="*" read-only="false"/>
<tx:method name="get*" read-only="true"/>
tx:attributes>
tx:advice>
使用aop:advisor在AOP配置中引用事务专属通知类
<aop:config>
<aop:pointcut id="pt" expression="execution(* top.hellocode.service.*Service.*(..))"/>
<aop:advisor advice-ref="txAdvice" pointcut-ref="pt"/>
aop:config>
aop:advice与aop:advisor的区别
aop:advice
配置的通知类可以是普通的Java对象,不实现接口,也不使用继承关系aop:advisor
配置的通知类必须实现通知接口
tx配置
名称:tx:advice
类型:标签
归属:beans标签
作用:专用于声明事务通知
格式
<beans>
<tx:advice id="txAdvice" transaction-manager="txManager">
tx:advice>
beans>
基本属性
名称:tx:attributes
类型:标签
归属:tx:advice
标签
作用:定义通知属性
格式
<tx:advice id="txAdvice" transaction-manager="txManager">
<tx:attributes>
tx:attributes>
tx:advice>
基本属性
名称:tx:method
类型:标签
归属:tx:attribute
标签
作用:设置具体的事务属性
格式
<tx:attributes>
<tx:method name="*" read-only="false"/>
<tx:method name="get*" read-only="true"/>
tx:attributes>
说明:通常事务属性会配置多个,包含1个读写的全事务属性,1个只读的查询类事务属性
tx:method属性
<tx:method
name="*" 待添加事务的方法名表达式(支持*号通配符),例如get*、*、....
read-only="false" 设置事务的读写属性,true为只读,false为读写
timeout="-1" 设置事务的超时时长,单位秒
isolation="DEFAULT" 设置事务隔离级别,该隔离级设定是基于Spring的设定,非数据库端
no-rollback-for="" 设置事务中不回滚的异常,多个异常间使用,分割
rollback-for="" 设置事务中必回滚的异常,多个异常间使用,分割
propagation="REQUIRED" 设置事务的传播行为
/>
事务传播行为
REQUIRED
:如果事务管理员开启了一个事务,那么如果事务协调员就会加入这个事务,所以他们两个都是用同一个事务都是T1;如果事务管理员没有开事务,那么事务协调员就会直接新建一个事务REQUIRES_NEW
:事务管理员不管开启还是没有开启一个事务,事务协调员都会再新建一个事务SUPPORTS
:如果事务管理员原来有事务,那么事务协调员就会加入这个事务;如果事务管理员原来没有事务,那么事务协调员不会加入,不要事务了NOT_SUPPORTED
:原来事务管理员来的时候有没有事务,事务协调员都不会有事务MANDATORY
:必须有事务,事务管理员来的时候带的有事务,事务协调员就会加入这个事务,如果事务管理员来的时候没有事务,那么就会报错NEVER
:不需要带的有事务,事务管理员来的时候如果带了事务,就会出现错误;如果不带事务的话,事务协调员也不会有事务事务传播应用
场景A:生成订单业务
场景B:生成订单业务
声明式事务(注解)
名称:@Transactional
类型:方法注解、类注解、接口注解(主流)
位置:方法定义上方,类定义上方,接口定义上方
作用:设置当前类/接口中所有的方法或具体方法开启事务,并指定相关事务属性
范例
@Transactional{
readOnly = false,
timeout = -1,
isolation = Isolation.DEFAULT,
rollbackFor = {ArithmeticException.class, IOException.class},
noRollbackFor = {}
propagation = Propagation.REQUIRES_NEW
}
名称:tx:annotation-driven
类型:标签
归属:beans标签
作用:开启事务注解驱动,并指定对应的事务管理器
范例
<tx:annotation-driven transaction-manager="txManager"/>
注解驱动
名称:@EnableTransactionManagement
类型:类注解
位置:Spring注解配置类上方
作用:开启注解驱动,等同XML格式中的注解驱动
范例
@Configuration
@ComponentScan("top.hellocode")
@EnableTransactionManagement
public class SpringConfig {
}
public class TransactionManagerConfig{
@Bean
public PlatformTransactionManager getTransactionManager(@Autowired DataSource dataSource){
return new DataSourceTransactionManager(dataSource);
}
}
了解学习即可
提供标准的sql语句操作API
public void save(Account account){
String sql = "insert into account(name,money) values(?,?)";
jdbcTemplate.update(sql,account.getName(),account.getMoney());
}
NamedParameterJdbcTemplate(了解)
提供标准的具名sql语句操作API
public void save(Account account){
String sql = "insert into account(name,money) values(:name,:money)";
Map pm = new HashMap();
pm.put("name",account.getName());
pm.put("money",account.getMoney());
jdbcTemplate.update(sql,pm);
}
public void changeMoney(Integer id, Double money) {
redisTemplate.opsForValue().set("account:id:"+id,money);
}
public Double findMondyById(Integer id) {
Object money = redisTemplate.opsForValue().get("account:id:" + id);
return new Double(money.toString());
}
策略模式
装饰模式